문제

나는 게으른 평가가 왜 유용한지 오랫동안 궁금했습니다.나는 아직 나에게 이해가 되는 방식으로 설명하는 사람을 본 적이 없습니다.대부분 그것은 "나를 믿으세요"로 귀결됩니다.

메모:메모를 의미하는 것이 아닙니다.

도움이 되었습니까?

해결책

주로 더 효율적일 수 있기 때문에 값을 사용하지 않을 경우 값을 계산할 필요가 없습니다. 예를 들어, 세 가지 값을 함수로 전달할 수 있지만 조건부 표현 시퀀스에 따라 서브 세트 만 실제로 사용될 수 있습니다. C와 같은 언어로, 세 값은 모두 어쨌든 계산됩니다. 그러나 Haskell에서는 필요한 값 만 계산됩니다.

또한 무한 목록과 같은 멋진 물건을 허용합니다. 나는 C와 같은 언어로 무한한 목록을 가질 수 없지만 Haskell에서는 아무런 문제가 없습니다. Infinite 목록은 특정 수학 영역에서 상당히 자주 사용되므로 조작 할 수있는 능력을 갖추는 것이 유용 할 수 있습니다.

다른 팁

게으른 평가의 유용한 예는 다음을 사용하는 것입니다. 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] 반환)를 수행합니다.지연 평가로 인해 나머지는 정렬되지 않으므로 계산 시간이 많이 절약됩니다.

이것은 물론 매우 간단한 예이지만 게으름은 매우 큰 프로그램에서도 같은 방식으로 작동합니다.

그러나 이 모든 것에는 단점이 있습니다.프로그램의 런타임 속도와 메모리 사용량을 예측하기가 더 어려워집니다.이것은 게으른 프로그램이 느리거나 더 많은 메모리를 차지한다는 것을 의미하지는 않지만 알아두면 좋습니다.

게으른 평가가 여러 가지에 유용하다고 생각합니다.

첫째, 기존의 모든 게으른 언어는 순수합니다. 게으른 언어의 부작용에 대해 추론하기는 매우 어렵 기 때문입니다.

순수한 언어를 사용하면 정식 추론을 사용하여 기능 정의에 대한 이유가 있습니다.

foo x = x + 3

불행히도 게으른 환경에서는 게으른 설정보다 더 많은 진술이 돌아 오지 못하므로 ML과 같은 언어에서는 덜 유용합니다. 그러나 게으른 언어에서는 평등에 대해 안전하게 추론 할 수 있습니다.

둘째, ML의 '가치 제한'과 같은 많은 것들이 Haskell과 같은 게으른 언어로는 필요하지 않습니다. 이것은 구문의 큰 정리로 이어집니다. 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 Clause에 세부 사항을 '추진'할 수 있습니다.

실제로, 우리는 경비원을 사용하고 다음과 같은 붕괴하는 경향이 있습니다.

foo x y 
  | condition1 = some (complicated set of combinators) (involving bigscaryexpression)
  | condition2 = bigscaryexpression
  | otherwise  = Nothing
  where some x y = ...
        bigscaryexpression = ...
        condition1 = ...
        condition2 = ...

넷째, 게으름은 때때로 특정 알고리즘을 훨씬 더 우아하게 표현합니다. Haskell의 Lazy 'Quick Sort'는 한 라이너이며 처음 몇 항목 만 보면 해당 항목 만 선택하는 비용에 비례하여 비용을 지불한다는 이점이 있습니다. 당신이 이것을 엄격하게 수행하지 못하게하는 것은 없지만, 동일한 점근 성능을 달성하기 위해 매번 알고리즘을 다시 코딩해야 할 것입니다.

다섯째, 게으름을 사용하면 언어로 새로운 제어 구조를 정의 할 수 있습니다. 당신은 새로운 'if .. then .. els. 다음과 같은 함수를 정의하려고하는 경우

if' True x y = x
if' False x y = y

엄격한 언어에서는 조건 값에 관계없이 두 가지 모두 평가됩니다. 루프를 고려할 때 악화됩니다. 모든 엄격한 솔루션은 언어가 일종의 인용문 또는 명시 적 람다 구조를 제공해야합니다.

마지막으로, 같은 맥락에서, Monads와 같은 유형 시스템에서 부작용을 다루기위한 최고의 메커니즘 중 일부는 실제로 게으른 환경에서만 효과적으로 표현할 수 있습니다. 이것은 F#의 워크 플로의 복잡성을 Haskell Monads와 비교함으로써 목격 할 수 있습니다. (당신은 엄격한 언어로 모나드를 정의 할 수 있지만 불행히도 비교하여 게으름과 워크 플로가 부족하여 엄격한 수하물을 집어 들기 때문에 종종 모나드 법을 실패하게됩니다.)

정상적인 순서 평가에는 게으른 평가 (Haskell에서와 같이) 사이에 차이가 있습니다.

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- 번호)와 동일합니다.

CPU와 관련된 게으른 평가 RAM과 관련된 쓰레기 수집과 동일한 방식으로. GC를 사용하면 무제한의 메모리가있는 척하여 필요한만큼 메모리에 많은 객체를 요청할 수 있습니다. 런타임은 사용할 수없는 객체를 자동으로 회수합니다. LE를 사용하면 무제한 계산 리소스가있는 척 할 수 있습니다. 필요한만큼 많은 계산을 수행 할 수 있습니다. 런타임은 불필요한 (주어진 경우) 계산을 실행하지 않습니다.

이러한 "척"모델의 실질적인 장점은 무엇입니까? 개발자가 리소스 관리를 어느 정도 출시하고 소스에서 일부 보일러 플레이트 코드를 제거합니다. 그러나 더 중요한 것은 더 넓은 상황에서 솔루션을 효율적으로 재사용 할 수 있다는 것입니다.

숫자 S 목록과 N 숫자 N이 있다고 상상해보십시오. 목록 S에서 가장 가까운 숫자 N 숫자 M을 찾아야합니다. 당신은 가장 가까운 m을 찾습니다). 게으른 평가를 사용하는 경우 S 정렬하고 이진 검색을 적용하여 N에 가장 가까운 M을 찾을 수 있습니다. 좋은 게으른 분류의 경우 단일 N 및 O (ln (size (s))에 대한 O (size) 단계가 필요합니다.* (size (s) + size (l))) 똑같이 분산 된 L에 대한 단계. 최적의 효율성을 달성하기 위해 게으른 평가가 없다면 각 컨텍스트에 대한 알고리즘을 구현해야합니다.

Simon Peyton Jones를 믿는다면 게으른 평가는 중요하지 않습니다. 그 자체 그러나 디자이너들이 언어를 순수하게 유지하도록 강요 한 '헤어 셔츠'로만. 나는이 관점에 동정심이있다.

Richard Bird, John Hughes와 Ralf Hinze는 게으른 평가를 통해 놀라운 일을 할 수 있습니다. 그들의 작품을 읽으면 감사하는 데 도움이 될 것입니다. 좋은 출발점입니다 새의 웅장한 스도쿠 솔버와 휴즈의 종이 기능 프로그래밍이 중요한 이유.

TIC-TAC-TOE 프로그램을 고려하십시오. 여기에는 네 가지 기능이 있습니다.

  • 현재 보드를 차지하고 한 번의 이동이 적용된 새 보드 목록을 생성하는 이동 생성 기능.
  • 그런 다음 이동 생성 함수를 적용 하여이 작업에서 따를 수있는 가능한 모든 보드 위치를 도출하는 "Move Tree"기능이 있습니다.
  • 가장 좋은 다음 움직임을 찾기 위해 나무를 걷는 최소한의 기능이 있습니다.
  • 플레이어 중 하나가 이겼는지 결정하는 이사회 평가 기능이 있습니다.

이것은 우려의 분명한 분리를 만듭니다. 특히 이동 생성 기능과 보드 평가 기능은 게임의 규칙을 이해해야하는 유일한 것입니다. 이동 트리와 최소 기능은 완전히 재사용 할 수 있습니다.

이제 tic-tac-toe 대신 체스를 구현해 보겠습니다. "열렬한"(즉, 기존) 언어에서는 이동 트리가 메모리에 맞지 않기 때문에 작동하지 않습니다. 따라서 이제 보드 평가 및 이동 생성 기능을 Move Tree 및 Minimax Logic과 혼합해야합니다. Minimax 논리는 생성 할 이동을 결정하는 데 사용되어야하기 때문입니다. 우리의 멋진 깨끗한 모듈 식 구조가 사라집니다.

그러나 게으른 언어에서 이동 트리의 요소는 Minimax 기능의 요구에 따라 만 생성됩니다. 상단 요소에서 Minimax를 느슨하게하기 전에 전체 이동 트리를 생성 할 필요가 없습니다. 따라서 우리의 깨끗한 모듈 식 구조는 여전히 실제 게임에서 작동합니다.

다음은 아직 토론에서 제기되었다고 생각하지 않는 두 가지 점이 있습니다.

  1. 게으름은 동시 환경에서 동기화 메커니즘입니다. 일부 계산에 대한 참조를 만들고 많은 스레드간에 결과를 공유하는 가볍고 쉬운 방법입니다. 여러 스레드가 평가되지 않은 값에 액세스하려고 시도하면 그 중 하나만이 실행되고 다른 스레드만이 실행되고 다른 스레드는 그에 따라 차단하여 값을 사용할 수있게되면 값을받습니다.

  2. 게으름은 순수한 환경에서 데이터 구조를 상각하는 데 필수적입니다. 이것은 Okasaki에서 설명합니다 순전히 기능적인 데이터 구조 자세하게, 기본적인 아이디어는 게으른 평가가 특정 유형의 데이터 구조를 효율적으로 구현할 수 있도록 중요한 제어 형태의 돌연변이라는 것입니다. 우리는 종종 순결한 헤어 셔츠를 입도록 강요하는 게으름에 대해 말하지만, 다른 방법도 적용됩니다. 그것들은 한 쌍의 시너지 언어 기능입니다.

컴퓨터를 켜고 Windows를 켜면 Windows Explorer의 하드 드라이브에서 모든 디렉토리를 열지 않고 컴퓨터에 설치된 모든 단일 프로그램을 시작하지 않아도됩니다. 특정 디렉토리가 필요하거나 특정 프로그램이 필요할 때까지 "게으른"평가입니다.

"게으른"평가는 필요할 때 그리고 필요한 경우 작업을 수행하고 있습니다. 프로그래밍 언어 나 라이브러리의 기능 일 때 유용합니다. 일반적으로 모든 것을 미리 계산하는 것보다 게으른 평가를 구현하기가 더 어렵 기 때문입니다.

  1. 효율성을 높일 수 있습니다.이것은 명백해 보이지만 실제로 가장 중요한 것은 아닙니다.(또한 게으름은 죽이다 효율성도 마찬가지입니다. 이 사실은 즉시 명확하지 않습니다.하지만, 바로 계산을 하기보다는 임시 결과를 많이 저장해 두게 되면 엄청난 양의 RAM을 소모하게 됩니다.)

  2. 이를 통해 언어에 하드 코딩하는 대신 일반 사용자 수준 코드에서 흐름 제어 구성을 정의할 수 있습니다.(예를 들어, Java에는 for 루프;하스켈은 for 기능.Java에는 예외 처리 기능이 있습니다.하스켈에는 다양한 유형의 예외 모나드가 있습니다.C#에는 goto;하스켈에는 연속 모나드가 있습니다...)

  3. 이를 통해 알고리즘을 분리할 수 있습니다. 생성 결정을 위한 알고리즘의 데이터 얼마나 많이 생성할 데이터.개념적으로 무한한 결과 목록을 생성하는 함수 하나와 이 목록을 필요한 만큼 처리하는 또 다른 함수를 작성할 수 있습니다.더 중요한 점은 다음과 같습니다. 다섯 생성기 기능 및 다섯 소비자 함수를 사용하면 두 작업을 한 번에 결합하는 5 x 5 = 25개의 함수를 수동으로 코딩하는 대신 모든 조합을 효율적으로 생성할 수 있습니다.(!) 우리 모두는 디커플링이 좋은 것이라는 것을 알고 있습니다.

  4. 그것은 어느 정도 당신이 디자인을 하도록 강요합니다. 순수한 기능적 언어.항상 지름길을 택하고 싶은 유혹이 있지만, 게으른 언어에서는 약간의 불순물이라도 코드를 망치게 됩니다. 격렬하게 예측할 수 없으므로 지름길을 택하는 것을 강력히 방해합니다.

이걸 고려하세요:

if (conditionOne && conditionTwo) {
  doSomething();
}

DoSomething () 방법은 ConditionOne True 인 경우에만 실행됩니다. 그리고 조건부는 사실입니다. 컨디션이 false 인 경우 왜 컨디셔닝 결과를 계산해야합니까? 조건부의 평가는이 경우에 시간 낭비가 될 것입니다. 특히 조건이 일부 방법 프로세스의 결과 인 경우.

그것은 게으른 평가 관심의 한 예입니다 ...

게으름의 큰 이점 중 하나는 합리적인 상각 범위를 사용하여 불변 데이터 구조를 작성할 수 있는 능력입니다.간단한 예는 불변 스택(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().

게으른 버전에서는 추가가 O(1) 작업입니다.1개의 노드를 반환하고 목록의 실제 재구축을 일시 중단합니다.이 목록의 헤드를 얻으면 노드의 내용을 평가하여 강제로 헤드를 반환하고 나머지 요소로 하나의 정지를 생성하므로 목록의 헤드를 가져오는 것은 O(1) 작업입니다.

따라서 우리의 게으른 목록은 지속적인 재구축 상태에 있으므로 모든 요소를 ​​탐색할 때까지 이 목록을 재구축하는 데 드는 비용을 지불하지 않습니다.게으름을 사용하여 이 목록은 O(1) 동의 및 추가를 지원합니다.흥미롭게도 노드에 액세스할 때까지 노드를 평가하지 않기 때문에 잠재적으로 무한한 요소로 목록을 구성하는 것이 전적으로 가능합니다.

위의 데이터 구조에서는 각 순회 시 노드를 다시 계산할 필요가 없으므로 .NET의 바닐라 IEnumerable과 확실히 다릅니다.

게으른 평가는 데이터 구조에서 가장 유용합니다. 구조의 특정 지점 만 지정하고 전체 배열 측면에서 다른 모든 점을 표현하는 배열 또는 벡터를 정의 할 수 있습니다. 이를 통해 데이터 구조를 매우 간결하고 높은 런 타임 성능으로 생성 할 수 있습니다.

이것을 실제로 보려면 내 신경망 라이브러리를 볼 수 있습니다. 본능. 우아함과 고성능에 게으른 평가를 많이 사용합니다. 예를 들어 전통적으로 필수적 인 활성화 계산을 완전히 제거합니다. 단순한 게으른 표현은 나를 위해 모든 것을합니다.

이것은 예를 들어 사용됩니다 활성화 기능 또한 역전 학습 알고리즘에서 (두 링크 만 게시 할 수 있으므로 learnPat 기능 AI.Instinct.Train.Delta 직접 모듈). 전통적으로 둘 다 훨씬 더 복잡한 반복 알고리즘이 필요합니다.

이 스 니펫은 게으른 평가와 게으른 평가의 차이를 보여줍니다. 물론이 Fibonacci 기능은 그 자체로 최적화되어 재귀 대신 게으른 평가를 사용할 수 있지만 예제를 망칠 수 있습니다.

우리를 가정 해 봅시다 5월 게으른 평가가 아닌 20 개의 첫 번째 숫자를 사용해야합니다. 20 개의 숫자는 모두 선불로 생성되어야하지만 게으른 평가를 통해 필요에 따라 생성됩니다. 따라서 필요할 때 계산 가격 만 지불합니다.

샘플 출력

Not lazy generation: 0.023373
Lazy generation: 0.000009
Not lazy output: 0.000921
Lazy output: 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)

다른 사람들은 이미 큰 이유를 모두 주었지만, 왜 게으름이 중요한지 이해하는 데 도움이되는 유용한 운동은 고정점 엄격한 언어로 기능합니다.

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를 확장하고 물건을 혼란스럽게 할 수 있습니다. (이것은 노아가 그것에 대해 말한 것과 매우 흡사합니다. 도서관 언어가 아니라 엄격하거나 게으른다).

이제 엄격한 스칼라에서 동일한 기능을 작성한다고 상상해보십시오.

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에 대한 많은 용도가 보입니다. 평가.

내가 사용한 게으른 평가의 가장 유용한 착취는 특정 순서로 일련의 하위 기능을 불리는 함수였습니다. 이러한 하위 기능 중 하나가 실패한 경우 (False가 반환 됨), 호출 함수는 즉시 반환해야했습니다. 그래서 나는 이런 식으로 할 수있었습니다.

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
  }

무엇보다도 게으른 언어는 다차원적인 무한 데이터 구조를 허용합니다.

체계, 파이썬 등은 스트림을 갖는 단일 치수 무한 데이터 구조를 허용하지만 한 차원을 따라 횡단 할 수 있습니다.

게으름은 같은 프린지 문제, 그러나 해당 링크에 언급 된 코 루틴 연결을 주목할 가치가 있습니다.

게으른 평가는 가난한 사람의 평등 한 추론입니다 (이상적으로는 관련된 유형 및 운영 속성에서 코드의 속성을 추론 할 것으로 예상됩니다).

아주 잘 작동하는 예 : sum . take 10 $ [1..10000000000]. 우리는 단 하나의 직접적이고 간단한 숫자 계산 대신 10 숫자의 합으로 줄어드는 것을 신경 쓰지 않습니다. 게으른 평가가 없으면 물론 이것은 처음 10 개의 요소를 사용하기 위해 메모리에 거대한 목록을 만들 것입니다. 확실히 매우 느리고 메모리가없는 오류가 발생할 수 있습니다.

우리가 원하는만큼 크지 않은 예 : sum . take 1000000 . drop 500 $ cycle [1..20]. 목록에있는 루프에 있더라도 실제로 1 000 000 숫자를 합산합니다. 아직도 ~해야 한다 조건부가 거의없고 공식이 거의없는 직접 숫자 계산으로 단 하나의 직접 숫자 계산으로 줄어 듭니다. 어느 ~일 것이다 1 000 000 숫자를 합산하는 것보다 훨씬 나아졌습니다. 루프에 있더라도 목록에 있지 않더라도 (즉, 삼림 벌채 최적화 후).


또 다른 점은 코딩 할 수 있다는 것입니다. 꼬리 재귀 모듈로 단점 스타일, 그리고 그것 그냥 작동합니다.

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)); 
          } 
    }
}

글쎄,이 기술은 클래스를 사용하여 클라이언트 코드를 사용하여 직원 객체를 사용하는 클라이언트가 감독자의 데이터에 액세스 해야하는 경우를 제외하고 감독자 데이터 레코드의 데이터베이스를 호출 할 필요가 없습니다. 그러나 감독자가 필요할 때, 감독자 재산에 대한 첫 번째 호출은 데이터베이스 호출을 트리거하고 데이터가 가져 와서 사용할 수 있습니다 ...

발췌 고차 기능

3829로 나눌 수있는 100,000 미만의 가장 큰 숫자를 찾아 보겠습니다. 그렇게하려면 솔루션이 있다는 것을 알 수있는 일련의 가능성을 필터링합니다.

largestDivisible :: (Integral a) => a  
largestDivisible = head (filter p [100000,99999..])  
    where p x = x `mod` 3829 == 0 

우리는 먼저 모든 숫자 목록을 100,000보다 낮게 만듭니다. 그런 다음 우리는 술어로 필터링하고 숫자가 내림차순으로 정렬되기 때문에 술어를 만족시키는 가장 큰 숫자는 필터링 된 목록의 첫 번째 요소입니다. 우리는 시작 세트에 유한 목록을 사용할 필요조차 없었습니다. 그것은 다시 행동의 게으름입니다. 우리는 필터링 된 목록의 헤드 만 사용하기 때문에 필터링 된 목록이 유한하거나 무한한 경우는 중요하지 않습니다. 첫 번째 적절한 솔루션이 발견되면 평가가 중지됩니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top