메모 화 된 순수한 함수 자체가 순수한 것으로 간주됩니까?


47

fn(x)의 주요 요소 목록을 반환하는 것과 같이 값 비싼 것을 수행하는 순수한 함수 라고 가정 해 봅시다 x.

그리고라는 동일한 함수의 메모 버전을 만든다고 가정 해 봅시다 memoizedFn(x). 주어진 입력에 대해 항상 동일한 결과를 반환하지만 성능을 향상시키기 위해 이전 결과의 개인 캐시를 유지합니다.

공식적으로 말하면 memoizedFn(x)순수한 것으로 간주됩니까?

아니면 FP 토론에서 그러한 기능을 나타내는 데 사용되는 다른 이름이나 적격 한 용어가 있습니까? (즉, 후속 호출의 계산 복잡성에는 영향을 줄 수 있지만 반환 값에는 영향을 미치지 않는 부작용이있는 함수)


24
아마 순수 주의자들에게는 순수하지는 않지만 실용적 사람들에게는 "충분히 순수하다";-)
Doc Brown

2
@DocBrown 난 그냥 '순수 충분히'에 대한 더 공식적인 용어가 있는지 궁금 동의
캘럼이

13
순수한 함수를 실행하면 프로세서의 명령어 캐시, 분기 예측기 등이 수정 될 가능성이 높습니다. 그러나 순수 주의자에게는 "충분히 순수한"것일 수도 있습니다. 그렇지 않으면 순수한 함수를 잊어 버릴 수도 있습니다.
gnasher729

10
@callum 아니오, "순수"에 대한 공식적인 정의는 없습니다. 순도 및 두 개의 "참조 적으로 투명한"호출의 의미 론적 동등성에 대해 논쟁 할 때는 항상 적용 할 의미론을 정확하게 명시해야합니다. 낮은 수준의 구현 세부 사항에서는 항상 세분화되고 다른 메모리 효과 또는 타이밍을 갖습니다. 그렇기 때문에 실용적이되어야합니다. 코드를 추론하는 데 어느 정도의 세부 정보가 유용한가요?
Bergi

3
그런 다음 실용주의를 위해 순도는 계산 시간을 출력의 일부로 간주할지 여부에 따라 달라집니다. funcx(){sleep(cached_time--); return 0;}매번 같은 val을 반환하지만 다르게 수행됩니다
Mars

답변:


41

예. 순수한 함수의 메모리 화 된 버전도 순수한 함수입니다.

함수 순도가 중요하게 생각하는 것은 함수의 반환 값에 대한 입력 매개 변수 (동일한 입력을 전달하면 항상 동일한 출력을 생성해야 함) 및 전역 상태와 관련된 모든 부작용 (예 : 텍스트를 터미널 또는 UI 또는 네트워크로 보내는 텍스트)입니다. . 계산 시간 및 추가 메모리 사용량은 기능 순도와 관련이 없습니다.

순수한 함수의 캐시는 프로그램에 거의 보이지 않습니다. 함수형 프로그래밍 언어는 순수 함수를 메모리 화 된 버전의 함수에 자동으로 최적화하여 유용하다고 판단 될 수 있습니다. 실제로 메모가 유익한시기를 자동으로 결정하는 것은 실제로 매우 어려운 문제이지만 이러한 최적화는 유효합니다.


19

Wikipedia는 "순수 함수" 를 다음 속성을 갖는 함수로 정의 합니다.

  • 반환 값이 동일한 동일 인수 (로컬 정적 변수, 비 로컬 변수, 가변 기준 입력 인자 또는 아무 변화가 I / O 장치로부터 스트림 없음).

  • 평가 에는 부작용이 없습니다 (로컬 정적 변수, 로컬이 아닌 변수, 변경 가능한 참조 인수 또는 I / O 스트림의 돌연변이 없음).

실제로 순수한 함수는 동일한 입력이 주어지면 동일한 출력을 반환하며 함수 외부의 다른 것에 영향을 미치지 않습니다. 순도를 위해, 동일한 입력이 주어지면 동일한 출력을 반환하는 한 함수가 반환 값을 계산하는 방법은 중요하지 않습니다.

Haskell과 같은 기능적으로 순수한 언어는 일상적으로 메모사용 하여 이전에 계산 된 결과를 캐싱하여 함수의 속도를 높입니다.


16
뭔가를 놓칠 수도 있지만 부작용없이 캐시를 어떻게 유지합니까?

1
함수 내부에 유지함으로써.
Robert Harvey

4
"로컬 정적 변수의 돌연변이 없음"은 호출간에 지속되는 로컬 변수도 제외하는 것으로 보입니다.

3
당신이 그렇다는 것을 암시하는 것처럼 보일지라도, 이것은 실제로 질문에 대답하지 않습니다.
화성

6
@val 당신은 맞습니다 :이 상태는 약간 완화되어야합니다. 그가 언급 한 순전히 기능적인 메모는 정적 데이터의 가시적 인 돌연변이 가 없습니다 . 결과는 함수가 처음 호출 될 때 결과가 계산되고 메모되고 호출 될 때마다 동일한 값을 반환한다는 것입니다. static constC ++ 의 로컬 변수 (C는 아님) 또는 Haskell의 느리게 평가 된 데이터 구조 가 많은 언어에 관용구가 있습니다. 필요한 조건이 하나 더 있습니다. 초기화는 스레드로부터 안전해야합니다.
Davislor

7

그렇습니다. 메모 화 된 순수 함수는 일반적으로 순수라고합니다. 이것은 기억, 지연 평가, 불변의 결과가 내장 된 Haskell과 같은 언어에서 특히 일반적입니다.

한 가지 중요한주의 사항이 있습니다. 메모 기능은 스레드로부터 안전해야합니다. 그렇지 않으면 두 스레드가 호출하려고 할 때 경쟁 조건이 발생할 수 있습니다.

이런 방식으로“순전히 기능적”이라는 용어를 사용하는 컴퓨터 과학자의 한 예는 자동 메모에 대한 Conal Elliott의 블로그 게시물입니다 .

놀랍게도, 메모 작성은 게으른 기능적 언어로 간단하고 순전히 기능적으로 구현 될 수 있습니다.

동료 검토 문헌에는 많은 예가 있으며 수십 년 동안 사용되었습니다. 예를 들어 1995 년 “실제 AI 시스템에서 소프트웨어 엔지니어링 도구로 자동 메모 사용”이라는 이 백서는 5.2 절에서 매우 유사한 언어를 사용하여 오늘날 우리가 순수한 기능이라고 부르는 것을 설명합니다.

메모는 절차가 아닌 실제 기능에 대해서만 작동합니다. 즉, 함수의 결과가 입력 매개 변수에 의해 완전하고 결정 론적으로 지정되지 않은 경우 메모를 사용하면 잘못된 결과가 나타납니다. 시스템 전체에서 기능적 프로그래밍 스타일의 사용을 장려함으로써 성공적으로 메모 할 수있는 기능의 수가 증가합니다.

일부 명령형 언어에는 비슷한 관용구가 있습니다. 예를 들어, static constC ++ 의 변수는 값이 사용되기 전에 한 번만 초기화되며 절대 변경되지 않습니다.


3

그것은 당신이 어떻게하는지에 달려 있습니다.

일반적으로 사람들은 일종의 캐시 사전을 변경하여 메모하고 싶어합니다. 이것은 동시성에 대한 걱정, 캐시가 너무 커지는 것에 대한 걱정 등 불순한 돌연변이와 관련된 모든 문제가 있습니다.

그러나 불완전한 메모리 돌연변이없이 메모 할 수 있습니다. 하나 의 예가이 답변 에 있는데, 여기서 lengths인수를 통해 메모 된 값을 외부에서 추적합니다 .

에서 로버트 하비 제공된 링크 , 게으른 평가는 부작용을 방지하기 위해 사용된다.

IOcats-effect의 memoize 함수 와 같은 유형 의 컨텍스트에서 메모를 명시 적으로 부작용으로 표시하는 경우가 있습니다 .

이 마지막 목표는 때로는 목표가 돌연변이를 제거하는 것이 아니라 캡슐화하는 것입니다. 대부분의 기능 프로그래머는 불순물을 명시적이고 캡슐화하기에 "충분히 순수"하다고 생각합니다.

용어를 진정으로 순수한 함수와 구별하기를 원한다면 "변경 가능한 사전으로 기억된다"고 말하는 것으로 충분하다고 생각합니다. 이를 통해 사람들이 안전하게 사용하는 방법을 알 수 있습니다.


나는 순수한 솔루션 중 하나가 위의 문제를 해결 생각하지 않는다 : 당신이 어떤 동시성 걱정을 잃게하는 동안, 당신은 또한 같은이 개 동시에 시작 통화에 대해 어떤 기회를 상실 collatz(100)하고 collatz(200)협력 할 수 있습니다. 캐시가 너무 커지는 문제인 IIUIC는 여전히 남아 있습니다.
maaartinus

참고 : IO순수합니다. 에 대한 모든 불완전한 방법 IO과 고양이는 이름이 지정 unsafe됩니다. Async.memoize또한 순수한 것이므로 우리는 "충분히 순수"하기 위해 정착 할 필요가 없습니다 :)
사무엘

2

일반적으로, 목록을 반환하는 함수는 스토리지 할당이 필요하고 실패 할 수 있기 때문에 전혀 순수하지 않습니다 (예 : 순수하지 않은 예외를 throw하여). 값 형식이 있고 목록을 경계 크기 값 형식으로 나타낼 수있는 언어에는이 문제가 없을 수 있습니다. 이런 이유로, 당신의 모범은 아마도 순수하지 않을 것입니다.

일반적으로 memoization이 실패 할 경우없는 방식으로 수행 될 수있는 경우 (예 : memoized 결과를 위해 저장소를 정적으로 할당하고, 언어가 스레드를 허용하는 경우 이에 대한 액세스를 제어하기위한 내부 동기화) 이러한 기능을 고려하는 것이 합리적입니다. 순수한.


0

state monad를 사용하여 부작용없이 메모를 구현할 수 있습니다 .

[상태 모나드]는 기본적으로 함수 S => (S, A)입니다. 여기서 S는 상태를 나타내는 유형이고 A는 함수가 생성하는 결과 – Cats State 입니다.

귀하의 경우 상태는 메모 된 값이거나 아무것도 아닙니다 (예 : Haskell Maybe또는 Scala Option[A]). 메모 된 값이 있으면로 반환되고 A그렇지 않으면 A계산 된 상태와 결과로 계산됩니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.