명령형이 아닌 함수형 프로그래밍 언어를 선언적으로 만드는 것은 무엇입니까?


17

함수형 프로그래밍의 이점을 설명하는 많은 기사에서 Haskell, ML, Scala 또는 Clojure와 같은 함수형 프로그래밍 언어를 C / C ++ / C # / Java와 같은 명령형 언어와 구별되는 "선언적 언어"라고합니다. 제 질문은 기능적 프로그래밍 언어를 명령형이 아닌 선언적으로 만드는 것입니다.

선언적 프로그래밍과 명령 적 프로그래밍의 차이점을 설명하는 자주 설명되는 설명은 명령형 프로그래밍에서 선언적 언어의 "무엇을해야하는지"와 반대로 "어떻게해야하는지"라고 컴퓨터에 알려주는 것입니다. 이 설명에서 내가 가진 문제는 모든 프로그래밍 언어로 끊임없이 두 가지를하고 있다는 것입니다. 가장 낮은 수준의 어셈블리로 내려가도 컴퓨터에 "무엇을해야하는지"라고 말하고 CPU에 두 개의 숫자를 추가하라고 지시하면 추가 방법을 지시하지 않습니다. Haskell과 같은 고급 순수 기능 언어 인 스펙트럼의 다른 쪽 끝으로 가면 실제로 컴퓨터에 특정 작업을 수행하는 방법을 알려주는 것입니다. 그것은 컴퓨터가 혼자서 달성하는 방법을 모르는 특정 작업을 수행하기 위해 프로그램이 일련의 명령입니다. Haskell, Clojure 등과 같은 언어는 분명히 C / C ++ / C # / Java보다 높은 수준이며 게으른 평가, 변경 불가능한 데이터 구조, 익명 함수, 카레, 영구 데이터 구조 등과 같은 기능을 제공합니다. 함수형 프로그래밍은 가능하고 효율적이지만 선언적 언어로 분류하지는 않습니다.

나를위한 순수한 선언적 언어는 선언으로 만 구성된 언어 일 것입니다. 이러한 언어의 예는 CSS입니다 (예 : CSS는 기술적으로 프로그래밍 언어가 아닙니다). CSS에는 페이지의 HTML 및 Javascript에서 사용되는 스타일 선언 만 포함되어 있습니다. CSS는 선언을 만드는 것 외에는 아무것도 할 수 없으며 클래스 함수를 만들 수 없습니다. 예를 들어 일부 매개 변수를 기반으로 표시 할 스타일을 결정하는 함수, CSS 스크립트를 실행할 수 없습니다 등. 선언적 언어를 설명합니다. 프로그래밍 언어).

최신 정보:

나는 최근에 Prolog를 가지고 놀았으며, Prolog는 완전히 선언적인 프로그래밍 언어가 아니라면 완전히 선언적인 언어 (적어도 내 의견으로는)에 가장 가까운 프로그래밍 언어입니다. Prolog에서 프로그래밍을 정교하게하려면 사실 (특정 입력에 대해 true를 리턴하는 술어 함수) 또는 규칙 (입력에 따라 주어진 조건 / 패턴에 대해 true를 리턴하는 술어 함수), 규칙을 선언하여 선언을 수행합니다. 패턴 매칭 기술을 사용하여 정의됩니다. 프롤로그에서 작업을 수행하려면 하나 이상의 술어 입력을 변수로 대체하여 지식 기반 데이터베이스에 쿼리하고 프롤로그는 술어가 성공한 변수의 값을 찾으려고 시도합니다.

내 요점은 프롤로그에 명령 지침이 없다는 것입니다. 기본적으로 컴퓨터에 알고있는 것을 말하고 지식에 대해 묻습니다. 함수형 프로그래밍 언어에서는 메모리 위치를 직접 조작하거나 단계별로 계산을 작성하지 않더라도 여전히 명령을 내립니다. 즉, 값을 가져오고 함수 X를 호출하여 1을 추가하는 등의 작업을 수행합니다. 하스켈, ML, 스칼라 또는 클로저에서의 프로그래밍이 이런 의미에서 선언적이라고 말하고 싶지는 않습니다. 위에서 설명한 의미에서 적절하고 진실하며 순수한 함수형 프로그래밍입니다.


당신이 그를 필요로 할 때 @JimmyHoffa는 어디에 있습니까?

2
1. C #과 최신 C ++은 매우 기능적인 언어입니다. 2. 비슷한 목적을 달성하기 위해 SQL과 Java를 작성하는 것의 차이점을 생각하십시오 (조인이있는 복잡한 쿼리 일 수 있음). C #에서 LINQ가 간단한 foreach 루프를 작성하는 것과 어떻게 다른지 생각할 수 있습니다.
AK_

또한
dilogrence

1
차이점을 인식하는 열쇠 중 하나는 할당 연산자를 사용할 때 와 하스켈의 정의 / 선언 연산자를 사용하는 경우입니다. 예를 들어 할당 연산자가 없습니다 . 이 두 연산자의 동작은 매우 다르므로 코드를 다르게 디자인해야합니다.
Jimmy Hoffa

1
불행히도 할당 연산자가 (let [x 1] (let [x (+ x 2)] (let [x (* x x)] x)))없어도이 작업을 수행 할 수 있습니다 (Choju 이해했습니다. 내 원래의 질문은 이것이 int와 다른 점입니다 x = 1; x += 2; x *= x; return x;. 내 의견으로는 대부분 동일합니다.
ALXGTV

답변:


16

물건을 선언하고 기계를 가르치는 사이에 선을 그리는 것 같습니다. 그렇게 단단하고 빠른 분리는 없습니다. 명령형 프로그래밍에 지시되는 머신은 물리적 하드웨어 일 필요가 없으므로 해석의 자유가 많습니다. 거의 모든 것이 올바른 추상 기계에 대한 명시 적 프로그램으로 볼 수 있습니다. 예를 들어, CSS는 주로 선택기를 해결하고 선택된 DOM 객체의 속성을 설정하는 시스템을 프로그래밍하기위한 고급 언어로 볼 수 있습니다.

문제는 그러한 관점이 합리적인지 여부와 반대로, 명령 시퀀스 가 계산되는 결과 의 선언 과 얼마나 밀접한가에 관한 것입니다. CSS의 경우 선언적 관점이 더 유용합니다. C의 경우 명령형 관점이 분명히 우세합니다. 하스켈과 같은 언어는 ...

언어는 구체적인 의미론을 지정했습니다. 즉, 물론 프로그램을 일련의 작업으로 해석 할 수 있습니다. 기본 작업을 선택하여 상용 하드웨어에 잘 매핑되도록 너무 많은 노력을 기울이지 않아도됩니다 (STG 시스템 및 기타 모델의 기능).

그러나 Haskell 프로그램이 작성되는 방식은 계산할 결과에 대한 설명으로 현명하게 읽을 수 있습니다 . 예를 들어 첫 N 계승의 합을 계산하는 프로그램을 예로 들어 보겠습니다.

sum_of_fac n = sum [product [1..i] | i <- ..n]

이것을 설탕을 제거하고 STG 연산의 시퀀스로 읽을 수 있지만 결과에 대한 설명 으로 읽는 것이 훨씬 더 자연 스럽습니다 . 결과는 [1..i]모두 i= 0,…, n 의 곱의 합 입니다. 그리고 그것은 거의 모든 C 프로그램이나 함수보다 훨씬 더 선언적입니다.


1
내가이 질문을 한 이유는 많은 사람들이 C / C ++ / C # / Java의 패러다임과 완전히 분리 된 새로운 고유 패러다임 또는 실제로는 함수형 프로그래밍이 또 다른 상위 수준의 형식 인 경우 Python으로 인해 함수형 프로그래밍을 홍보하려고하기 때문입니다. 명령형 프로그래밍.
ALXGTV

여기서 내가 의미하는 바를 설명하기 위해 sum_of_fac를 정의하고 있지만 sum_of_fac는 구멍 응용 프로그램이 수행하지 않는 한 그 자체로는 거의 쓸모가 없습니다. 결과를 사용하여 특정 작업을 달성하기 위해 빌드되는 다른 결과를 계산해야합니다. 프로그램이 해결하도록 설계된 작업.
ALXGTV

6
@ALXGTV 함수형 프로그래밍 매우 다른 패러다임입니다. 비록 당신이 그것을 명령형으로 읽는 것에 대해 단호하더라도 (매우 광범위한 분류이므로 프로그래밍 패러다임보다 더 많은 것이 있습니다). 이 코드를 기반으로하는 다른 코드도 선언적으로 읽을 수 있습니다 (예 :) map (sum_of_fac . read) (lines fileContent). 물론 어느 시점에서 I / O가 작동하지만 내가 말했듯이 연속체입니다. Haskell 프로그램의 일부는 C가 일종의 선언적 표현 구문 ( x + y, not load x; load y; add;)을 갖는 것처럼 더 필수적입니다.

1
@ALXGTV 직관이 정확합니다. 선언적 프로그래밍 "그냥"은 특정 명령에 대한보다 높은 수준의 추상화를 제공합니다. 나는 이것이 큰 문제라고 주장한다!
Andres F.

1
@Brendan 함수형 프로그래밍이 명령형 프로그래밍의 하위 집합이라고 생각하지 않습니다 (불변성이 FP의 필수 특성도 아닙니다). 순수 정적 형식의 FP의 경우 형식에서 효과가 명백한 명령형 프로그래밍의 슈퍼 세트라고 주장 할 수 있습니다. Haskell이 "세계 최고의 명령형 언어"라는 혀에서 혀를 주장한다는 것을 알고 있습니다.)
Andres F.

21

명령형 프로그램의 기본 단위는 진술 입니다. 명령문은 부작용으로 실행됩니다. 그들은받은 상태를 수정합니다. 일련의 명령문은 일련의 명령이며,이를 수행함을 나타냅니다. 프로그래머는 계산을 수행 할 정확한 순서를 지정합니다. 이것이 사람들에게 컴퓨터 에게 방법 을 알려주는 의미 입니다.

선언적 프로그램의 기본 단위는 표현식 입니다. 식에는 부작용이 없습니다. 입력과 출력 사이의 관계를 지정하여 입력 상태를 수정하지 않고 입력에서 새롭고 별도의 출력을 만듭니다. 표현식의 순서는 관계를 지정하는 일부 표현식이 없으면 의미가 없습니다. 프로그래머는 데이터 간의 관계를 지정하고 프로그램은 이러한 관계에서 계산을 수행하는 순서를 유추합니다. 이것은 사람들에게 컴퓨터 에게 무엇해야하는지 말함으로써 의미하는 것입니다.

명령형 언어에는 표현이 있지만 일을 수행하는 주요 수단은 진술입니다. 마찬가지로 선언적 언어에는 Haskell의 모나드 시퀀스와 같은 문장과 유사한 의미를 가진 표현이 있습니다.do 표기법 있지만 그 핵심은 하나의 큰 표현입니다. 이를 통해 초보자는 매우 필수적인 코드를 작성할 수 있지만 패러다임을 벗어날 때 언어의 진정한 힘이옵니다.


1
예제가 포함되어 있으면 완벽한 답변이 될 것입니다. 선언적 대 명령형을 설명하는 가장 좋은 예는 Linq vs. foreach입니다.
Robert Harvey

7

선언적 프로그래밍과 명령형 프로그래밍을 분리하는 실제 정의 특성은 시퀀스 스타일 명령을 제공하지 않는 선언적 스타일입니다. 하위 수준에서 CPU는 이런 식으로 작동하지만 컴파일러의 관심사입니다.

CSS는 "선언적 언어"라고 제안합니다. 언어라고 부르지는 않겠습니다. JSON, XML, CSV 또는 INI와 같은 데이터 구조 형식으로, 일부 자연 해석기에게 알려진 데이터를 정의하기위한 형식 일뿐입니다.

할당 연산자를 언어에서 제외 시키면 몇 가지 흥미로운 부작용이 발생하며, 이는 선언적 언어로 된 모든 1 단계-2 단계-3 단계 명령을 잃어버린 실제 원인입니다.

함수에서 대입 연산자로 무엇을합니까? 중간 단계를 만듭니다. 그것이 핵심입니다. 할당 연산자는 단계별 방식으로 데이터를 변경하는 데 사용됩니다. 더 이상 데이터를 변경할 수 없으면 모든 단계를 잃고 다음과 같이 끝납니다.

모든 함수에는 하나의 명령문 만 있으며이 명령문은 단일 선언입니다.

이제 단일 진술을 많은 진술처럼 보이게 만드는 많은 방법이 있지만, 예를 들어 속임수입니다. 1 + 3 + (2*4) + 8 - (7 / (8*3))이것은 분명히 단일 진술이지만, 만약 당신이 그것을 다음과 같이 쓰면 ...

1 +
3 +
(
  2*4
) +
8 - 
(
  7 / (8*3)
)

그것은 뇌가 저자가 의도 한 원하는 분해를 더 쉽게 인식 할 수있는 일련의 동작의 출현을 취할 수있다. 나는 종종 C #에서 이와 같은 코드를 사용합니다.

people
  .Where(person => person.MiddleName != null)
  .Select(person => person.MiddleName)
  .Distinct();

이것은 하나의 진술이지만 많은 것으로 분해되는 모습을 보여줍니다. 과제가 없습니다.

또한 위의 두 가지 방법 모두에서-코드가 실제로 실행되는 방식은 즉시 읽는 순서가 아닙니다. 이 코드는 무엇을해야하는지 알려주지 만 방법을 지시하지는 않습니다 . 위의 간단한 산술은 분명히 왼쪽에서 오른쪽으로 작성된 순서대로 실행되지 않으며 위의 C # 예제에서는 위의 세 가지 메소드가 모두 다시 입력되고 순서대로 완료되지 않습니다. 실제로 컴파일러는 코드를 생성합니다. 그것은 그 진술이 원하는 것을 할 것이지만, 반드시 당신이 어떻게 가정 하는지는 아닙니다 .

나는 선언적 코딩의 중간 단계가 아닌 접근 방식에 익숙해지는 것이 실제로 가장 어려운 부분이라고 생각합니다. 왜 Haskell처럼 하스켈처럼 그것을 허용하지 않는 언어가 거의 없기 때문에 하스켈이 그렇게 까다로운가? 합산과 같은 중간 변수의 도움으로 일반적으로하는 일을 달성하기 위해 재미있는 체조를 시작해야합니다.

선언적 언어에서 중간 변수로 무언가를 수행하려면 변수를 매개 변수로 해당 기능을 수행하는 함수에 전달하는 것을 의미합니다. 이것이 재귀가 매우 중요한 이유입니다.

sum xs = (head xs) + sum (tail xs)

당신은 만들 수 없습니다 resultSum변수를하고 통해 루프로 추가 xs, 당신은 단순히 첫 번째 값을, 그리고 합이 다른 모든 것들의 무엇이든에 추가 - 그리고 다른 액세스 모든 것을 당신은 통과해야 xs하는 기능을 tail하기 때문에 당신은 xs에 대한 변수를 만들 수 없으며 명령적인 접근 방식 인 헤드 오프를 터뜨릴 수 없습니다. (그렇습니다. 구조화를 사용할 수는 있지만이 예는 설명을위한 것입니다)


내가 그것을 이해하는 방법은, 당신의 대답에서, 순수 함수형 프로그래밍에서, 전체 프로그램이 반면에 필수적인 프로그래밍 기능 (절차) 작업의 정의 된 순서에 하나 개 이상의 표현을 포함, 많은 단일 표현식 함수로 구성되어 있다는 것입니다
ALXGTV

1 + 3 + (2 * 4) + 8-(7 / (8 * 3)) 실제로 여러 문장 / 표현식입니다. 물론 단일 표현으로 보는 내용에 따라 다릅니다. 요약하자면 함수형 프로그래밍은 기본적으로 세미콜론이없는 프로그래밍입니다.
ALXGTV

1
@ALXGTV 그 선언과 명령 적 스타일의 차이는 수식이 문이 있다면, 그것이 될 것입니다 필수적를 기록대로 실행할 수있다. 그러나 그것은 일련의 명령 문이 아니라 원하는 것을 정의 하는 표현식 이기 때문에 how 라고 말하지 않기 때문에 서면으로 실행되는 것이 필수적이지 않습니다. 따라서 컴파일은 표현식을 줄이거 나 리터럴로 메모 할 수도 있습니다. 명령형 언어도이 작업을 수행하지만 단일 명령문에 넣을 때만 가능합니다. 선언.
Jimmy Hoffa

@ALXGTV 선언은 관계를 정의하고 관계는 가단성입니다. 만약 x = 1 + 2다음 x = 3x = 4 - 1. 시퀀싱 된 명령문은 x = (y = 1; z = 2 + y; return z;)더 이상 가단성, 컴파일러가 여러 가지 방법으로 수행 할 수있는 것이 없을 때 명령어를 정의 합니다. 변경하지 마십시오.
Jimmy Hoffa

당신은 공정한 포인트가 있습니다.
ALXGTV

6

나는 파티에 늦었다는 것을 알고 있지만, 다른 날에는 주현절이 있었으므로 여기에 간다.

기능적, 불변성 및 부작용에 대한 의견은 선언적과 명령 적의 차이점을 설명하거나 선언적 프로그래밍의 의미를 설명 할 때 마크를 놓치지 않습니다. 또한 질문에서 언급했듯이 전체 "무엇을해야하는지"와 "어떻게해야하는지"는 너무 모호하며 실제로 설명하지도 않습니다.

간단한 코드 a = b + c를 기초로하여 몇 가지 다른 언어로 된 문장을보고 아이디어를 얻자.

a = b + cC와 같은 명령형 언어로 쓸 때 현재 값을 b + c변수에 할당합니다 a. 우리는 무엇 a에 대한 근본적인 진술을하지 않습니다 . 오히려 우리는 단순히 프로세스의 단계를 실행하고 있습니다.

우리가 쓸 때 a = b + c선언적 언어, Microsoft Excel에서와 같이, (예, 엑셀 입니다 우리는 프로그래밍 언어와 그들 모두의 아마 가장 선언) 의 관계 주장 사이를 a, b그리고 c이 경우 항상되도록 a의 합이 다른 두. 그것은 과정의 한 단계가 아니며 변하지 않는 보증이며 진실의 선언입니다.

기능적 언어도 선언적이지만 거의 우연히 발생합니다. 예 해스켈에서는 a = b + c또한 만하기 때문에, 불변 관계를 어서 bc불변.

따라서 예, 객체가 불변이고 함수에 부작용이없는 경우 코드가 선언적이지만 명령형 코드와 동일하더라도 중요하지 않습니다. 과제를 피하는 것도 아닙니다. 선언적 코드의 핵심은 관계에 대한 기본적인 진술을하는 것입니다.


0

컴퓨터 에게 무엇해야하는지어떻게 해야하는지 명확하게 구분하지 않는 것이 옳 습니다.

그러나 스펙트럼의 한쪽에서 메모리를 조작하는 방법에 대해 거의 독점적으로 생각합니다. 즉, 문제를 해결하려면 "이 메모리 위치를 x로 설정 한 다음 해당 메모리 위치를 y로 설정하고 메모리 위치 z로 이동하십시오."와 같은 형식으로 컴퓨터에 표시합니다. 다른 메모리 위치의 결과.

Java, C # 등과 같은 관리되는 언어에서는 더 이상 하드웨어 메모리에 직접 액세스 할 수 없습니다. 명령형 프로그래머는 이제 클래스 인스턴스의 정적 변수, 참조 또는 필드에 관심이 있으며 모두 메모리 위치에 대한 어느 정도의 흡수입니다.

OTOH Haskell과 같은 언어에서는 메모리가 완전히 사라졌습니다. 그것은 단순히 그렇지 않습니다

f a b = let y = sum [a..b] in y*y

인수 a와 b를 보유하는 두 개의 메모리 셀과 중간 결과 y를 보유하는 다른 하나의 메모리 셀이 있어야합니다. 확실히, 컴파일러 백엔드는 이런 방식으로 작동하는 최종 코드를 생성 할 수 있습니다 (어떤 의미에서는 대상 아키텍처가 v. Neumann 시스템 인 한 그렇게해야합니다).

그러나 요점은 위의 기능을 이해하기 위해 v. Neumann 아키텍처를 내재화 할 필요가 없다는 것입니다. 우리는 그것을 운영하기 위해 현대 컴퓨터가 필요하지 않습니다. 예를 들어 순수한 FP 언어로 된 프로그램을 SKI 미적분의 기초에서 작동하는 가상의 기계로 쉽게 변환 할 수 있습니다. 이제 C 프로그램으로 동일하게 시도하십시오!

나를 위해 순수한 선언적 언어는 선언으로 만 구성된 언어 일 것입니다.

이것은 충분히 강하지 않습니다, IMHO. C 프로그램조차도 일련의 선언 일뿐입니다. 선언문을 추가로 검증해야한다고 생각합니다. 예를 들어, 뭔가가 무엇을 우리에게 말하고 있습니다 입니다 (선언) 또는 무엇을 하지 (필수).


나는 함수형 프로그래밍이 C에서의 프로그래밍과 다르지 않다고 말하지 않았다. 실제로 Haskell과 같은 언어는 C보다 훨씬 높은 수준에 있으며 훨씬 더 큰 추상화를 제공한다고 말했다.
ALXGTV
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.