절차와 기능의 차이를 진정으로 이해


114

절차 적 프로그래밍 패러다임 과 함수 프로그래밍 패러다임 의 차이점을 이해하는 데 정말 어려움을 겪고 있습니다.

다음은 함수형 프로그래밍 에 대한 Wikipedia 항목의 처음 두 단락입니다 .

컴퓨터 과학에서 함수형 프로그래밍은 계산을 수학적 함수의 평가로 취급하고 상태 및 변경 가능한 데이터를 피하는 프로그래밍 패러다임입니다. 상태의 변화를 강조하는 명령형 프로그래밍 스타일과 달리 함수의 적용을 강조합니다. 함수형 프로그래밍은 함수 정의, 함수 적용 및 재귀를 조사하기 위해 1930 년대에 개발 된 공식 시스템 인 람다 미적분에 뿌리를두고 있습니다. 많은 함수형 프로그래밍 언어는 람다 미적분에 대한 정교함으로 볼 수 있습니다.

실제로 수학적 함수와 명령형 프로그래밍에서 사용되는 "함수"개념의 차이점은 명령형 함수는 프로그램 상태의 값을 변경하는 부작용을 가질 수 있다는 것입니다. 이 때문에 참조 투명성이 부족합니다. 즉, 동일한 언어 표현이 실행중인 프로그램의 상태에 따라 다른 시간에 다른 값을 생성 할 수 있습니다. 반대로 함수 코드에서 함수의 출력 값은 함수에 입력 된 인수에만 의존하므로 인수 f에 대해 동일한 값 으로 함수를 두 번 호출 x하면 동일한 결과가 생성됩니다.f(x)두 번. 부작용을 제거하면 프로그램의 동작을 훨씬 쉽게 이해하고 예측할 수 있으며, 이는 함수형 프로그래밍 개발의 핵심 동기 중 하나입니다.

2 항에서

반대로 함수 코드에서 함수의 출력 값은 함수에 입력 된 인수에만 의존하므로 인수 f에 대해 동일한 값 으로 함수를 두 번 호출 x하면 f(x)두 번 모두 동일한 결과가 생성됩니다 .

절차 적 프로그래밍의 경우와 똑같지 않습니까?

눈에 띄는 절차 적 대 기능적 측면에서 무엇을 찾아야합니까?


1
Abafei의 "Charming Python : Functional Programming in Python"링크가 끊어졌습니다. : 여기에 링크의 좋은 세트입니다 ibm.com/developerworks/linux/library/l-prog/index.html ibm.com/developerworks/linux/library/l-prog2/index.html
크리스 Koknat

이것의 또 다른 측면은 이름 지정입니다. 예 : JavaScript와 Common Lisp에서는 부작용이 허용 되더라도 함수라는 용어를 사용하며 Scheme에서는 동일하게 proceduere라고합니다. 순수한 CL 함수는 순수한 기능 체계 프로 시저로 작성 될 수 있습니다. Scheme에 관한 거의 모든 책은 절차라는 용어를 사용합니다. 이는 표준에서 사용되는 유형이기 때문에 절차 적이거나 기능적인 것과는 아무 관련이 없습니다.
Sylwester 2015 년

답변:


276

함수형 프로그래밍

함수형 프로그래밍은 함수를 값으로 처리하는 능력을 나타냅니다.

"일반"값과의 비유를 고려해 봅시다. 두 개의 정수 값을 가져 와서 +새 정수를 얻기 위해 연산자를 사용하여 결합 할 수 있습니다 . 또는 정수에 부동 소수점 수를 곱하여 부동 소수점 수를 얻을 수 있습니다.

함수형 프로그래밍에서는 compose 또는 lift 같은 연산자를 사용하여 두 함수 값을 결합하여 새로운 함수 값을 생성 할 수 있습니다 . 또는 함수 값과 데이터 값을 결합하여 map 또는 fold 와 같은 연산자를 사용하여 새로운 데이터 값을 생성 할 수 있습니다 .

많은 언어에는 함수형 프로그래밍 기능이 있습니다. 일반적으로 함수형 언어로 간주되지 않는 언어도 있습니다. Grandfather FORTRAN조차도 함수 값을 지원했지만 함수 결합 연산자 방식에서는 많이 제공하지 않았습니다. 언어를 "기능적"이라고 부르려면 함수형 프로그래밍 기능을 크게 수용해야합니다.

절차 적 프로그래밍

절차 적 프로그래밍은 복사 및 붙여 넣기에 의존하지 않고 여러 위치에서 이러한 명령어를 호출 할 수 있도록 일반적인 명령어 시퀀스를 프로 시저로 캡슐화하는 기능을 말합니다. 프로시 저는 프로그래밍에서 매우 초기 개발 이었기 때문에이 기능은 기계 또는 어셈블리 언어 프로그래밍에서 요구하는 프로그래밍 스타일과 거의 항상 연결되어 있습니다.이 스타일은 저장 위치 및 해당 위치간에 데이터를 이동하는 명령의 개념을 강조하는 스타일입니다.

대조

두 스타일은 정반대가 아닙니다. 서로 다를뿐입니다. 두 스타일을 완전히 포용하는 언어가 있습니다 (예 : LISP). 다음 시나리오는 두 스타일의 차이점을 보여줄 수 있습니다. 목록의 모든 단어에 홀수 문자가 있는지 확인하려는 넌센스 요구 사항에 대한 코드를 작성해 보겠습니다. 첫째, 절차 적 스타일 :

function allOdd(words) {
  var result = true;
  for (var i = 0; i < length(words); ++i) {
    var len = length(words[i]);
    if (!odd(len)) {
      result = false;
      break;
    }
  }
  return result;
}

이 예는 이해할 수 있다는 점을 감안할 것입니다. 이제 기능적인 스타일 :

function allOdd(words) {
  return apply(and, map(compose(odd, length), words));
}

이 정의는 내부에서 밖으로 작동하여 다음 작업을 수행합니다.

  1. compose(odd, length)oddlength함수를 결합하여 문자열 길이가 홀수인지 여부를 결정하는 새 함수를 생성합니다.
  2. map(..., words)의 각 요소에 대해 새 함수를 호출하여 words궁극적으로 각각 해당 단어에 홀수 문자가 있는지 여부를 나타내는 새로운 부울 값 목록을 반환합니다.
  3. apply(and, ...)결과 목록에 "및"연산자를 적용 하고 , 최종 결과를 생성하도록 함께 부울 모두 -ing.

이 예제에서 절차 적 프로그래밍은 변수에서 값을 이동하고 최종 결과를 생성하는 데 필요한 작업을 명시 적으로 설명하는 것과 매우 관련이 있음을 알 수 있습니다. 대조적으로, 기능 스타일은 초기 입력을 최종 출력으로 변환하는 데 필요한 기능 조합을 강조합니다.

이 예제는 또한 절차 적 코드와 기능적 코드의 일반적인 상대적 크기를 보여줍니다. 또한 절차 적 코드의 성능 특성이 기능적 코드의 성능 특성보다 더 쉽게 볼 수 있음을 보여줍니다. 고려 사항 : 함수가 목록에있는 모든 단어의 길이를 계산합니까, 아니면 첫 번째 짝수 길이 단어를 찾은 후 즉시 중지합니까? 반면에 기능 코드는 명시적인 알고리즘이 아닌 의도를 주로 표현하기 때문에 고품질 구현이 꽤 심각한 최적화를 수행하도록 허용합니다.

추가 읽기

이 질문은 많이 나옵니다. 예를 들면 다음과 같습니다.

John Backus의 Turing Award 강의는 함수형 프로그래밍의 동기를 매우 자세하게 설명합니다.

프로그래밍이 폰 노이만 스타일에서 해방 될 수 있습니까?

저는 현재의 맥락에서 그 논문을 언급하지 말아야합니다. 왜냐하면 그것은 상당히 기술적이고 매우 빠르게되기 때문입니다. 정말 기초라고 생각하기 때문에 저항 할 수 없었습니다.


부록-2013

해설자들은 인기있는 현대 언어가 절차 적 및 기능적 이상의 다른 프로그래밍 스타일을 제공한다고 지적합니다. 이러한 언어는 종종 다음 프로그래밍 스타일 중 하나 이상을 제공합니다.

  • 쿼리 (예 : 목록 이해, 언어 통합 쿼리)
  • 데이터 흐름 (예 : 암시 적 반복, 대량 작업)
  • 객체 지향 (예 : 캡슐화 된 데이터 및 메서드)
  • 언어 지향 (예 : 응용 프로그램 별 구문, 매크로)

이 응답의 의사 코드 예제가 이러한 다른 스타일에서 사용할 수있는 일부 기능에서 이점을 얻을 수있는 방법에 대한 예제는 아래 설명을 참조하십시오. 특히, 절차 적 예는 거의 모든 상위 레벨 구성의 적용에서 이점을 얻을 수 있습니다.

표시된 예제는 논의중인 두 스타일 간의 차이를 강조하기 위해 이러한 다른 프로그래밍 스타일에서 혼합을 의도적으로 피합니다.


1
실제로 좋은 대답이지만 코드를 약간 단순화 할 수 있습니까? 예 : "function allOdd (words) {foreach (자동 단어) {odd (length (word)? return false :;} return true;}"
Dainius

기능적 스타일은 파이썬의 "기능적 스타일"에 비해 읽기가 꽤 어렵습니다 : def odd_words (words) : return [x for x in words if odd (len (x))]
boxed

@boxed : 귀하의 odd_words(words)정의는 답변의 allOdd. 필터링 및 매핑의 경우 목록 이해가 선호되는 경우가 많지만 여기서 함수 allOdd는 단어 목록을 단일 부울 값으로 줄여야합니다.
ShinNoNoir

@WReach : 다음과 같이 함수 예제를 작성했을 것입니다. function allOdd (words) {return and (odd (length (first (words))), allOdd (rest (words))); } 귀하의 예제보다 더 우아하지는 않지만 꼬리 재귀 언어에서는 명령형 스타일과 동일한 성능 특성을 갖습니다.
mishoo

@mishoo 당신의 가정을 유지하려면 언어가 꼬리 재귀적이고 엄격해야하며 and 연산자가 짧아야합니다.
kqr 2013-12-12

46

함수형 프로그래밍과 명령형 프로그래밍의 실제 차이점은 사고 방식입니다. 명령형 프로그래머는 변수와 메모리 블록을 생각하는 반면, 함수형 프로그래머는 " 입력 데이터를 출력 데이터로 변환 하는 방법"을 생각 합니다. "프로그램"은 파이프 라인입니다. 입력에서 출력으로 가져 오기 위해 데이터 에 대한 변환 세트 . 그것은 "변수를 사용하지 말라"비트가 아니라 IMO에서 흥미로운 부분입니다.

이 사고 방식의 결과로, FP 프로그램은 일반적으로 설명하는 대신에 특정 메커니즘, 일어날 방법 이 일어날 것입니다 - 우리는 분명히 "어디"와 "집계"라는 뜻을 무엇을 "선택"을 명시 할 수 있다면 때문에 강력한, 우리를 AsParallel ()과 마찬가지로 구현을 자유롭게 교체 할 수 있으며 갑자기 단일 스레드 앱이 n 코어로 확장됩니다 .


코드 예제 조각을 사용하여 두 가지를 대조 할 수 있습니까? 진정 감사합니다
Philoxopher 2011 년

1
@KerxPhilo : 다음은 매우 간단한 작업입니다 (1에서 n까지의 숫자 추가). 필수 : 현재 숫자를 수정하고 지금까지 합계를 수정합니다. 코드 : int i, sum; 합계 = 0; for (i = 1; i <= n; i ++) {sum + = i; }. 기능적 (Haskell) : 게으른 숫자 목록을 가져와 0에 더하면서 함께 접습니다. 코드 : foldl (+) 0 [1..n]. 댓글에 서식이 없습니다.
dirkt 2014 년

답변에 +1. 즉, 함수형 프로그래밍은 가능할 때마다 부작용없이 함수를 작성하는 것입니다. 즉, 동일한 매개 변수가 주어지면 함수는 항상 동일한 것을 반환합니다. 이것이 기초입니다. 극단적으로 접근하면 부작용 (항상 필요)이 격리되고 나머지 함수는 입력 데이터를 출력 데이터로 변환하기 만하면됩니다.
beluchin

12
     Isn't that the same exact case for procedural programming?

아니요, 절차 코드에는 부작용이있을 수 있기 때문입니다. 예를 들어, 호출 사이에 상태를 저장할 수 있습니다.

즉, 절차 적 언어로이 제약 조건을 충족하는 코드를 작성할 수 있습니다. 또한 기능적으로 간주되는 일부 언어에서이 제약 조건을 위반하는 코드를 작성할 수도 있습니다.


1
예와 비교를 보여줄 수 있습니까? 가능하다면 정말 감사합니다.
Philoxopher 2011 년

8
C의 rand () 함수는 모든 호출에 대해 다른 결과를 제공합니다. 통화 사이의 상태를 저장합니다. 참조 적으로 투명하지 않습니다. 이에 비해 C ++의 std :: max (a, b)는 동일한 인수가 주어지면 항상 동일한 결과를 반환하며 부작용이 없습니다 (내가 알고있는 ...).
Andy Thomas

11

WReach의 대답에 동의하지 않습니다. 불일치가 어디에서 왔는지 확인하기 위해 그의 대답을 조금 분해 해 보겠습니다.

첫째, 그의 코드 :

function allOdd(words) {
  var result = true;
  for (var i = 0; i < length(words); ++i) {
    var len = length(words[i]);
    if (!odd(len)) {
      result = false;
      break;
    }
  }
  return result;
}

function allOdd(words) {
  return apply(and, map(compose(odd, length), words));
}

가장 먼저 주목해야 할 것은 그가 합병하고 있다는 것입니다.

  • 기능의
  • 표현 지향적이고
  • 반복자 중심

반복적 스타일 프로그래밍이 일반적인 기능적 스타일보다 더 명시적인 제어 흐름을 가질 수있는 능력이 없습니다.

이것에 대해 빨리 이야기합시다.

표현 중심 스타일은 가능한 한 사물 을 사물로 평가 하는 스타일입니다 . 함수형 언어는 표현에 대한 사랑으로 유명하지만 실제로는 조합 가능한 표현 없이도 함수형 언어를 가질 수 있습니다. 표현 이 없는 문장 만 만들어 보겠습니다 .

lengths: map words length
each_odd: map lengths odd
all_odd: reduce each_odd and

이것은 함수가 명령문과 바인딩의 체인을 통해 순전히 연결된다는 점을 제외하면 이전에 제공된 것과 거의 동일합니다.

반복자 중심의 프로그래밍 스타일은 Python이 취하는 스타일 일 수 있습니다. 순전히 반복적 인 반복자 중심 스타일을 사용하겠습니다 .

def all_odd(words):
    lengths = (len(word) for word in words)
    each_odd = (odd(length) for length in lengths)
    return all(each_odd)

각 절은 반복적 인 프로세스이고 스택 프레임의 명시 적 일시 중지 및 재개에 의해 함께 묶여 있기 때문에 이것은 작동하지 않습니다. 구문은 기능적 언어에서 부분적으로 영감을받을 수 있지만 완전히 반복적 인 구현에 적용됩니다.

물론 이것을 압축 할 수 있습니다.

def all_odd(words):
    return all(odd(len(word)) for word in words)

명령형은 지금 그렇게 나쁘게 보이지 않습니까? :)

마지막 요점은보다 명확한 제어 흐름에 관한 것입니다. 이것을 사용하기 위해 원래 코드를 다시 작성해 보겠습니다.

function allOdd(words) {
    for (var i = 0; i < length(words); ++i) {
        if (!odd(length(words[i]))) {
            return false;
        }
    }
    return true;
}

반복기를 사용하면 다음을 수행 할 수 있습니다.

function allOdd(words) {
    for (word : words) { if (!odd(length(word))) { return false; } }
    return true;
}

그래서 입니다 차이가 사이 인 경우 함수형 언어의 요점은 :

return all(odd(len(word)) for word in words)
return apply(and, map(compose(odd, length), words))
for (word : words) { if (!odd(length(word))) { return false; } }
return true;


함수형 프로그래밍 언어의 주요 특징은 일반적인 프로그래밍 모델의 일부로 변이를 제거한다는 것입니다. 사람들은 종종 이것을 함수형 프로그래밍 언어가 명령문이 없거나 표현식을 사용하지 않는다는 것을 의미하지만 이것은 단순화입니다. 기능적 언어는 명시 적 계산을 행동 선언으로 대체 한 다음 언어가 감소를 수행합니다.

이 기능의 하위 집합으로 자신을 제한하면 프로그램의 동작에 대해 더 많은 보증을받을 수 있으며이를 통해보다 자유롭게 구성 할 수 있습니다.

기능적 언어가있는 경우 새 함수를 만드는 것은 일반적으로 밀접하게 관련된 함수를 구성하는 것만 큼 간단합니다.

all = partial(apply, and)

함수의 전역 종속성을 명시 적으로 제어하지 않은 경우 간단하지 않거나 가능하지 않을 수도 있습니다. 함수형 프로그래밍의 가장 큰 특징은보다 일반적인 추상화를 지속적으로 생성 할 수 있고 더 큰 전체로 결합 될 수 있다는 신뢰입니다.


당신은 내가 확신 해요, 알 applyA와 아주 동일한 작업하지 않습니다 fold또는 reduce내가 매우 일반적인 알고리즘을 가지고있는 좋은 능력에 동의하지만,.
Benedict Lee

내가 들어 본 적이없는 apply것을 의미하는 foldreduce,하지만 그것은 부울을 반환하는이 상황에 있어야하는 것처럼 나에게 보인다.
Veedrac

아, 그래, 이름이 헷갈 렸어. 정리해 주셔서 감사합니다.
Benedict Lee

6

절차 적 패러다임 (대신 "구조화 된 프로그래밍"이라고 말해야합니까?)에서, 당신은 가변 메모리와이를 어떤 순서로 읽고 / 쓰기하는 명령어를 공유했습니다.

기능적 패러다임에는 변수와 함수가 있습니다 (수학적 의미에서 : 변수는 시간이 지남에 따라 변하지 않으며 함수는 입력에 따라 무언가를 계산할 수 있습니다).

(이는 지나치게 단순화되어 있습니다. 예를 들어 FPL은 일반적으로 가변 메모리 작업을위한 기능을 가지고있는 반면, 절차 적 언어는 종종 고차 절차를 지원할 수 있으므로 일이 명확하지는 않지만 아이디어를 얻을 수 있습니다.)



2

기호 (변수 또는 함수 이름)의 의미를 추론하기 위해 함수형 프로그래밍에서 현재 범위와 기호 이름의 두 가지만 알면됩니다. 불변성을 가진 순전히 기능적인 언어를 가지고 있다면이 두 가지 모두 "정적"(심하게 오버로드 된 이름에 대해 죄송합니다) 개념입니다. 즉, 소스 코드를 보면 현재 범위와 이름을 모두 볼 수 있습니다.

절차 적 프로그래밍에서이면에있는 가치가 무엇인지에 대한 질문에 답하려면 x거기에 어떻게 도달했는지 알아야합니다. 범위와 이름만으로는 충분하지 않습니다. 이 실행 경로는 "런타임"속성이고 많은 다른 것들에 의존 할 수 있기 때문에 이것이 제가 가장 큰 도전이라고 생각하는 것입니다. 대부분의 사람들은 실행 경로를 복구하지 않고 디버그하는 방법 만 배웁니다.


1

최근에 표현 문제 의 차이를 생각하고 있습니다. Phil Wadler의 설명 은 자주 인용되지만 이 질문에 대한 대답 은 이해하기 더 쉬울 것입니다. 기본적으로 명령형 언어는 문제에 대한 한 가지 접근 방식을 선택하는 반면 기능적 언어는 다른 방식을 선택하는 경향이 있습니다.


0

두 프로그래밍 패러다임의 명확한 차이점은 상태입니다.

함수형 프로그래밍에서는 상태를 피합니다. 간단히 말해서, 값이 할당 된 변수는 없습니다.

예:

def double(x):
    return x * 2

def doubleLst(lst):
    return list(map(double, action))

그러나 절차 적 프로그래밍은 상태를 사용합니다.

예:

def doubleLst(lst):
    for i in range(len(lst)):
        lst[i] = lst[i] * 2  # assigning of value i.e. mutation of state
    return lst
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.