답변:
대부분 더 효율적일 수 있기 때문에 값을 사용하지 않을 경우 계산할 필요가 없습니다. 예를 들어 함수에 세 개의 값을 전달할 수 있지만 조건식의 시퀀스에 따라 실제로 하위 집합 만 사용할 수 있습니다. C와 같은 언어에서는 어쨌든 세 가지 값이 모두 계산됩니다. 그러나 Haskell에서는 필요한 값만 계산됩니다.
또한 무한 목록과 같은 멋진 것들을 허용합니다. C와 같은 언어로는 무한 목록을 가질 수 없지만 Haskell에서는 문제가되지 않습니다. 무한 목록은 수학의 특정 영역에서 상당히 자주 사용되므로이를 조작하는 능력이 있으면 유용 할 수 있습니다.
지연 평가의 유용한 예는 다음을 사용하는 것입니다 quickSort
.
quickSort [] = []
quickSort (x:xs) = quickSort (filter (< x) xs) ++ [x] ++ quickSort (filter (>= x) xs)
이제 목록의 최소값을 찾으려면 다음을 정의 할 수 있습니다.
minimum ls = head (quickSort ls)
먼저 목록을 정렬 한 다음 목록의 첫 번째 요소를 가져옵니다. 그러나 게으른 평가 때문에 머리 만 계산됩니다. 예를 들어 목록의 최소값을 취하면[2, 1, 3,]
quickSort는 먼저 2보다 작은 모든 요소를 필터링합니다. 그런 다음 이미 충분한 QuickSort를 수행합니다 (단일 목록 [1] 반환). 지연 평가로 인해 나머지는 정렬되지 않으므로 계산 시간이 많이 절약됩니다.
이것은 물론 매우 간단한 예이지만 게으름은 매우 큰 프로그램에 대해 동일한 방식으로 작동합니다.
그러나이 모든 것에는 단점이 있습니다. 프로그램의 런타임 속도와 메모리 사용량을 예측하기가 더 어려워집니다. 이것은 게으른 프로그램이 느리거나 더 많은 메모리를 차지한다는 것을 의미하지는 않지만 알아두면 좋습니다.
take k $ quicksort list
O (n + k log k) 시간 만 걸립니다 n = length list
. 지연되지 않은 비교 정렬을 사용하면 항상 O (n log n) 시간이 걸립니다.
게으른 평가는 여러 가지에 유용합니다.
첫째, 기존의 모든 게으른 언어는 순수합니다. 게으른 언어에서는 부작용에 대해 추론하기가 매우 어렵 기 때문입니다.
순수 언어를 사용하면 방정식 추론을 사용하여 함수 정의에 대해 추론 할 수 있습니다.
foo x = x + 3
안타깝게도 지연이 아닌 설정에서는 지연 설정보다 더 많은 문이 반환되지 않으므로 ML과 같은 언어에서는 유용하지 않습니다. 그러나 게으른 언어에서는 평등에 대해 안전하게 추론 할 수 있습니다.
둘째, 하스켈과 같은 게으른 언어에서는 ML의 '값 제한'과 같은 많은 것들이 필요하지 않습니다. 이로 인해 구문이 크게 정리됩니다. 언어와 같은 ML은 var 또는 fun과 같은 키워드를 사용해야합니다. Haskell에서 이러한 것들은 하나의 개념으로 축소됩니다.
셋째, 게으름을 사용하면 조각으로 이해할 수있는 매우 기능적인 코드를 작성할 수 있습니다. Haskell에서는 다음과 같은 함수 본문을 작성하는 것이 일반적입니다.
foo x y = if condition1
then some (complicated set of combinators) (involving bigscaryexpression)
else if condition2
then bigscaryexpression
else Nothing
where some x y = ...
bigscaryexpression = ...
condition1 = ...
condition2 = ...
이렇게하면 함수 본문을 이해하면서 '하향식'으로 작업 할 수 있습니다. ML과 유사한 언어 let
는 엄격하게 평가되는를 사용하도록 강요합니다 . 결과적으로 값이 비싸거나 부작용이있는 경우 항상 평가되는 것을 원하지 않기 때문에 let 절을 함수의 본문으로 '리프트'할 수 없습니다. Haskell은 해당 절의 내용이 필요한 경우에만 평가된다는 것을 알고 있으므로 where 절에 대한 세부 정보를 명시 적으로 '푸시'할 수 있습니다.
실제로 우리는 가드를 사용하는 경향이 있으며 다음을 위해 더 축소합니다.
foo x y
| condition1 = some (complicated set of combinators) (involving bigscaryexpression)
| condition2 = bigscaryexpression
| otherwise = Nothing
where some x y = ...
bigscaryexpression = ...
condition1 = ...
condition2 = ...
넷째, 게으름은 때때로 특정 알고리즘의 훨씬 더 우아한 표현을 제공합니다. Haskell의 게으른 '빠른 정렬'은 한 줄짜리이며 처음 몇 개의 항목 만 보면 해당 항목 만 선택하는 비용에 비례하는 비용 만 지불한다는 이점이 있습니다. 이 작업을 엄격하게 방해하는 것은 없지만 동일한 점근 적 성능을 얻으려면 매번 알고리즘을 다시 코딩해야합니다.
다섯째, 게으름을 통해 언어로 새로운 제어 구조를 정의 할 수 있습니다. 엄격한 언어로 구문과 같이 새로운 'if .. then .. else ..'를 작성할 수 없습니다. 다음과 같은 함수를 정의하려고하면 :
if' True x y = x
if' False x y = y
엄격한 언어에서는 조건 값에 관계없이 두 분기가 모두 평가됩니다. 루프를 고려하면 더 나빠집니다. 모든 엄격한 솔루션에는 일종의 인용문 또는 명시 적 람다 구성을 제공하는 언어가 필요합니다.
마지막으로, 같은 맥락에서 모나드와 같은 유형 시스템의 부작용을 처리하는 가장 좋은 메커니즘 중 일부는 실제로 게으른 설정에서만 효과적으로 표현할 수 있습니다. 이것은 F #의 워크 플로의 복잡성을 Haskell Monads와 비교하여 확인할 수 있습니다. (엄격한 언어로 모나드를 정의 할 수 있지만, 안타깝게도 게으름과 워크 플로 부족으로 인해 모나드 법칙을 한두 번 실패하는 경우가 많습니다.
let
는 위험한 짐승입니다. R6RS 체계 #f
에서는 매듭을 묶는 것이 엄격하게 순환으로 이어지는 모든 용어에 무작위 가 나타날 수 있습니다 ! 의도 된 말장난은 아니지만 let
, 게으른 언어 에서는 엄격히 더 재귀적인 바인딩이 합리적입니다. 엄격함은 또한 where
SCC를 제외하고는 상대적 효과를 주문할 방법이 전혀 없다는 사실을 악화시킵니다. SCC에 의한 경우를 제외하고는 문 수준의 구성이며 그 효과는 엄격하게 어떤 순서로든 발생할 수 있으며 순수한 언어를 사용하더라도 #f
발행물. 엄격한 where
로컬 문제로 코드를 수수께끼로 만듭니다.
ifFunc(True, x, y)
둘을 평가하는 것입니다 x
그리고 y
대신의 x
.
일반 주문 평가와 지연 평가에는 차이가 있습니다 (하스켈에서와 같이).
square x = x * x
다음 식을 평가하는 중 ...
square (square (square 2))
... 열심히 평가 :
> square (square (2 * 2))
> square (square 4)
> square (4 * 4)
> square 16
> 16 * 16
> 256
... 일반 주문 평가 :
> (square (square 2)) * (square (square 2))
> ((square 2) * (square 2)) * (square (square 2))
> ((2 * 2) * (square 2)) * (square (square 2))
> (4 * (square 2)) * (square (square 2))
> (4 * (2 * 2)) * (square (square 2))
> (4 * 4) * (square (square 2))
> 16 * (square (square 2))
> ...
> 256
... 게으른 평가 :
> (square (square 2)) * (square (square 2))
> ((square 2) * (square 2)) * ((square 2) * (square 2))
> ((2 * 2) * (2 * 2)) * ((2 * 2) * (2 * 2))
> (4 * 4) * (4 * 4)
> 16 * 16
> 256
지연 평가는 구문 트리를보고 트리 변환을 수행하기 때문입니다.
square (square (square 2))
||
\/
*
/ \
\ /
square (square 2)
||
\/
*
/ \
\ /
*
/ \
\ /
square 2
||
\/
*
/ \
\ /
*
/ \
\ /
*
/ \
\ /
2
... 일반 주문 평가는 텍스트 확장 만 수행합니다.
이것이 우리가 지연 평가를 사용할 때 더 강력 해지면서 (평가가 다른 전략보다 더 자주 종료 됨) 성능이 열망 평가 (적어도 O 표기법에서)와 동일한 이유입니다.
RAM 관련 가비지 콜렉션과 동일한 방식으로 CPU 관련 지연 평가. GC를 사용하면 메모리가 무제한 인 것처럼 가장하여 필요한만큼 메모리에있는 개체를 요청할 수 있습니다. 런타임은 사용할 수없는 개체를 자동으로 회수합니다. LE를 사용하면 계산 리소스가 무제한 인 척 할 수 있습니다. 필요한만큼 계산을 수행 할 수 있습니다. 런타임은 불필요한 (주어진 경우) 계산을 실행하지 않습니다.
이러한 "가장"모델의 실질적인 이점은 무엇입니까? 개발자가 리소스 관리에서 어느 정도까지는 해제하고 소스에서 일부 상용구 코드를 제거합니다. 그러나 더 중요한 것은보다 광범위한 컨텍스트에서 솔루션을 효율적으로 재사용 할 수 있다는 것입니다.
숫자 S와 숫자 N의 목록이 있다고 가정합니다. 목록 S에서 숫자 N에 가장 가까운 숫자 M을 찾아야합니다. 단일 N과 N의 일부 목록 L (L의 각 N에 대해 ei)의 두 가지 컨텍스트를 가질 수 있습니다. S)에서 가장 가까운 M을 찾습니다. 지연 평가를 사용하는 경우 S를 정렬하고 이진 검색을 적용하여 M에서 N에 가장 가까운 M을 찾을 수 있습니다. 좋은 지연 정렬을 위해서는 단일 N 및 O (ln (size (S)) *에 대해 O (size (S)) 단계가 필요합니다. (size (S) + size (L))) 균등 분포 L에 대한 단계. 최적의 효율성을 달성하기위한 지연 평가가없는 경우 각 컨텍스트에 대한 알고리즘을 구현해야합니다.
Simon Peyton Jones를 믿는다면 게으른 평가는 그 자체로 중요하지 않고 디자이너가 언어를 순수하게 유지하도록 강요하는 '헤어 셔츠'로서 만 중요 합니다. 나는이 관점에 공감한다.
Richard Bird, John Hughes 및 Ralf Hinze는 지연 평가로 놀라운 일을 할 수 있습니다. 그들의 작품을 읽으면 감사하는 데 도움이 될 것입니다. 좋은 출발점은 Bird의 웅장한 스도쿠 솔버와 Why Functional Programming Matters 에 대한 Hughes의 논문입니다 .
IO
의 서명이 모나드) main
이 될 것입니다 String -> String
그리고 당신은 이미 제대로 대화 형 프로그램을 작성할 수 있습니다.
IO
모나드 로 강제하는 것을 막는 것은 무엇입니까 ?
tic-tac-toe 프로그램을 고려하십시오. 여기에는 네 가지 기능이 있습니다.
이것은 관심사를 명확하게 분리합니다. 특히 이동 생성 기능과 보드 평가 기능은 게임의 규칙을 이해하는 데 필요한 유일한 기능입니다. 이동 트리와 미니 맥스 기능은 완전히 재사용 할 수 있습니다.
이제 tic-tac-toe 대신 체스를 구현해 보겠습니다. "eager"(즉, 전통적인) 언어에서는 이동 트리가 메모리에 맞지 않기 때문에 작동하지 않습니다. 따라서 이제 보드 평가 및 이동 생성 기능을 이동 트리 및 미니 맥스 논리와 혼합해야합니다. 생성 할 이동을 결정하는 데 minimax 논리를 사용해야하기 때문입니다. 우리의 멋진 모듈 식 구조가 사라졌습니다.
그러나 게으른 언어에서 이동 트리의 요소는 minimax 함수의 요구에 응답해서 만 생성됩니다. 전체 이동 트리를 생성 할 필요는 없습니다. 그래서 우리의 깨끗한 모듈 구조는 여전히 실제 게임에서 작동합니다.
여기에 아직 논의에서 제기되지 않은 두 가지 사항이 더 있습니다.
게으름은 동시 환경의 동기화 메커니즘입니다. 일부 계산에 대한 참조를 만들고 그 결과를 여러 스레드간에 공유 할 수있는 가볍고 쉬운 방법입니다. 여러 스레드가 평가되지 않은 값에 액세스하려고하면 그중 하나만 실행하고 나머지 스레드는 그에 따라 차단하여 값이 사용 가능 해지면 수신합니다.
게으름은 순수한 환경에서 데이터 구조를 분할하는 데 기본이됩니다. 이것은 Okasaki가 Purely Functional Data Structures 에서 자세히 설명하지만, 기본 아이디어는 lazy 평가가 특정 유형의 데이터 구조를 효율적으로 구현하는 데 중요한 제어 된 형태의 돌연변이라는 것입니다. 우리가 순결한 헤어 셔츠를 입도록 강요하는 게으름에 대해 자주 이야기하지만, 다른 방법도 적용됩니다. 시너지 효과가있는 한 쌍의 언어 기능입니다.
컴퓨터를 켜고 Windows가 Windows 탐색기에서 하드 드라이브의 모든 단일 디렉토리를 열지 않고 특정 디렉토리가 필요하거나 특정 프로그램이 필요하다는 것을 표시 할 때까지 컴퓨터에 설치된 모든 단일 프로그램을 실행하지 않는 경우, "게으른"평가입니다.
"지연"평가는 필요할 때 작업을 수행합니다. 프로그래밍 언어 또는 라이브러리의 기능 일 때 유용합니다. 일반적으로 모든 것을 미리 계산하는 것보다 직접 지연 평가를 구현하는 것이 더 어렵 기 때문입니다.
이걸 고려하세요:
if (conditionOne && conditionTwo) {
doSomething();
}
doSomething () 메서드는 conditionOne이 true 이고 conditionTwo가 true 인 경우에만 실행됩니다 . conditionOne이 거짓 인 경우 왜 conditionTwo의 결과를 계산해야합니까? 이 경우 conditionTwo의 평가는 시간 낭비입니다. 특히 조건이 일부 메서드 프로세스의 결과 인 경우 더욱 그렇습니다.
이것이 게으른 평가 관심의 한 예입니다 ...
효율성을 높일 수 있습니다. 이것은 명백해 보이지만 실제로 가장 중요한 것은 아닙니다. (참고도 게으름 수 죽일 너무 효율이 -이 사실을 즉시 명확하지 않다 그러나, 일시적인 결과를 많이를 저장하는 것이 아니라 즉시를 계산하여, 당신은 RAM의 엄청난 금액을 사용할 수 있습니다..)
이를 통해 언어로 하드 코딩되지 않고 일반 사용자 수준 코드에서 흐름 제어 구문을 정의 할 수 있습니다. (예 : Java에는 for
루프가 있고, Haskell에는 for
함수가 있습니다. Java에는 예외 처리 기능이 있으며, Haskell에는 다양한 유형의 예외 모나드가 있습니다. C #에는 있고 goto
, Haskell에는 연속 모나드가 있습니다 ...)
생성 할 데이터의 양 을 결정 하는 알고리즘에서 데이터 생성 알고리즘을 분리 할 수 있습니다 . 개념적으로 무한한 결과 목록을 생성하는 함수 하나와 필요한만큼이 목록을 처리하는 다른 함수를 작성할 수 있습니다. 요컨대, 5 개의 생성기 함수와 5 개의 소비자 함수를 가질 수 있으며 한 번에 두 작업을 결합하는 5 x 5 = 25 함수를 수동으로 코딩하는 대신 모든 조합을 효율적으로 생성 할 수 있습니다. (!) 우리 모두는 디커플링이 좋은 것임을 압니다.
다소간 순수한 기능적 언어 를 디자인하도록 강요합니다 . 항상 지름길을 사용하는 것은 유혹적이지만 게으른 언어에서는 약간의 불순물이 코드를 만듭니다. 격렬 바로 가기를 복용에 대한 어떤 강하게을 방해 할, 예측할 수없는.
게으름의 큰 이점 중 하나는 합리적인 상각 된 경계로 변경 불가능한 데이터 구조를 작성할 수 있다는 것입니다. 간단한 예는 변경 불가능한 스택 (F # 사용)입니다.
type 'a stack =
| EmptyStack
| StackNode of 'a * 'a stack
let rec append x y =
match x with
| EmptyStack -> y
| StackNode(hd, tl) -> StackNode(hd, append tl y)
코드는 합리적이지만 두 개의 스택 x와 y를 추가하면 최상의 경우, 최악의 경우 및 평균적인 경우 O (x 길이) 시간이 걸립니다. 두 개의 스택을 추가하는 것은 모 놀리 식 작업이며 스택 x의 모든 노드에 연결됩니다.
데이터 구조를 지연 스택으로 다시 작성할 수 있습니다.
type 'a lazyStack =
| StackNode of Lazy<'a * 'a lazyStack>
| EmptyStack
let rec append x y =
match x with
| StackNode(item) -> Node(lazy(let hd, tl = item.Force(); hd, append tl y))
| Empty -> y
lazy
생성자에서 코드 평가를 일시 중단하여 작동합니다. 를 사용하여 평가되면 .Force()
반환 값이 캐시되고 이후에 다시 사용됩니다..Force()
.
lazy 버전에서 추가는 O (1) 작업입니다. 1 개의 노드를 반환하고 목록의 실제 재 구축을 일시 중단합니다. 이 목록의 헤드를 얻으면 노드의 내용을 평가하여 헤드를 강제로 반환하고 나머지 요소로 하나의 서스펜션을 생성하므로 목록의 헤드를 가져 오는 것은 O (1) 작업입니다.
따라서 우리의 게으른 목록은 지속적으로 재 구축 상태에 있으며 모든 요소를 탐색 할 때까지이 목록을 재 구축하는 데 드는 비용을 지불하지 않습니다. laziness를 사용하여이 목록은 O (1) consing 및 appending을 지원합니다. 흥미롭게도 노드에 액세스 할 때까지 노드를 평가하지 않기 때문에 잠재적으로 무한한 요소로 목록을 구성 할 수 있습니다.
위의 데이터 구조는 각 순회에서 노드를 다시 계산할 필요가 없으므로 .NET의 바닐라 IEnumerables와 뚜렷하게 다릅니다.
이 스 니펫은 lazy 평가와 not lazy 평가의 차이점을 보여줍니다. 물론이 피보나치 함수는 자체적으로 최적화 될 수 있고 재귀 대신 지연 평가를 사용할 수 있지만 이는 예제를 망칠 것입니다.
하자 우리가 가정 월 필요한 경우에만 게으른 평가와 함께 그들이 생성됩니다, 모든 20 개 숫자가 선행 생성 할 필요가 없습니다 게으른 평가와 함께, 뭔가를 20 개 첫 번째 번호를 사용해야하지만. 따라서 필요한 경우 계산 가격 만 지불합니다.
샘플 출력
지연 생성이 아님 : 0.023373 지연 생성 : 0.000009 지연 출력이 아님 : 0.000921 지연 출력 : 0.024205
import time
def now(): return time.time()
def fibonacci(n): #Recursion for fibonacci (not-lazy)
if n < 2:
return n
else:
return fibonacci(n-1)+fibonacci(n-2)
before1 = now()
notlazy = [fibonacci(x) for x in range(20)]
after1 = now()
before2 = now()
lazy = (fibonacci(x) for x in range(20))
after2 = now()
before3 = now()
for i in notlazy:
print i
after3 = now()
before4 = now()
for i in lazy:
print i
after4 = now()
print "Not lazy generation: %f" % (after1-before1)
print "Lazy generation: %f" % (after2-before2)
print "Not lazy output: %f" % (after3-before3)
print "Lazy output: %f" % (after4-before4)
지연 평가는 데이터 구조에서 가장 유용합니다. 구조의 특정 점만 지정하고 전체 배열의 관점에서 다른 모든 점을 표현하는 방식으로 배열 또는 벡터를 정의 할 수 있습니다. 이를 통해 매우 간결하고 높은 런타임 성능으로 데이터 구조를 생성 할 수 있습니다.
이것이 실제로 작동하는지 확인하려면 instinct 라는 신경망 라이브러리를 살펴볼 수 있습니다 . 우아함과 고성능을 위해 게으른 평가를 많이 사용합니다. 예를 들어 저는 전통적으로 명령적인 활성화 계산을 완전히 제거했습니다. 단순한 게으른 표현이 나를 위해 모든 것을 수행합니다.
이것은 예를 들어 활성화 함수 와 역 전파 학습 알고리즘에서 사용됩니다 (2 개의 링크 만 게시 할 수 있으므로 모듈 에서 learnPat
함수를 AI.Instinct.Train.Delta
직접 찾아봐야합니다 ). 전통적으로 둘 다 훨씬 더 복잡한 반복 알고리즘이 필요합니다.
다른 사람들은 이미 모든 큰 이유를 제시했지만 게으름이 중요한 이유를 이해하는 데 도움이되는 유용한 연습 은 엄격한 언어로 고정 소수점 함수를 작성하는 것입니다.
Haskell에서 고정 소수점 함수는 매우 쉽습니다.
fix f = f (fix f)
이것은 확장
f (f (f ....
그러나 Haskell은 게으 르기 때문에 무한한 계산 체인은 문제가되지 않습니다. 평가는 "외부에서 내부로"수행되며 모든 것이 훌륭하게 작동합니다.
fact = fix $ \f n -> if n == 0 then 1 else n * f (n-1)
중요한 fix
것은 게 으르는 것이 아니라 게 으르는 것이 중요 f
합니다. 이미 엄격한을 받으면 f
손을 공중에 던지고 포기하거나 eta 확장하여 물건을 정리할 수 있습니다. (이것은 언어가 아니라 엄격 / 게으른 라이브러리 에 대해 Noah가 말한 것과 매우 유사 합니다).
이제 엄격한 Scala에서 동일한 함수를 작성한다고 상상해보십시오.
def fix[A](f: A => A): A = f(fix(f))
val fact = fix[Int=>Int] { f => n =>
if (n == 0) 1
else n*f(n-1)
}
물론 스택 오버플로가 발생합니다. 작동하게하려면 필요에 따라 f
인수 를 만들어야 합니다.
def fix[A](f: (=>A) => A): A = f(fix(f))
def fact1(f: =>Int=>Int) = (n: Int) =>
if (n == 0) 1
else n*f(n-1)
val fact = fix(fact1)
나는 당신이 현재 어떻게 생각하는지 모르겠지만, 게으른 평가를 언어 기능보다는 도서관 문제로 생각하는 것이 유용하다고 생각합니다.
엄격한 언어에서는 몇 가지 데이터 구조를 구축하여 게으른 평가를 구현할 수 있으며 게으른 언어 (적어도 Haskell)에서는 원할 때 엄격함을 요청할 수 있습니다. 따라서 언어 선택은 실제로 프로그램을 지연 또는 비 지연으로 만드는 것이 아니라 기본적으로 얻는 프로그램에 영향을줍니다.
그렇게 생각하면 나중에 데이터를 생성하는 데 사용할 수있는 데이터 구조를 작성하는 모든 위치를 생각하면 (그 전에 너무 많이 보지 않고) lazy에 대한 많은 용도를 볼 수 있습니다. 평가.
내가 사용한 게으른 평가의 가장 유용한 활용은 일련의 하위 기능을 특정 순서로 호출하는 함수였습니다. 이러한 하위 함수 중 하나가 실패하면 (거짓 반환) 호출하는 함수는 즉시 반환해야합니다. 그래서 나는 이렇게 할 수 있었다.
bool Function(void) {
if (!SubFunction1())
return false;
if (!SubFunction2())
return false;
if (!SubFunction3())
return false;
(etc)
return true;
}
또는 더 우아한 솔루션 :
bool Function(void) {
if (!SubFunction1() || !SubFunction2() || !SubFunction3() || (etc) )
return false;
return true;
}
사용을 시작하면 점점 더 자주 사용할 수있는 기회를 보게 될 것입니다.
게으른 평가가 없으면 다음과 같이 작성할 수 없습니다.
if( obj != null && obj.Value == correctValue )
{
// do smth
}
무엇보다도 게으른 언어는 다차원 무한 데이터 구조를 허용합니다.
scheme, python 등은 스트림이있는 1 차원 무한 데이터 구조를 허용하지만 한 차원 만 따라 이동할 수 있습니다.
게으름은 동일한 프린지 문제에 유용 하지만 해당 링크에 언급 된 코 루틴 연결에 주목할 가치가 있습니다.
게으른 평가는 가난한 사람의 방정식 추론입니다 (이상적으로는 관련된 유형 및 작업의 속성에서 코드의 속성을 추론하는 것으로 예상 할 수 있음).
잘 작동하는 예 : sum . take 10 $ [1..10000000000]
. 하나의 직접적이고 간단한 숫자 계산 대신에 10 개의 숫자의 합계로 줄여도 괜찮습니다. 당연히 게으른 평가가 없으면 처음 10 개의 요소를 사용하기 위해 메모리에 거대한 목록이 생성됩니다. 확실히 매우 느리고 메모리 부족 오류가 발생할 수 있습니다.
우리가 원하는만큼 좋지 않은 예 : sum . take 1000000 . drop 500 $ cycle [1..20]
. 목록이 아닌 루프에 있더라도 실제로 1000000 개의 숫자를 합산합니다. 여전히이 해야 몇 조건문과 몇 가지 수식, 하나의 직접 수치 계산을 줄일 수. 1000000 숫자를 합산하는 것 보다 훨씬 낫습니다. 루프에 있고 목록에없는 경우에도 (즉, 삼림 벌채 최적화 이후).
또 다른 점은 코드에 대한 것이 가능하게된다 꼬리 재귀 모듈로 죄수의 스타일, 그리고 그것은 단지 작동합니다 .
cf. 관련 답변 .
"지연 평가"가 다음과 같이 결합 된 부울에서와 같이 의미하는 경우
if (ConditionA && ConditionB) ...
답은 단순히 프로그램이 소비하는 CPU주기가 적을수록 더 빨리 실행된다는 것입니다. 시간의) 어쨌든 그들을 수행하기 위해 ...
만약 otoh, 당신은 내가 "게으른 이니셜 라이저"로 알려진 것을 의미합니다.
class Employee
{
private int supervisorId;
private Employee supervisor;
public Employee(int employeeId)
{
// code to call database and fetch employee record, and
// populate all private data fields, EXCEPT supervisor
}
public Employee Supervisor
{
get
{
return supervisor?? (supervisor = new Employee(supervisorId));
}
}
}
음,이 기술은 Employee 개체를 사용하는 클라이언트가 감독자의 데이터에 액세스해야하는 경우를 제외하고는 클래스를 사용하는 클라이언트 코드를 통해 Supervisor 데이터 레코드에 대한 데이터베이스를 호출 할 필요가 없도록합니다. 이렇게하면 Employee를 인스턴스화하는 프로세스가 더 빨라집니다. 그러나 Supervisor가 필요할 때 Supervisor 속성에 대한 첫 번째 호출은 Database 호출을 트리거하고 데이터를 가져와 사용할 수 있습니다.
고차 함수 에서 발췌
3829로 나눌 수있는 100,000 미만의 가장 큰 숫자를 찾아 보겠습니다.이를 위해 우리는 해결책이 있다는 것을 알고있는 가능성 집합을 필터링합니다.
largestDivisible :: (Integral a) => a
largestDivisible = head (filter p [100000,99999..])
where p x = x `mod` 3829 == 0
먼저 100,000 미만의 모든 숫자 목록을 내림차순으로 만듭니다. 그런 다음 술어로 필터링하고 숫자가 내림차순으로 정렬되기 때문에 술어를 충족하는 가장 큰 숫자가 필터링 된 목록의 첫 번째 요소입니다. 시작 세트에 유한 목록을 사용할 필요조차 없었습니다. 그것은 다시 행동의 게으름입니다. 필터링 된 목록의 헤드 만 사용하기 때문에 필터링 된 목록이 유한인지 무한인지는 중요하지 않습니다. 첫 번째 적절한 솔루션을 찾으면 평가가 중지됩니다.