함수형 프로그래밍에서 “기억”값 것과 같은 간단한

함수형 프로그래밍을 배우는 작업을 스스로 결정했습니다. 지금까지 폭발이었고, 나는 ‘빛을 보았습니다’. 불행히도, 나는 실제로 질문을 반송 할 수있는 기능 프로그래머를 모른다. 스택 교환을 소개합니다.

웹 / 소프트웨어 개발 과정을 수강하지만 강사는 기능적 프로그래밍에 익숙하지 않습니다. 그는 그것을 사용하는 것이 좋으며, 방금 코드를 더 잘 읽을 수 있도록 작동 방식을 이해하도록 도와달라고 요청했습니다.

가장 좋은 방법은 가치를 거듭 제곱하는 것과 같은 간단한 수학 함수를 설명하는 것입니다. 이론적으로는 미리 작성된 기능으로 쉽게 수행 할 수 있지만 예제의 목적을 잃을 것입니다.

어쨌든, 나는 가치를 유지하는 방법을 알아내는 데 어려움을 겪고 있습니다. 이것은 함수형 프로그래밍이므로 변수를 변경할 수 없습니다. 필연적으로 이것을 코딩해야한다면 다음과 같이 보일 것입니다.

(다음은 모두 의사 코드입니다)

f(x,y) {
  int z = x;
  for(int i = 0, i < y; i++){
    x = x * z;
  }
  return x;
}

함수형 프로그래밍에서는 확실하지 않았습니다. 이것이 내가 생각해 낸 것입니다.

f(x,y,z){
  if z == 'null',
    f(x,y,x);
  else if y > 1,
    f(x*z,y-1,z);
  else
    return x;
}

이게 옳은 거니? z두 경우 모두 값을 보유해야 하지만 함수 프로그래밍 에서이 작업을 수행하는 방법을 잘 모르겠습니다. 이론적으로는 내가 한 방식으로 작동하지만 그것이 옳은지 확실하지 않았습니다. 더 좋은 방법이 있습니까?



답변

우선, “빛을보고”축하합니다. 시야를 넓혀 소프트웨어 세계를 더 나은 곳으로 만들었습니다.

둘째, 함수형 프로그래밍을 이해하지 못하는 교수가 코드에 대해 유용한 정보를 말할 수있는 방법은 없습니다. 대부분의 웹 개발은 HTML / CSS / JavaScript를 사용하여 수행되므로 웹 개발 과정에서 그렇게 놀라운 것은 아닙니다. 실제로 웹 개발 학습에 얼마나 관심이 있는지에 따라 교수가 가르치는 도구를 배우려는 노력을 기울이고 싶을 수도 있습니다.

명시된 질문을 해결하기 위해 : 명령형 코드가 루프를 사용하는 경우 기능 코드가 재귀 될 가능성이 있습니다.

(* raises x to the power of y *)
fun pow (x: real) (y: int) : real =
    if y = 1 then x else x * (pow x (y-1))

이 알고리즘은 실제로 명령 코드와 거의 동일합니다. 실제로, 위의 루프를 반복 재귀 프로세스의 구문 설탕으로 간주 할 수 있습니다.

부수적으로 z, 실제로 명령형 코드 나 기능 코드 중 하나 의 값이 필요하지 않습니다 . 명령 함수를 다음과 같이 작성해야합니다.

def pow(x, y):
    var ret = 1
    for (i = 0; i < y; i++)
         ret = ret * x
    return ret

변수의 의미를 바꾸는 대신에 x.


답변

이것은 실제로 가든 헤드의 답변에 대한 부록이지만,보고있는 패턴의 이름이 접 히고 있음을 지적하고 싶습니다.

함수형 프로그래밍에서 접기 는 각 작업 사이의 값을 “기억하는”일련의 값을 결합하는 방법입니다. 숫자 목록을 반드시 추가하십시오.

def sum_all(xs):
  total = 0
  for x in xs:
    total = total + x
  return total

우리는 값 목록 수행 xs하고 , 초기 상태0(의해 표현 total이 경우에는). 그런 다음 각 xin 에 대해 일부 결합 작업 (이 경우 추가) 에 따라 xs해당 값을 현재 상태 와 결합 하고 결과를 상태 로 사용합니다 . 본질적 으로와 같습니다 . 이 패턴 은 함수를 인수로받는 함수 인 고차 함수 로 추출 할 수 있습니다 .sum_all([1, 2, 3])(3 + (2 + (1 + 0)))

def fold(items, initial_state, combiner_func):
  state = initial_state
  for item in items:
    state = combiner_func(item, state)
  return state

def sum_all(xs):
  return fold(xs, 0, lambda x y: x + y)

이 구현 fold은 여전히 ​​필수적이지만 재귀 적으로 수행 할 수도 있습니다.

def fold_recursive(items, initial_state, combiner_func):
  if not is_empty(items):
    state = combiner_func(initial_state, first_item(items))
    return fold_recursive(rest_items(items), state, combiner_func)
  else:
    return initial_state

폴드로 표현하면 기능은 다음과 같습니다.

def exponent(base, power):
  return fold(repeat(base, power), 1, lambda x y: x * y))

… where repeat(x, n)n사본 목록을 리턴합니다 x.

많은 기능, 특히 함수형 프로그래밍에 적합한 언어는 표준 라이브러리에서 접기를 제공합니다. Javascript조차도 이름으로 제공합니다 reduce. 일반적으로 재귀를 사용하여 어떤 종류의 루프에서 값을 “기억”하는 경우 접을 수 있습니다.


답변

이것은지도와 접힘을 설명하는 데 도움이되는 보충 답변입니다. 아래 예에서는이 목록을 사용하겠습니다. 이 목록은 변경할 수 없으므로 변경되지 않습니다.

var numbers = [1, 2, 3, 4, 5]

예제에서 숫자를 사용하면 코드를 쉽게 읽을 수 있으므로 사용할 것입니다. 폴드는 전통적인 명령형 루프가 사용될 수있는 모든 것에 사용될 수 있습니다.

지도는 무언가의 목록 및 기능을 소요하고 기능을 사용하여 수정 된 목록을 반환합니다. 각 항목은 함수에 전달되고 함수가 반환하는 모든 항목이됩니다.

가장 쉬운 예는 목록의 각 숫자에 숫자를 추가하는 것입니다. 의사 코드를 사용하여 언어에 관계없이 사용합니다.

function add-two(n):
    return n + 2

var numbers2 =
    map(add-two, numbers)

를 인쇄 numbers2하면 [3, 4, 5, 6, 7]각 요소에 2가 추가 된 첫 번째 목록이 표시됩니다. 사용할 함수 add-two가 주어졌습니다 map.

접기 기능은 두 가지 인수를 가져와야 한다는 점을 제외하면 비슷합니다. 첫 번째 인수는 일반적으로 누산기 (왼쪽 접힘)가 가장 일반적입니다. 누산기는 루핑하는 동안 전달되는 데이터입니다. 두 번째 인수는 목록의 현재 항목입니다. map기능에 대해서는 위와 같습니다 .

function add-together(n1, n2):
    return n1 + n2

var sum =
    fold(add-together, 0, numbers)

인쇄 sum하면 숫자 목록의 합계가 표시됩니다. 15.

다음과 같은 주장을 fold해야합니다.

  1. 이것이 우리가 접는 기능입니다. 접기는 함수에 현재 누산기와 목록의 현재 항목을 전달합니다. 함수가 반환하는 것이 무엇이든 새로운 누산기가되고 다음에 함수에 전달됩니다. FP 스타일을 반복 할 때 값을 “기억”하는 방법입니다. 나는 2 개의 숫자를 취해 더하는 함수를주었습니다.

  2. 이것은 초기 누산기입니다. 목록의 항목이 처리되기 전에 누산기가 시작되는 것. 숫자를 합산 할 때 숫자를 더하기 전에 총계는 얼마입니까? 0, 두 번째 인수로 전달했습니다.

  3. 마지막으로지도와 마찬가지로 처리 할 숫자 목록도 전달합니다.

접힌 부분이 여전히 이해가되지 않는다면 이것을 고려하십시오. 당신이 쓸 때 :

# Notice I passed the plus operator directly this time,
#  instead of wrapping it in another function.
fold(+, 0, numbers)

기본적으로 전달 된 함수를 목록의 각 항목 사이에 놓고 왼쪽 또는 오른쪽에 초기 누적기를 추가합니다 (왼쪽 또는 오른쪽 접기의 경우에 따라 다름).

[1, 2, 3, 4, 5]

된다 :

0 + 1 + 2 + 3 + 4 + 5
^ Note the initial accumulator being added onto the left (for a left fold).

15와 같습니다.

map한 목록을 같은 길이의 다른 목록으로 바꾸려면를 사용하십시오 .

를 사용 fold하면 번호 목록을 합산처럼, 하나의 값으로 목록을 설정하고자 할 때.

@Jorg가 주석에서 지적했듯이 “단일 값”은 숫자와 같은 단순한 것이 될 필요는 없습니다. 목록이나 튜플을 포함한 단일 객체 일 수 있습니다! 제가 실제로 접기를 클릭하는 방식은 접기의 관점 에서지도를 정의하는 것이 었습니다 . 누산기가 어떻게 목록인지 확인하십시오.

function map(f, list):
    fold(
        function(xs, x): # xs is the list that has been processed so far
            xs.add( f(x) ) # Add returns the list instead of mutating it
        , [] # Before any of the list has been processed, we have an empty list
        , list)

솔직히 말해서, 일단 각각을 이해하면 거의 모든 루핑이 접기 또는 맵으로 대체 될 수 있음을 알게 될 것입니다.


답변

기본 제공 기능으로는 해결할 수없는 좋은 문제를 찾기가 어렵습니다. 그리고 그것이 내장되어 있다면, 언어 x에서 좋은 스타일의 예가되어야합니다.

예를 들어 haskell에서는 (^)Prelude에 이미 기능 이 있습니다.

또는 더 프로그래밍 방식으로 수행하려는 경우 product (replicate y x)

내가 말하는 것은 당신이 제공하는 기능을 사용하지 않으면 스타일 / 언어의 장점을 보여주기가 어렵다는 것입니다. 그러나 장면 뒤에서 어떻게 작동하는지 보여주는 좋은 단계 일 수 있지만 사용중인 언어에 관계없이 가장 좋은 방법을 코딩 한 다음 필요한 경우 진행 상황을 이해하도록 도와야한다고 생각합니다.


답변