폴더 대 폴더 (또는 폴더 ')의 의미


155

먼저, 내가 읽고있는 Real World Haskell 은 결코 사용하지 foldl않고 대신 사용 한다고 말합니다 foldl'. 그래서 나는 그것을 믿습니다.

하지만 사용하는 경우에 흐릿 해요 foldrfoldl'. 나는 그들이 다르게 작동하는 방식의 구조를 볼 수 있지만 "어느 쪽이 더 낫다"는 것을 이해하기에는 너무 바보입니다. 둘 다 동일한 대답을 생성하기 때문에 어떤 것이 사용되는지는 중요하지 않은 것처럼 보입니다. 사실,이 구성에 대한 나의 이전 경험은 Ruby 's inject와 Clojure 's reduce에서 왔으며, "왼쪽"과 "오른쪽"버전이없는 것 같습니다. (질문 : 어떤 버전을 사용합니까?)

나 같은 똑똑한 도전에 도움이 될만한 통찰력은 대단히 감사하겠습니다!

답변:


174

다음과 같은 foldr f x ys곳 의 재귀ys = [y1,y2,...,yk]

f y1 (f y2 (... (f yk x) ...))

반면 재귀는 foldl f x ys다음과 같습니다.

f (... (f (f x y1) y2) ...) yk

여기서 중요한 차이점은 결과 경우이다 f x y의 값을 사용하여 계산 될 수는 x다음 foldr하지 않습니다 '필요가 전체 목록을 검토 할 수 있습니다. 예를 들어

foldr (&&) False (repeat False)

반환 False반면,

foldl (&&) False (repeat False)

종료되지 않습니다. (참고 : repeat False모든 요소가 무한대의 목록을 만듭니다 False.)

반면에 foldl'꼬리는 재귀적이고 엄격합니다. 무엇이든 (예를 들어, 목록의 숫자를 합하여) 전체 목록을 순회해야한다는 것을 알고 있다면 foldl'공간보다 (아마도 시간이) 효율적 foldr입니다.


2
foldr에서는 f y1 썽 크로 평가되므로 False를 반환하지만 foldl에서는 f가 매개 변수를 알 수 없습니다 .Haskell에서는 꼬리 재귀 여부에 관계없이 썽크 오버플로를 유발할 수 있습니다. 너무 큰. foldl '는 실행에 따라 즉시 썽크를 줄일 수 있습니다.
소여

25
혼동을 피하기 위해 괄호에는 실제 평가 순서가 표시되지 않습니다. Haskell이 게으 르기 때문에 가장 바깥 쪽 표현이 먼저 평가됩니다.
Lii

그레이트 답변. 리스트에서 도중에 멈출 수있는 접기를 원한다면 폴더를 사용해야한다고 덧붙이고 싶습니다. 내가 실수하지 않으면 왼쪽 주름을 멈출 수 없습니다. (당신이 "알고 있다면 ... 당신은 ... 전체 목록을 통과 할 것"이라고 말할 때 이것을 암시합니다). 또한 "값에만 사용"오타가 "값만 사용"으로 변경되어야합니다. 즉, "on"이라는 단어를 제거하십시오. (Stackoverflow를 사용하면 2 문자 변경 사항을 제출할 수 없습니다!).
Lqueryvg

@Lqueryvg 왼쪽 폴드를 멈추는 두 가지 방법 : 1. 오른쪽 폴드로 코드를 작성하십시오 (참조 fodlWhile); 2. 왼쪽 스캔 ( scanl) 으로 변환하고 last . takeWhile p이와 유사하게 중지하십시오 . 어, 그리고 3. 사용하십시오 mapAccumL. :)
Will Ness

1
@Desty는 전체 결과 foldl를 수집하고 모든 작업이 완료되고 더 이상 수행 할 단계가없는 경우에만 결과를 수집하는 것과 달리 각 단계에서 전체 결과의 새로운 부분을 생성하기 때문에 . 그래서 예를 들어 foldl (flip (:)) [] [1..3] == [3,2,1], 그래서 scanl (flip(:)) [] [1..] = [[],[1],[2,1],[3,2,1],...]... IOW, foldl f z xs = last (scanl f z xs)그리고 무한리스트에는 마지막 요소가 없다 (위의 예에서는 자체 INF에서 아래로 1, 무한 목록이 될 것이다).
Will Ness


33

의미가 다르므로 foldl와를 교환 할 수 없습니다 foldr. 하나는 왼쪽에서 요소를 접고 다른 하나는 오른쪽에서 접습니다. 이렇게하면 연산자가 다른 순서로 적용됩니다. 이것은 빼기와 같은 모든 비 연관 연산에 중요합니다.

Haskell.org 에는 이 주제에 관한 흥미로운 기사 가 있습니다.


그들의 의미는 사소한 방식으로 만 다르며 실제로는 의미가 없습니다. 사용 된 함수의 인수 순서. 따라서 인터페이스 측면에서도 여전히 교환 가능한 것으로 계산됩니다. 실제 차이점은 최적화 / 구현 만하는 것 같습니다.
Evi1M4chine

2
@ Evi1M4chine 차이점이 없습니다. 반대로, 그들은 실질적입니다 (실제로 의미가 있습니다). 사실, 내가 오늘이 답을 쓰면이 차이를 더욱 강조 할 것입니다.
Konrad Rudolph

23

요약하자면 foldr누산기 함수가 두 번째 인수에서 게으 르면 더 좋습니다. Haskell Wiki의 Stack Overflow (pun 예정) 에서 더 많은 것을 읽으십시오 .


18

모든 용도의 99 % foldl'가 선호 되는 이유 는 foldl대부분의 용도로 일정한 공간에서 실행할 수 있기 때문입니다.

기능을 수행하십시오 sum = foldl['] (+) 0. 때 foldl'사용되며, 합계는 바로 이렇게 적용, 계산 sum이 같은 것을 사용하는 경우 무한 목록은 (일정한 공간에서 가장 가능성이 영원히 실행 한 것 Int들, Double이야, Float이야. Integers는 경우 일정한 공간보다 더 많은 사용합니다 숫자가)보다 커집니다 maxBound :: Int.

을 사용하면 foldl응답을 저장하는 대신 나중에 평가할 수있는 응답을 얻는 방법의 레시피와 같은 썽크가 구성됩니다. 이러한 썽 크는 많은 공간을 차지할 수 있으며,이 경우 썽크를 저장하는 것보다 스택을 오버플로하는 것보다 식을 평가하는 것이 훨씬 좋습니다 (스택 오버플로로 이어지고…

희망이 도움이됩니다.


1
전달 된 함수 foldl가 하나 이상의 인수에 생성자를 적용하는 것 외에는 큰 예외입니다 .
dfeuer

foldl실제로 최선의 선택이 언제 인지에 대한 일반적인 패턴이 있습니까? (무한리스트처럼 때 foldr.? 잘못된 선택, 최적화 - 현명)
Evi1M4chine

@ Evi1M4chine foldr은 무한 목록에 대한 잘못된 선택이 무엇을 의미하는지 확실하지 않습니다 . 실제로 foldl또는 foldl'무한 목록을 사용해서는 안됩니다 . 스택 오버플로에
KevinOrr

14

그건 그렇고, Ruby inject와 Clojure reducefoldl(또는 foldl1사용하는 버전에 따라)입니다. 일반적으로 언어에 하나의 양식 만있는 경우 Python reduce, Perl List::Util::reduce, C ++ accumulate, C # Aggregate, Smalltalk inject:into:, PHP array_reduce, Mathematica Fold등을 reduce포함한 왼쪽 접힘입니다 . 일반적인 Lisp의 기본값은 왼쪽 접힘이지만 오른쪽 접는 옵션이 있습니다.


2
이 의견은 도움이 되나 출처에 감사하겠습니다.
titaniumdecoy

일반적인 Lisp reduce은 게으르지 않으므로 foldl'여기에서 고려해야 할 사항이 많지 않습니다.
MicroVirus

나는 foldl'그들이 엄격한 언어이기 때문에 당신이 의미하는 것 같아요 ? 그렇지 않으면 모든 버전이 스택 오버플로를 일으키는 것을 의미 foldl하지 않습니까?
Evi1M4chine

6

으로 콘라드는 지적, 그 의미는 다르다. 그들은 같은 유형조차 가지고 있지 않습니다.

ghci> :t foldr
foldr :: (a -> b -> b) -> b -> [a] -> b
ghci> :t foldl
foldl :: (a -> b -> a) -> a -> [b] -> a
ghci> 

예를 들어,리스트 추가 연산자 (++)는 다음과 foldr같이 구현할 수 있습니다.

(++) = flip (foldr (:))

동안

(++) = flip (foldl (:))

유형 오류가 발생합니다.


그들의 유형은 동일하며 방금 전환되었으므로 관련이 없습니다. 불쾌한 P / NP 문제 (읽기 : 무한 목록)를 제외하고 인터페이스와 결과는 동일합니다. ;) 구현으로 인한 최적화는 내가 알 수있는 한 실제로 차이가 있습니다.
Evi1M4chine

@ Evi1M4chine 이것은 올바르지 않습니다.이 예를보십시오. foldl subtract 0 [1, 2, 3, 4]평가하는 -10동안 foldr subtract 0 [1, 2, 3, 4]평가하는 -2. foldl실제로 0 - 1 - 2 - 3 - 4동안 foldr이다 4 - 3 - 2 - 1 - 0.
krapht

@krapht, foldr (-) 0 [1, 2, 3, 4]이다 -2하고 foldl (-) 0 [1, 2, 3, 4]있다 -10. 반면에, subtract(당신이 예상에서 거꾸로 subtract 10 14입니다 4그래서) foldr subtract 0 [1, 2, 3, 4]이다 -10foldl subtract 0 [1, 2, 3, 4]이다 2(긍정적).
pianoJames
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.