나는 게으른 평가가 어떻게 작동하는지 배웠는데 왜 현재 평가되는 모든 소프트웨어에 게으른 평가가 적용되지 않습니까? 여전히 열성적인 평가를 사용하는 이유는 무엇입니까?
나는 게으른 평가가 어떻게 작동하는지 배웠는데 왜 현재 평가되는 모든 소프트웨어에 게으른 평가가 적용되지 않습니까? 여전히 열성적인 평가를 사용하는 이유는 무엇입니까?
답변:
지연 평가에는 장부 관리 오버 헤드가 필요합니다. 아직 평가되지 않았는지 여부 등을 알아야합니다. 열성적인 평가는 항상 평가되므로 알 필요가 없습니다. 동시 상황에서 특히 그렇습니다.
두 번째로, 원하는 경우 나중에 호출 할 함수 객체로 패키징하여 열성적인 평가를 지연 평가로 변환하는 것은 쉽지 않습니다.
셋째, 게으른 평가는 통제력 상실을 의미합니다. 디스크에서 파일 읽기를 게으르게 평가하면 어떻게됩니까? 아니면 시간이 있습니까? 허용되지 않습니다.
열악한 평가는보다 효율적이고 제어 가능하며 게으른 평가로 간단하게 변환됩니다. 왜 게으른 평가를 원하십니까?
readFile
가 정확히 필요한 것입니다. 게다가, 게으른 평가에서 열성적인 평가로 전환하는 것은 사소한 일입니다.
head [1 ..]
Haskell에서 제공하는 코드는 1
무엇입니까?
주로 게으른 코드와 상태가 잘못 혼합되어 버그를 찾기가 어려울 수 있기 때문입니다. 종속 개체의 상태가 변경되면 평가할 때 지연 개체의 값이 잘못 될 수 있습니다. 상황이 적절하다는 것을 알면 프로그래머가 게으른 객체를 명시 적으로 코딩하는 것이 훨씬 좋습니다.
참고로 Haskell은 모든 것에 Lazy 평가를 사용합니다. 이것은 기능적 언어이고 상태를 사용하지 않기 때문에 가능합니다 (명백하게 표시된 예외적 인 상황 제외).
set!
은 게으른 Scheme 인터프리터에서 사용 하는 것입니다. > :(
게으른 평가가 항상 좋은 것은 아닙니다.
게으른 평가의 성능 이점은 클 수 있지만 열악한 환경에서 대부분의 불필요한 평가를 피하는 것은 어렵지 않습니다. 반드시 게으른 것이 쉽고 완벽하게 이루어 지지만 코드에서 불필요한 평가는 거의 문제가되지 않습니다.
게으른 평가의 장점은보다 명확한 코드를 작성할 수 있다는 것입니다. 무한 자연수 목록을 필터링하여 10 번째 소수를 얻는 것과 해당 목록의 10 번째 요소를 취하는 것은 가장 간결하고 명확한 진행 방법 중 하나입니다 (의사 코드).
let numbers = [1,2...]
fun is_prime x = none (map (y-> x mod y == 0) [2..x-1])
let primes = filter is_prime numbers
let tenth_prime = first (take primes 10)
게으르지 않고 간결하게 표현하는 것은 매우 어렵다고 생각합니다.
그러나 게으름은 모든 것에 대한 답이 아닙니다. 우선 상태의 경우 게으름을 투명하게 적용 할 수 없으며 상태가 매우 명시 적 인 경우 Haskell에서 작업하지 않는 한 상태 저장을 자동으로 감지 할 수 없다고 생각합니다. 따라서 대부분의 언어에서 게으름은 수동으로 수행해야하므로 일이 덜 명확 해지고 게으른 평가의 큰 이점 중 하나가 제거됩니다.
게다가 게으름은 평가되지 않은 표현을 유지하는 데 상당한 오버 헤드가 발생하기 때문에 성능상의 단점이 있습니다. 스토리지를 모두 사용하며 단순한 값보다 작업 속도가 느립니다. 게으른 버전은 개 속도가 느리고 성능에 대해 추론하기가 어렵 기 때문에 코드를 간절히 찾아야한다는 사실은 드문 일이 아닙니다.
그것이 일어나는 경향이 있기 때문에 절대적인 최선의 전략은 없습니다. 게으름은 무한한 데이터 구조 나 다른 전략을 사용하여 더 나은 코드를 작성할 수 있다면 훌륭하지만, 최적화하기가 더 쉬울 수 있습니다.
간절하고 게으른 평가의 장단점을 간단히 비교하면 다음과 같습니다.
열성적인 평가 :
불필요하게 평가하는 물건의 잠재적 인 오버 헤드.
방해받지 않고 빠른 평가.
게으른 평가 :
불필요한 평가가 없습니다.
값을 사용할 때마다 부기 오버 헤드가 발생합니다.
따라서 평가할 필요가없는 표현이 많으면 게으른 것이 좋습니다. 그러나 평가할 필요가없는 표현식이없는 경우 게으른 것은 순수한 오버 헤드입니다.
이제, 실제 소프트웨어를 살펴 수 있습니다 : 얼마나 많은 기능 당신이 할 쓰기 것을 하지 모든 인수의 평가를 필요로? 특히 한 가지만 수행하는 현대적인 짧은 기능의 경우 기능의 비율이이 범주에 속합니다. 따라서 게으른 평가는 실제로 아무것도 저장하지 않고 부기 오버 헤드를 대부분 발생시킵니다.
결과적으로 게으른 평가는 단순히 평균을 지불하지 않으며, 열성적인 평가는 현대 코드에 더 적합합니다.
@DeadMG가 지적한 것처럼 지연 평가에는 예약 유지 오버 헤드가 필요합니다. 이것은 열악한 평가에 비해 비쌀 수 있습니다. 이 진술을 고려하십시오 :
i = (243 * 414 + 6562 / 435.0 ) ^ 0.5 ** 3
계산하려면 약간의 계산이 필요합니다. 지연 평가를 사용하는 경우 평가를 사용할 때마다 평가되었는지 확인해야합니다. 이것이 많이 사용되는 타이트 루프 안에 있으면 오버 헤드가 크게 증가하지만 이점은 없습니다.
열성적인 평가와 괜찮은 컴파일러로 수식은 컴파일 타임에 계산됩니다. 대부분의 옵티마이 저는 해당되는 경우 루프에서 할당을 이동합니다.
지연 평가는 자주 액세스하지 않고 검색해야하는 오버 헤드가 높은 데이터를로드하는 데 가장 적합합니다. 따라서 핵심 기능보다 사례를 처리하는 것이 더 적합합니다.
일반적으로 가능한 빨리 액세스하는 항목을 평가하는 것이 좋습니다. 이 평가에서는 지연 평가가 작동하지 않습니다. 항상 무언가에 액세스 할 경우 게으른 평가는 오버 헤드를 추가하는 것입니다. 지연 평가 사용의 비용 / 혜택은 액세스하는 항목에 액세스 할 가능성이 낮아짐에 따라 감소합니다.
항상 게으른 평가를 사용하는 것도 초기 최적화를 의미합니다. 이것은 종종 더 복잡하고 비싼 코드를 초래하는 나쁜 습관입니다. 불행히도, 조기 최적화는 종종 간단한 코드보다 성능이 느린 코드를 생성합니다. 최적화 효과를 측정 할 수있을 때까지 코드를 최적화하는 것은 좋지 않습니다.
조기 최적화를 피하는 것은 좋은 코딩 방법과 충돌하지 않습니다. 모범 사례가 적용되지 않은 경우 초기 최적화는 루프 밖으로 계산을 이동하는 등의 우수한 코딩 사례를 적용하는 것으로 구성 될 수 있습니다.
식의 가치를 결정하기 위해 식을 완전히 평가해야하는 경우 게으른 평가는 단점이 될 수 있습니다. 부울 값의 긴 목록이 있고 모든 값 이 true 인지 확인하려고 합니다.
[True, True, True, ... False]
이를 위해서는 목록의 모든 요소 를 살펴 봐야 합니다. 따라서 평가를 지연시킬 가능성은 없습니다. 접기를 사용하여 목록의 모든 부울 값이 참인지 확인할 수 있습니다. 지연 평가를 사용하는 접기 권한을 사용하면 목록의 모든 요소를 살펴 봐야하기 때문에 지연 평가의 이점을 얻지 못합니다.
foldr (&&) True [True, True, True, ... False]
> 0.27 secs
이 경우 폴드 오른쪽은 느린 폴드 왼쪽보다 훨씬 느리며 지연 평가를 사용하지 않습니다.
foldl' (&&) True [True, True, True, ... False]
> 0.09 secs
그 이유는 왼쪽으로 접는 부분이 꼬리 재귀를 사용하기 때문입니다. 즉, 반환 값을 누적하고 대량의 작업 체인을 작성하여 메모리에 저장하지 않습니다. 어쨌든 두 함수가 전체 목록을보아야하고 fold right는 tail recursion을 사용할 수 없기 때문에 lazy fold right보다 훨씬 빠릅니다. 요점은, 당신은 당면한 작업에 가장 적합한 것을 사용해야한다는 것입니다.