게으른 평가의 개념이 유용한 이유는 무엇입니까?


30

보인다 게으른 평가 식의 자신의 코드가 실행되는 순서 이상 잃을 제어 프로그래머가 발생할 수 있습니다. 프로그래머가 왜 이것이 수용 가능하거나 원하는지 이해하는 데 어려움이 있습니다.

언제, 어디서 식을 평가할 지 보장 할 수없는 경우,이 패러다임을 사용하여 예상대로 작동하는 예측 가능한 소프트웨어를 구축 할 수 있습니까?


10
대부분의 경우 문제가되지 않습니다. 다른 모든 사람들에게는 엄격함을 적용 할 수 있습니다.
Cat Plus Plus

22
haskell 과 같은 순수하게 기능적인 언어의 요점은 코드가 실행될 때 부작용이 없으므로 코드를 실행할 필요가 없다는 것입니다.
비트 마스크

21
"실행 코드"에 대한 생각을 멈추고 "계산 결과"에 대한 생각을 시작해야합니다. 그것이 가장 흥미로운 문제에서 실제로 원하는 것이기 때문입니다. 물론 프로그램은 대개 어떤 방식 으로든 환경과 상호 작용해야하지만 코드의 작은 부분으로 축소 될 수 있습니다. 나머지는 순전히 기능적으로 일할 수 있으며 게으름은 추론을 훨씬 간단하게 만들 수 있습니다.
leftaroundabout

6
제목 ( "게으른 평가를 사용하는 이유는 무엇입니까?")의 제목과 신체의 문제 ( "게으른 평가는 어떻게 사용합니까?")와 매우 다릅니다. 전자의 경우이 관련 질문에 대한 내 대답을 참조하십시오 .
Daniel Wagner

1
게으름이 유용한 예 : In Haskell head . sortO(n)게으름 (not O(n log n)) 으로 인해 복잡성 이 있습니다 . 지연 평가 및 시간 복잡성을 참조하십시오 .
Petr Pudlák

답변:


62

많은 답변이 무한 목록 및 계산되지 않은 계산 부분의 성능 향상과 같은 것들에 들어가고 있지만 게으름에 대한 더 큰 동기 부여가 누락되었습니다 : 모듈성 .

John Hughes가 인용 한 "Why Functional Programming Matters"(PDF 링크) 에 고전적인 주장이 제시되어 있습니다. 이 논문의 주요 예 (섹션 5)는 알파-베타 검색 알고리즘을 사용하여 Tic-Tac-Toe를 재생하는 것입니다. 요점은 (p. 9)입니다.

[지연 평가]는 가능한 많은 답변을 구성하는 생성기 및 적절한 답변을 선택하는 선택기로 프로그램을 모듈화하는 것이 실용적입니다.

Tic-Tac-Toe 프로그램은 주어진 위치에서 시작하여 전체 게임 트리를 생성하는 함수와이를 소비하는 별도의 함수로 작성 될 수 있습니다. 런타임에 이것은 전체 게임 트리를 본질적으로 생성하지 않으며 소비자가 실제로 필요한 하위 부분 만 생성합니다. 소비자를 변화시킴으로써 대안이 만들어지는 순서와 조합을 바꿀 수 있습니다. 발전기를 전혀 교체 할 필요가 없습니다.

간절한 언어에서는 트리를 생성하는 데 너무 많은 시간과 메모리를 소비하기 때문에 이런 방식으로 작성할 수 없습니다. 그래서 당신은 결국 :

  1. 생성과 소비를 동일한 기능으로 결합
  2. 특정 소비자에게만 최적으로 작동하는 프로듀서 작성
  3. 게으름의 자신의 버전을 구현합니다.

자세한 정보 또는 예를 참조하십시오. 흥미로운 소리입니다.
Alex Nye

1
@AlexNye : John Hughes 논문에 더 많은 정보가 있습니다. 협박 때문에 의심의 여지 학술 논문되는 없습니다 --- 그리고에도 불구하고 --- 그것은 사실이다 매우 접근하고 읽을. 길이가 아니라면 아마도 여기에 대한 답으로 적합 할 것입니다!
Tikhon Jelvis

아마도이 대답을 이해하려면 휴즈의 논문을 읽어야합니다. 그렇게하지 않았지만, 게으름과 모듈성이 어떻게, 왜 관련되어 있는지 여전히 알 수 없습니다.
stakx

@stakx 더 나은 설명이 없으면 우연히 말고는 관련이없는 것 같습니다. 이 예제에서 게으름의 장점은 게으른 생성기가 게임의 모든 가능한 상태를 생성 할 수 있지만 발생하는 것만 소비되므로 시간 / 메모리를 낭비하지 않는다는 것입니다. 생성기는 지연 생성기없이 소비자로부터 분리 될 수 있으며, 소비자로부터 분리되지 않고 지연 될 수있다 (더 어렵지만).
이즈 카타

@Iztaka : "게으른 발전기가 아닌 발전기를 소비자와 분리 할 수 ​​있으며 소비자와 분리하지 않고 게을러는 것이 가능합니다." 이 경로로 이동하면 ** 과도하게 특수화 된 발전기 ** (생성기는 한 소비자를 최적화하기 위해 작성되었으며 다른 소비자에게는 재사용 할 때 차선 책임)로 끝날 수 있습니다. 일반적인 예는 루트 객체에서 하나의 문자열을 원하기 때문에 전체 객체 그래프를 가져오고 구성하는 객체 관계형 매퍼입니다. 게으름은 많은 경우를 피합니다 (모두는 아님).
sacundim

32

언제, 어디서 식을 평가할 지 보장 할 수없는 경우,이 패러다임을 사용하여 예상대로 작동하는 예측 가능한 소프트웨어를 구축 할 수 있습니까?

표현식에 부작용이없는 경우 표현식이 평가되는 순서는 값에 영향을 미치지 않으므로 프로그램의 동작은 순서에 영향을받지 않습니다. 따라서 행동은 완벽하게 예측할 수 있습니다.

이제 부작용은 다릅니다. 부작용이 어떤 순서로든 발생할 수 있다면 프로그램의 동작은 실제로 예측할 수 없을 것입니다. 그러나 실제로는 그렇지 않습니다. Haskell과 같은 게으른 언어는 참조가 투명하다는 것을 의미합니다. 즉,식이 평가되는 순서가 결과에 영향을 미치지 않도록하십시오. Haskell에서는 사용자가 볼 수있는 부작용이있는 모든 작업이 IO 모나드 내부에서 발생하도록합니다. 이렇게하면 모든 부작용이 예상 한 순서대로 정확하게 발생합니다.


15
이것이 Haskell과 같은 "강제 순도"를 가진 언어 만이 기본적으로 모든 곳에서 게으름을 지원하는 이유입니다. 스칼라와 같은 "권장 된 순도"언어는 프로그래머가 게으름을 원하는 곳을 명시 적으로 말해야하며, 게으름이 문제를 일으키지 않도록하는 것은 프로그래머에게 달려 있습니다. 기본적으로 게으름이 있고 추적되지 않은 부작용 이있는 언어 는 실제로 예측하기가 어렵습니다.
Ben

1
반드시 IO 이외의 모나드도 부작용을 일으킬 수 있습니다
jk.

1
@jk IO 만 외부 부작용을 일으킬 수 있습니다 .
dave4420

@ dave4420 그렇습니다. 그러나이 대답은
jk

1
@jk 하스켈에서 실제로 아니오. IO를 제외한 모나드 (또는 IO에 빌드 된 모나드)에는 부작용이 없습니다. 그리고 이것은 컴파일러가 IO를 다르게 취급하기 때문입니다. IO를 "Immutable Off"로 생각합니다. 모나드는 특정 실행 순서를 보장하는 (간단한) 방법입니다 (따라서 사용자가 "yes"를 입력 한 후에 만 파일이 삭제됩니다 ).
scarfridge

22

데이터베이스에 익숙한 경우 데이터를 처리하는 가장 빈번한 방법은 다음과 같습니다.

  • 다음과 같은 질문을하십시오 select * from foobar
  • 더 많은 데이터가 있지만 다음 행을 가져 와서 처리하십시오.

결과가 생성되는 방법과 방법 (인덱스? 전체 테이블 스캔?) 또는시기 (요청시 모든 데이터를 한 번에 또는 증분으로 생성해야합니까)를 제어하지 않습니다. 당신이 아는 전부는 : if 더 많은 데이터가 당신이 요청하는 경우, 당신이 그것을 얻을 것이다.

게으른 평가는 같은 것에 가깝습니다. ie로 정의 된 무한 목록이 있다고 가정하십시오. 피보나치 수열-5 개의 숫자가 필요한 경우 5 개의 숫자를 계산합니다. 1000이 필요한 경우 1000을 얻습니다. 트릭은 런타임이 언제 어디서 무엇을 제공해야하는지 알고 있다는 것입니다. 매우 편리합니다.

(Java 프로그래머는 Iterator를 사용하여이 동작을 에뮬레이트 할 수 있습니다. 다른 언어는 비슷한 것을 가질 수 있습니다)


좋은 지적. 예를 들어 Collection2.filter()(해당 클래스의 다른 메소드와 마찬가지로) 게으른 평가를 거의 구현합니다. 결과는 보통과 비슷해 보이지만 Collection실행 순서는 직관적이지 않을 수도 있습니다 (적어도 명백하지 않음). 또한 yield파이썬에는 평범한 반복자보다 게으른 평가를 지원하는 데 더 가까운 Python (및 C #의 비슷한 기능)이 있습니다.
Joachim Sauer

C에서 @JoachimSauer는 # 항복 반환하거나, 물론 당신은 게으른 그 중 반 정도는 사전 빌드의 LINQ의 oprerators을 사용할 수 있습니다
JK.

+1 : 명령형 / 객체 지향 언어로 반복자를 언급합니다. Java에서 스트림 및 스트림 기능을 구현하기 위해 비슷한 솔루션을 사용했습니다. 반복자를 사용하면 길이를 알 수없는 입력 스트림에서 take (n), dropWhile ()과 같은 함수를 사용할 수 있습니다.
조르지오

13

이름이 "Ab"로 시작하고 20 년이 지난 최초 2000 명의 사용자 목록을 데이터베이스에 요청하십시오. 또한 그들은 남성이어야합니다.

여기 작은 다이어그램이 있습니다.

You                                            Program Processor
------------------------------------------------------------------------------
Get the first 2000 users ---------->---------- OK!
                         --------------------- So I'll go get those records...
WAIT! Also, they have to ---------->---------- Gotcha!
start with "Ab"
                         --------------------- NOW I'll get them...
WAIT! Make sure they're  ---------->---------- Good idea Boss!
over 20!
                         --------------------- Let's go then...
And one more thing! Make ---------->---------- Anything else? Ugh!
sure they're male!

No that is all. :(       ---------->---------- FINE! Getting records!

                         --------------------- Here you go. 
Thanks Postgres, you're  ---------->----------  ...
my only friend.

이 끔찍한 끔찍한 상호 작용으로 볼 수 있듯이 "데이터베이스"는 모든 조건을 처리 할 준비가 될 때까지 실제로 아무것도하지 않습니다. 각 단계에서 결과가 느리게 로딩되고 매번 새로운 조건이 적용됩니다.

처음 2000 명의 사용자를 가져 오는 대신, "Ab"로 필터링하고, 반환하고, 20 명 이상을 필터링하고, 반환하고, 수컷을 필터링하여 최종적으로 반환하는 사용자와는 반대로.

간단히 말해서 게으른 로딩.


1
이것은 IMHO에 대한 정말 형편없는 설명입니다. 불행히도 나는이 특정 SE 사이트에 대해 투표를하기에 충분한 담당자가 없습니다. 게으른 평가의 실제 포인트는 다른 결과를 사용할 준비가 될 때까지 이러한 결과가 실제로 생성 되지 않는다는 것입니다.
Alnitak

내 게시 된 답변은 귀하의 의견과 똑같은 것을 말하고 있습니다.
sergserg

매우 정중 한 프로그램 프로세서입니다.
Julian

9

식의 게으른 평가는 주어진 코드 조각의 디자이너가 코드가 실행되는 순서를 제어하지 못하게합니다.

디자이너는 결과가 동일한 경우 표현식이 평가되는 순서를 신경 쓰지 않아야합니다. 평가를 연기하면 일부 표현식을 모두 평가하지 않아도되므로 시간이 절약됩니다.

낮은 수준에서 동일한 아이디어를 볼 수 있습니다. 많은 마이크로 프로세서가 명령을 순서대로 실행할 수 없기 때문에 다양한 실행 단위를보다 효율적으로 사용할 수 있습니다. 핵심은 명령 간의 종속성을보고 결과가 변경되는 위치의 순서를 바꾸지 않는 것입니다.


5

게으른 평가에 대한 몇 가지 주장이 있습니다.

  1. 모듈 식 지연 평가를 사용하면 코드를 여러 부분으로 나눌 수 있습니다. 예를 들어, "목록 목록에서 요소의 처음 10 개의 왕복을 찾아서 왕복이 1보다 작은"문제가 있다고 가정합니다. Haskell과 같은 것으로 쓸 수 있습니다

    take 10 . filter (<1) . map (1/)
    

    그러나 이것은 엄격한 언어에서는 정확하지 않습니다. 제공하면 [2,3,4,5,6,7,8,9,10,11,12,0]0으로 나누기 때문입니다. 이것이 실제로 왜 멋진 지에 대한 sacundim의 답변을보십시오.

  2. 더 많은 일이 엄격하게 수행됩니다. 프로그램이 "열심 한"평가 전략으로 종료되면 "게으른"전략으로 종료되지만 그 반대는 사실이 아닙니다. 이 현상의 특정 예로서 무한 데이터 구조 (실제로는 멋진 것)와 같은 것을 얻습니다. 더 많은 프로그램이 게으른 언어로 작동합니다.

  3. 최적의 호출 별 평가는 시간에 대해 점진적으로 최적입니다. 주요 게으른 언어 (기본적으로 Haskell 및 Haskell)가 필요에 따라 약속하지는 않지만 최적의 비용 모델을 기대할 수 있습니다. 엄격 분석기 (및 추론 적 평가)는 실제로 오버 헤드를 줄입니다. 우주는 더 복잡한 문제입니다.

  4. 게으른 평가를 사용하여 순도강요 하면 프로그래머가 통제력을 상실하기 때문에 부작용을 치료할 수없는 방식으로 치료할 수 있습니다. 이것은 좋은 일입니다. 참조 투명성은 프로그램에 대한 프로그래밍, 굴절 및 추론을 훨씬 쉽게 만듭니다. 엄격한 언어는 필연적으로 약간의 불결한 압력에 굴복합니다. Haskell과 Clean은 아름답게 저항했습니다. 부작용이 항상 악하다고 말하는 것은 아니지만, 그 이유만으로도 게으른 언어를 사용하기에 충분할 정도로 유용합니다.


2

제공되는 값 비싼 계산이 많지만 실제로 어떤 계산이 필요한지 또는 어떤 순서로 알지 못한다고 가정하십시오. 소비자가 사용 가능한 것을 파악하고 아직 수행되지 않은 계산을 트리거하도록 복잡한 mother-may-i 프로토콜을 추가 할 수 있습니다. 또는 계산이 모두 완료된 것처럼 작동하는 인터페이스를 제공 할 수 있습니다.

또한 무한한 결과가 있다고 가정하십시오. 예를 들어 모든 소수 세트. 집합을 미리 계산할 수 없다는 것은 명백합니다. 따라서 소수 영역의 모든 작업은 게 으르 야합니다.


1

게으른 평가를 사용하면 코드 실행에 대한 제어력을 잃지 않아도 여전히 결정적입니다. 그래도 익숙해지기는 어렵습니다.

게으른 평가는 간절한 평가는 실패하지만 그 반대는 아닌 일부 경우에 종료 될 람다 용어의 감소 방법이기 때문에 유용합니다. 여기에는 1) 주기적 그래프 구조를 생성하지만 함수형 스타일로 수행하려는 경우와 같이 실제로 계산을 실행하기 전에 계산 결과에 링크해야 할 때, 2) 무한한 데이터 구조를 정의하지만이 구조 피드를 작동시키는 경우가 포함됩니다. 데이터 구조의 일부만 사용합니다.

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