이 답변의 목적을 위해 함수가 참조 적으로 투명한 기능적 언어를 의미하는 "순전히 기능적인 언어"를 정의합니다. 즉, 동일한 인수를 사용하여 동일한 함수를 여러 번 호출하면 항상 동일한 결과가 생성됩니다. 이것은 순수한 기능적 언어의 일반적인 정의입니다.
순수한 기능적 프로그래밍 언어는 부작용을 허용하지 않습니다 (따라서 유용한 프로그램이 외부 세계와 상호 작용할 때 부작용이 있기 때문에 실제로는 거의 사용되지 않습니다).
참조 투명성을 달성하는 가장 쉬운 방법은 실제로 부작용을 허용하지 않는 것이며 실제로 언어가 있습니다 (주로 도메인 특정 언어). 그러나 확실히 유일한 방법은 아니며 가장 보편적 인 순수한 기능 언어 (Haskell, Clean 등)는 부작용을 허용합니다.
또한 부작용이없는 프로그래밍 언어는 실제로 거의 쓸모가 없다고 생각합니다. 도메인 특정 언어는 아니지만 범용 언어조차도 부작용을 제공하지 않고 언어가 매우 유용 할 수 있다고 생각합니다. . 콘솔 응용 프로그램에는 적합하지 않지만 기능적 반응 패러다임과 같은 부작용없이 GUI 응용 프로그램을 훌륭하게 구현할 수 있다고 생각합니다.
포인트 1과 관련하여 순전히 기능적인 언어로 환경과 상호 작용할 수 있지만이를 소개하는 코드 (기능)를 명시 적으로 표시해야합니다 (예 : 모나 딕 유형을 사용하여 Haskell에서).
그것은 약간 단순화되었습니다. 부작용이있는 기능을 C ++의 const-correctness와 유사하지만 일반적인 부작용과 같이 표시해야하는 시스템만으로는 참조 투명성을 확보하기에 충분하지 않습니다. 프로그램이 동일한 인수로 함수를 여러 번 호출 할 수없고 다른 결과를 얻을 수 없도록해야합니다. 당신은 같은 것을 만들어서 그렇게 할 수 있습니다readLine
함수가 아니거나 (하스켈이 IO 모나드로하는 것) 같은 인자로 부수적 인 함수를 여러 번 호출하는 것을 불가능하게 만들 수 있습니다 (Clean이하는 것). 후자의 경우 컴파일러는 부작용 함수를 호출 할 때마다 새로운 인수를 사용하여 동일한 인수를 부작용 함수에 두 번 전달하는 모든 프로그램을 거부합니다.
순수 기능 프로그래밍 언어는 상태를 유지하는 프로그램을 작성할 수 없습니다 (많은 응용 프로그램에서 상태가 필요하기 때문에 프로그래밍이 매우 어색합니다).
다시 말하지만, 순전히 기능적인 언어는 변경 가능한 상태를 매우 잘 허용하지 않을 수 있지만, 위의 부작용으로 설명한 것과 동일한 방식으로 구현하면 순수하고 여전히 변경 가능한 상태를 가질 수 있습니다. 실제로 변경 가능한 상태는 또 다른 형태의 부작용입니다.
즉, 함수형 프로그래밍 언어는 변경 가능한 상태, 특히 순수한 상태를 권장하지 않습니다. 그리고 그것이 프로그래밍을 어색하게 만드는 것이라고 생각하지 않습니다. 때때로 (그러나 종종 그런 것은 아니지만) 변경 가능한 상태는 성능이나 명확성을 잃지 않고 피할 수 없지만 (하스켈과 같은 언어에는 변경 가능한 상태를위한 기능이 있습니다) 대부분의 경우 가능합니다.
그들이 오해라면 어떻게 되었습니까?
많은 사람들이 단순히 "같은 인수로 호출 할 때 함수가 동일한 결과를 가져와야합니다"를 읽고 readLine
가변 상태를 유지 하는 것과 같은 코드 나 코드 를 구현할 수 없다고 결론 내립니다 . 따라서 그들은 순전히 기능적인 언어가 참조 투명성을 손상시키지 않고 이러한 것들을 소개하는 데 사용할 수있는 "속임수"를 알지 못합니다.
또한 변경 가능한 상태는 기능적 언어에서는 크게 실망하지 않기 때문에 순전히 기능적인 언어에서는 전혀 허용되지 않는다고 가정하는 것이 큰 도약은 아닙니다.
(1) 부작용을 구현하고 (2) 상태를 사용하여 계산을 구현하는 Haskell 관용적 방법을 설명하는 (아마도 작은) 코드 스 니펫을 작성할 수 있습니까?
Pseudo-Haskell의 응용 프로그램은 사용자에게 이름을 묻고 인사합니다. Pseudo-Haskell은 방금 발명 한 언어로 Haskell의 IO 시스템을 가지고 있지만보다 일반적인 구문, 더 설명적인 함수 이름을 사용하며 표기법이 없습니다 do
(IO 모나드의 작동 방식을 방해 하지 않기 때문에) :
greet(name) = print("Hello, " ++ name ++ "!")
main = composeMonad(readLine, greet)
단서 여기 즉 readLine
타입의 값 IO<String>
및 composeMonad
유형의 인수를 취하는 함수이다 IO<T>
(몇몇 유형 T
)과 유형의 인수를 취하는 함수 다른 인수 T
입력 값을 반환 IO<U>
(몇몇 유형 U
). print
문자열을 가져 와서 type 값을 반환하는 함수입니다 IO<void>
.
type 값은 type IO<A>
값을 생성하는 지정된 작업을 "인코딩"하는 값입니다 A
. 의 조치를 수행 한 다음 의 조치를 인 코드 composeMonad(m, f)
하는 새 IO
값을 생성합니다 . 여기서 값은의 조치를 수행하여 생성합니다 .m
f(x)
x
m
가변 상태는 다음과 같습니다.
counter = mutableVariable(0)
increaseCounter(cnt) =
setIncreasedValue(oldValue) = setValue(cnt, oldValue + 1)
composeMonad(getValue(cnt), setIncreasedValue)
printCounter(cnt) = composeMonad( getValue(cnt), print )
main = composeVoidMonad( increaseCounter(counter), printCounter(counter) )
다음 mutableVariable
은 모든 유형의 값을 가져와 T
a를 생성하는 함수입니다 MutableVariable<T>
. 이 함수 getValue
는 현재 값을 생성하는를 가져 MutableVariable
오고 반환합니다 IO<T>
. setValue
a MutableVariable<T>
와 a를 가져 와서 값을 설정하는를 T
반환합니다 IO<void>
. 첫 번째 인수가 합리적인 값을 생성하지 않고 두 번째 인수가 모나드를 반환하는 함수가 아닌 다른 모나드 composeVoidMonad
라는 composeMonad
점을 제외하고 는 동일 IO
합니다.
Haskell에는 구문상의 설탕이 있는데,이 전체 시련은 덜 고통 스럽지만, 변하기 쉬운 상태는 언어가 실제로 원하지 않는 것임이 여전히 분명합니다.