Viết gấp bằng cách sử dụng gấp


79

Trong Thế giới thực Haskell , Chương 4. về Lập trình Chức năng :

Viết gấp bằng giấy gấp:

-- file: ch04/Fold.hs
myFoldl :: (a -> b -> a) -> a -> [b] -> a

myFoldl f z xs = foldr step id xs z
    where step x g a = g (f a x)

Đoạn mã trên khiến tôi bối rối rất nhiều và ai đó gọi là dps đã viết lại nó bằng một cái tên có ý nghĩa để làm cho nó rõ ràng hơn một chút:

myFoldl stepL zeroL xs = (foldr stepR id xs) zeroL
where stepR lastL accR accInitL = accR (stepL accInitL lastL)

Một người khác, Jef G, sau đó đã làm một công việc xuất sắc bằng cách cung cấp một ví dụ và hiển thị cơ chế cơ bản từng bước:

myFoldl (+) 0 [1, 2, 3]
= (foldR step id [1, 2, 3]) 0
= (step 1 (step 2 (step 3 id))) 0
= (step 1 (step 2 (\a3 -> id ((+) a3 3)))) 0
= (step 1 (\a2 -> (\a3 -> id ((+) a3 3)) ((+) a2 2))) 0
= (\a1 -> (\a2 -> (\a3 -> id ((+) a3 3)) ((+) a2 2)) ((+) a1 1)) 0
= (\a1 -> (\a2 -> (\a3 -> (+) a3 3) ((+) a2 2)) ((+) a1 1)) 0
= (\a1 -> (\a2 -> (+) ((+) a2 2) 3) ((+) a1 1)) 0
= (\a1 -> (+) ((+) ((+) a1 1) 2) 3) 0
= (+) ((+) ((+) 0 1) 2) 3
= ((0 + 1) + 2) + 3

Nhưng tôi vẫn không thể hiểu hết điều đó, đây là câu hỏi của tôi:

  1. Hàm id để làm gì? Vai trò của là gì? Tại sao chúng ta cần nó ở đây?
  2. Trong ví dụ trên, hàm id có phải là bộ tích lũy trong hàm lambda không?
  3. Nguyên mẫu của foldr là foldr :: (a -> b -> b) -> b -> [a] -> b, và tham số đầu tiên là một hàm cần hai tham số, nhưng hàm bước trong triển khai của myFoldl sử dụng 3 tham số, tôi rất bối rối!

1
Đối với masochistic thực,step = curry $ uncurry (&) <<< (flip f) *** (.)
Weijun Zhou

Câu trả lời:


99

Một số giải thích theo thứ tự!

Hàm id để làm gì? Vai trò của là gì? Tại sao chúng ta cần nó ở đây?

idchức năng nhận diện , id x = xvà được sử dụng như tương đương với zero khi xây dựng một chuỗi các chức năng với hàm hợp , (.). Bạn có thể tìm thấy nó được định nghĩa trong Prelude .

Trong ví dụ trên, hàm id có phải là bộ tích lũy trong hàm lambda không?

Bộ tích lũy là một chức năng đang được xây dựng thông qua ứng dụng chức năng lặp lại. Không có lambda rõ ràng, vì chúng tôi đặt tên cho bộ tích lũy step,. Bạn có thể viết nó bằng lambda nếu bạn muốn:

foldl f a bs = foldr (\b g x -> g (f x b)) id bs a

Hoặc như Graham Hutton sẽ viết :

5.1 Nhà foldlđiều hành

Bây giờ chúng ta hãy khái quát hóa từ sumlví dụ và xem xét toán tử chuẩn foldlxử lý các phần tử của danh sách theo thứ tự từ trái sang phải bằng cách sử dụng một hàm fđể kết hợp các giá trị và một giá trị vlàm giá trị bắt đầu:

foldl :: (β → α → β) → β → ([α] → β)
foldl f v [ ] = v
foldl f v (x : xs) = foldl f (f v x) xs

Sử dụng toán tử này, sumlcó thể được định nghĩa lại đơn giản bằng suml = foldl (+) 0. Nhiều chức năng khác có thể được xác định một cách đơn giản bằng cách sử dụng foldl. Ví dụ, hàm tiêu chuẩn reversecó thể được định nghĩa lại bằng cách sử dụng foldlnhư sau:

reverse :: [α] → [α]
reverse = foldl (λxs x → x : xs) [ ]

Định nghĩa này hiệu quả hơn định nghĩa ban đầu của chúng tôi bằng cách sử dụng gấp, vì nó tránh sử dụng toán tử nối thêm không hiệu quả (++)cho danh sách.

Tổng quát hóa đơn giản của phép tính trong phần trước cho hàm sumlcho thấy cách xác định lại hàm foldltheo nghĩa fold:

foldl f v xs = fold (λx g → (λa → g (f a x))) id xs v

Ngược lại, không thể xác định lại foldvề mặt foldl, do thực tế foldllà chặt chẽ ở phần đuôi của đối số danh sách của nó nhưng foldkhông phải vậy. Có một số 'định lý đối ngẫu' hữu ích liên quan foldfoldlcũng như một số hướng dẫn để quyết định toán tử nào phù hợp nhất với các ứng dụng cụ thể (Bird, 1998).

Nguyên mẫu của foldr là foldr :: (a -> b -> b) -> b -> [a] -> b

Một lập trình viên Haskell sẽ nói rằng các loại của foldr(a -> b -> b) -> b -> [a] -> b.

và tham số đầu tiên là một hàm cần hai tham số, nhưng hàm bước trong triển khai của myFoldl sử dụng 3 tham số, tôi rất khó hiểu

Điều này thật khó hiểu và kỳ diệu! Chúng tôi chơi một mẹo và thay thế bộ tích lũy bằng một hàm, lần lượt được áp dụng cho giá trị ban đầu để mang lại kết quả.

Graham Hutton giải thích thủ thuật để biến foldlthành foldrtrong bài viết trên. Chúng ta bắt đầu bằng cách viết ra một định nghĩa đệ quy về foldl:

foldl :: (a -> b -> a) -> a -> [b] -> a
foldl f v []       = v
foldl f v (x : xs) = foldl f (f v x) xs

Và sau đó cấu trúc lại nó thông qua chuyển đổi đối số tĩnh trên f:

foldl :: (a -> b -> a) -> a -> [b] -> a    
foldl f v xs = g xs v
    where
        g []     v = v
        g (x:xs) v = g xs (f v x)

Bây giờ chúng ta hãy viết lại gđể làm nổi phần vbên trong:

foldl f v xs = g xs v
    where
        g []     = \v -> v
        g (x:xs) = \v -> g xs (f v x)

Điều này cũng giống như suy nghĩ về gmột hàm của một đối số, trả về một hàm:

foldl f v xs = g xs v
    where
        g []     = id
        g (x:xs) = \v -> g xs (f v x)

Bây giờ chúng ta có g, một hàm duyệt đệ quy một danh sách, áp dụng một số hàm f. Giá trị cuối cùng là hàm nhận dạng và mỗi bước cũng dẫn đến một hàm.

Nhưng , chúng tôi đã có sẵn một hàm đệ quy rất giống trên danh sách foldr,!

2 Toán tử gấp

Các foldnhà điều hành có nguồn gốc từ lý thuyết đệ quy (Kleene, 1952), trong khi việc sử dụng foldnhư một khái niệm trung tâm trong một ngày ngôn ngữ lập trình sao cho các nhà điều hành giảm APL (Iverson, 1962), và sau đó để các nhà điều hành chèn của FP (Backus , 1978). Trong Haskell, foldtoán tử cho danh sách có thể được định nghĩa như sau:

fold :: (α → β → β) → β → ([α] → β)
fold f v [ ] = v
fold f v (x : xs) = f x (fold f v xs)

Nghĩa là, cho một hàm fkiểu α → β → βvà một giá trị vkiểu β, hàm fold f vxử lý một danh sách kiểu [α]để cung cấp một giá trị kiểu βbằng cách thay thế hàm tạo nil []ở cuối danh sách bằng giá trị vvà mỗi hàm tạo (:)trong danh sách bằng chức năng f. Theo cách này, foldtoán tử đóng gói một mẫu đệ quy đơn giản để xử lý danh sách, trong đó hai hàm tạo cho danh sách được thay thế bằng các giá trị và hàm khác. Một số hàm quen thuộc trong danh sách có một định nghĩa đơn giản fold.

Điều này trông giống như một lược đồ đệ quy rất giống với ghàm của chúng ta . Bây giờ là mẹo: sử dụng tất cả phép thuật có sẵn trong tầm tay (còn gọi là Bird, Meertens và Malcolm), chúng tôi áp dụng một quy tắc đặc biệt, thuộc tính phổ quát của nếp gấp , là sự tương đương giữa hai định nghĩa cho một hàm gxử lý danh sách, được nêu như:

g [] = v
g (x:xs) = f x (g xs)

nếu và chỉ nếu

g = fold f v

Vì vậy, thuộc tính phổ quát của các nếp gấp nói rằng:

    g = foldr k v

trong đó gphải tương đương với hai phương trình, đối với một số kv:

    g []     = v
    g (x:xs) = k x (g xs)

Từ các thiết kế gấp trước đó của chúng tôi, chúng tôi biết v == id. Tuy nhiên, đối với phương trình thứ hai, chúng ta cần tính định nghĩa của k:

    g (x:xs)         = k x (g xs)        
<=> g (x:xs) v       = k x (g xs) v      -- accumulator of functions
<=> g xs (f v x)     = k x (g xs) v      -- definition of foldl
<=  g' (f v x)       = k x g' v          -- generalize (g xs) to g'
<=> k = \x g' -> (\a -> g' (f v x))      -- expand k. recursion captured in g'

Điều này, thay thế các định nghĩa được tính toán của chúng tôi về kvtạo ra định nghĩa về nếp gấp là:

foldl :: (a -> b -> a) -> a -> [b] -> a    
foldl f v xs =
    foldr
        (\x g -> (\a -> g (f v x)))
        id
        xs
        v

Đệ quy gđược thay thế bằng bộ tổ hợp gấp và bộ tích lũy trở thành một hàm được xây dựng thông qua một chuỗi các thành phần của fmỗi phần tử của danh sách, theo thứ tự ngược lại (vì vậy chúng tôi gấp sang trái thay vì phải).

Điều này chắc chắn có phần nâng cao, vì vậy để hiểu sâu sắc về sự biến đổi này, thuộc tính phổ quát của các nếp gấp , giúp cho phép biến đổi có thể thực hiện được, tôi đề xuất hướng dẫn của Hutton, được liên kết bên dưới.


Người giới thiệu


1
Làm ơn sửa lỗi đánh máy k = \x g' -> (\a -> g' (f v x)) (\x g -> (\a -> g (f v x)))
Kamel

10

Hãy xem xét loại foldr:

foldr :: (b -> a -> a) -> a -> [b] -> a

Trong khi loại steplà một cái gì đó giống như b -> (a -> a) -> a -> a. Vì bước đang được chuyển qua foldr, chúng ta có thể kết luận rằng trong trường hợp này, nếp gấp có một kiểu như thế nào (b -> (a -> a) -> (a -> a)) -> (a -> a) -> [b] -> (a -> a).

Đừng nhầm lẫn bởi các ý nghĩa khác nhau của acác chữ ký khác nhau; nó chỉ là một biến kiểu. Ngoài ra, hãy nhớ rằng mũi tên hàm là liên kết đúng, a -> b -> cđiều tương tự như vậy a -> (b -> c).

Vì vậy, có, giá trị tích lũy cho foldrlà một hàm của kiểu a -> avà giá trị ban đầu là id. Điều này có ý nghĩa gì đó, bởi vì đây idlà một hàm không thực hiện bất cứ điều gì - đó cũng là lý do bạn bắt đầu bằng 0 làm giá trị ban đầu khi thêm tất cả các giá trị trong danh sách.

Đối với stepviệc lấy ba đối số, hãy thử viết lại như thế này:

step :: b -> (a -> a) -> (a -> a)
step x g = \a -> g (f a x)

Điều đó có giúp bạn dễ dàng xem những gì đang diễn ra hơn không? Nó nhận thêm một tham số vì nó trả về một hàm và hai cách viết nó tương đương nhau. Cũng lưu ý các tham số phụ sau foldr: (foldr step id xs) z. Phần trong dấu ngoặc đơn là chính phần gấp, trả về một hàm, sau đó được áp dụng cho z.


6

(lướt nhanh qua các câu trả lời của tôi [1] , [2] , [3] , [4] để đảm bảo bạn hiểu cú pháp của Haskell, các hàm bậc cao hơn, currying, thành phần hàm, $ operator, các toán tử infix / prefix, các phần và lambdas )

Thuộc tính chung của nếp gấp

Một nếp gấp chỉ là một mã hóa của một số loại đệ quy nhất định. Và thuộc tính phổ quát chỉ đơn giản nói rằng, nếu đệ quy của bạn phù hợp với một dạng nhất định, nó có thể được chuyển thành dạng gấp theo một số quy tắc chính thức. Và ngược lại, mọi nếp gấp có thể được chuyển đổi thành một đệ quy của loại đó. Một lần nữa, một số đệ quy có thể được dịch thành các nếp gấp cung cấp chính xác cùng một câu trả lời, và một số đệ quy thì không, và có một quy trình chính xác để thực hiện điều đó.

Về cơ bản, nếu hàm đệ quy của bạn hoạt động trên các danh sách giống như bên trái , bạn có thể biến đổi nó để gấp một bên phải , thay thế fvcho những gì thực sự ở đó.

g []     = v              ⇒
g (x:xs) = f x (g xs)     ⇒     g = foldr f v

Ví dụ:

sum []     = 0   {- recursion becomes fold -}
sum (x:xs) = x + sum xs   ⇒     sum = foldr 0 (+)

Đây v = 0sum (x:xs) = x + sum xstương đương với sum (x:xs) = (+) x (sum xs), do đó f = (+). 2 ví dụ khác

product []     = 1
product (x:xs) = x * product xs  ⇒  product = foldr 1 (*)

length []     = 0
length (x:xs) = 1 + length xs    ⇒  length = foldr (\_ a -> 1 + a) 0

Tập thể dục:

  1. Thực hiện map, filter, reverse, concatconcatMapđệ quy, giống như các chức năng trên trên trái bên.

  2. Chuyển 5 hàm này thành hàm gấp theo một công thức ở trên , nghĩa là thay thế fvtrong công thức gấp ở bên phải .

Gấp qua màn hình gấp

Làm thế nào để viết một hàm đệ quy tính tổng các số từ trái sang phải?

sum [] = 0     -- given `sum [1,2,3]` expands into `(1 + (2 + 3))`
sum (x:xs) = x + sum xs

Hàm đệ quy đầu tiên tìm thấy mở rộng đầy đủ trước khi bắt đầu cộng lại, đó không phải là những gì chúng ta cần. Một cách tiếp cận là tạo một hàm đệ quy có bộ tích lũy , ngay lập tức cộng các số trên mỗi bước (đọc về đệ quy đuôi để tìm hiểu thêm về các chiến lược đệ quy):

suml :: [a] -> a
suml xs = suml' xs 0
  where suml' [] n = n   -- auxiliary function
        suml' (x:xs) n = suml' xs (n+x)

Được rồi, dừng lại! Chạy mã này trong GHCi và đảm bảo rằng bạn hiểu cách nó hoạt động, sau đó tiến hành cẩn thận và chu đáo. sumlkhông thể được xác định lại bằng một nếp gấp, nhưng suml'có thể được.

suml' []       = v    -- equivalent: v n = n
suml' (x:xs) n = f x (suml' xs) n

suml' [] n = ntừ định nghĩa hàm, phải không? Và v = suml' []từ công thức tính chất phổ quát. Cùng điều này mang lại v n = n, một chức năng mà ngay lập tức trở lại bất cứ điều gì nó nhận được: v = id. Hãy tính toán f:

suml' (x:xs) n = f x (suml' xs) n
-- expand suml' definition
suml' xs (n+x) = f x (suml' xs) n
-- replace `suml' xs` with `g`
g (n+x)        = f x g n

Do đó, suml' = foldr (\x g n -> g (n+x)) idvà, do đó suml = foldr (\x g n -> g (n+x)) id xs 0,.

foldr (\x g n -> g (n + x)) id [1..10] 0 -- return 55

Bây giờ chúng ta chỉ cần tổng quát hóa, thay thế +bằng một hàm biến:

foldl f a xs = foldr (\x g n -> g (n `f` x)) id xs a
foldl (-) 10 [1..5] -- returns -5

Phần kết luận

Bây giờ hãy đọc Hướng dẫn của Graham Hutton về tính phổ biến và tính biểu cảm của nếp gấp . Lấy một ít bút và giấy, cố gắng hình dung mọi thứ mà anh ấy viết cho đến khi bạn tự mình vẽ được gần hết các nếp gấp. Đừng đổ mồ hôi nếu bạn không hiểu điều gì đó, bạn luôn có thể quay lại sau, nhưng cũng đừng trì hoãn nhiều.


Tôi thấy câu trả lời này đơn giản và rõ ràng hơn câu được chấp nhận. Quá xấu nó có quá ít phiếu lên ...
gigabyte

5

Đây là bằng chứng của tôi foldlcó thể được diễn đạt về mặt foldr, mà tôi thấy khá đơn giản ngoài cái tên spaghetti mà stephàm giới thiệu.

Mệnh đề foldl f z xstương đương với

myfoldl f z xs = foldr step_f id xs z
        where step_f x g a = g (f a x)

Điều quan trọng đầu tiên cần lưu ý ở đây là phía bên phải của dòng đầu tiên thực sự được đánh giá là

(foldr step_f id xs) z

foldrchỉ nhận ba tham số. Điều này đã gợi ý rằng foldrsẽ không tính toán một giá trị mà là một hàm curried, sau đó được áp dụng cho z. Có hai trường hợp cần điều tra để tìm hiểu xem có phải myfoldlfoldl:

  1. Trường hợp cơ sở: danh sách trống

      myfoldl f z []
    = foldr step_f id [] z    (by definition of myfoldl)
    = id z                    (by definition of foldr)
    = z
    
      foldl f z []
    = z                       (by definition of foldl)
    
  2. Danh sách không trống

      myfoldl f z (x:xs)
    = foldr step_f id (x:xs) z          (by definition of myfoldl)
    = step_f x (foldr step_f id xs) z   (-> apply step_f)
    = (foldr step_f id xs) (f z x)      (-> remove parentheses)
    = foldr step_f id xs (f z x)
    = myfoldl f (f z x) xs              (definition of myfoldl)
    
      foldl f z (x:xs)
    = foldl f (f z x) xs
    

Vì trong 2. dòng đầu tiên và dòng cuối cùng có cùng dạng trong cả hai trường hợp, nó có thể được sử dụng để gấp danh sách xuống cho đến khi xs == [], trong trường hợp đó 1. đảm bảo cùng một kết quả. Vì vậy, bằng cách cảm ứng , myfoldl == foldl.


2

Trước khi phản đối, vui lòng đọc đoạn sau

Tôi đăng câu trả lời cho những người có thể thấy cách tiếp cận này phù hợp hơn với cách suy nghĩ của họ. Câu trả lời có thể chứa thông tin và suy nghĩ thừa, nhưng đó là thứ tôi cần để giải quyết vấn đề. Hơn nữa, vì đây là một câu trả lời khác cho cùng một câu hỏi, rõ ràng là nó có sự trùng lặp đáng kể với các câu trả lời khác, tuy nhiên nó cho biết câu chuyện về cách tôi có thể nắm bắt được khái niệm này.

Thật vậy, tôi bắt đầu viết ra ghi chú này như một ghi chép cá nhân về những suy nghĩ của tôi trong khi cố gắng hiểu chủ đề này. Tôi đã mất cả ngày để chạm vào cốt lõi của nó, nếu tôi thực sự hiểu được nó.

Con đường dài của tôi để hiểu bài tập đơn giản này

Phần dễ: chúng ta cần xác định điều gì?

Điều gì xảy ra với cuộc gọi ví dụ sau

foldl f z [1,2,3,4]

có thể được hình dung bằng sơ đồ sau (có trên Wikipedia , nhưng lần đầu tiên tôi nhìn thấy nó trên một câu trả lời khác ):

          _____results in a number
         /
        f          f (f (f (f z 1) 2) 3) 4
       / \
      f   4        f (f (f z 1) 2) 3
     / \
    f   3          f (f z 1) 2
   / \
  f   2            f z 1
 / \
z   1

(Lưu ý thêm, khi sử dụng foldlmỗi ứng dụng của fkhông được thực hiện và các biểu thức được xử lý giống như cách tôi đã viết ở trên; về nguyên tắc, chúng có thể được tính toán khi bạn đi từ dưới lên trên và đó chính xác là những gì foldl'đang làm.)

Bài tập về cơ bản thách thức chúng ta sử dụng foldrthay vì foldlbằng cách thay đổi một cách thích hợp hàm bước (vì vậy chúng ta sử dụng sthay vì f) và bộ tích lũy ban đầu (vì vậy chúng ta sử dụng ?thay vì z); danh sách vẫn giữ nguyên, nếu không thì chúng ta đang nói về điều gì?

Lời kêu gọi phải foldrtrông như thế này:

foldr s ? [1,2,3,4]

và sơ đồ tương ứng là:

    _____what does the last call return?
   /
  s
 / \
1   s
   / \
  2   s
     / \
    3   s
       / \
      4   ? <--- what is the initial accumulator?

Kết quả cuộc gọi là

s 1 (s 2 (s 3 (s 4 ?)))

Là gì svà là ?gì? Và chúng có những loại nào? Có vẻ như sđó là một hàm hai đối số, rất giống f, nhưng chúng ta đừng vội đi đến kết luận. Ngoài ra, chúng ta hãy để ?sang một bên một chút, và hãy quan sát điều đó zphải phát huy tác dụng ngay khi 1phát huy tác dụng; tuy nhiên, làm thế nào để có thể zphát huy tác dụng trong lệnh gọi hàm có thể có hai đối số s, cụ thể là trong lệnh gọi s 1 (…)? Chúng ta có thể giải quyết phần bí ẩn này bằng cách chọn một hàm schứa 3 đối số, thay vì 2 đối số mà chúng ta đã đề cập trước đó, để lời gọi ngoài cùng s 1 (…)sẽ dẫn đến một hàm nhận một đối số, mà chúng ta có thể chuyển ztới!

Điều này có nghĩa là chúng tôi muốn cuộc gọi ban đầu, mở rộng thành

f (f (f (f z 1) 2) 3) 4

tương đương với

s 1 (s 2 (s 3 (s 4 ?))) z

hay nói cách khác, chúng tôi muốn hàm được áp dụng một phần

s 1 (s 2 (s 3 (s 4 ?)))

tương đương với hàm lambda sau

(\z -> f (f (f (f z 1) 2) 3) 4)

Một lần nữa, những mảnh "duy nhất" chúng ta cần là s?.

Bước ngoặt: nhận ra thành phần chức năng

Hãy vẽ lại sơ đồ trước và viết bên phải những gì chúng ta muốn mỗi lệnh gọi stương đương với:

  s          s 1 (…) == (\z -> f (f (f (f z 1) 2) 3) 4)
 / \
1   s        s 2 (…) == (\z -> f (f (f    z    2) 3) 4)
   / \
  2   s      s 3 (…) == (\z -> f (f       z       3) 4)
     / \
    3   s    s 4  ?  == (\z -> f          z          4)
       / \
      4   ? <--- what is the initial accumulator?

Tôi hy vọng rõ ràng từ cấu trúc của sơ đồ rằng (…)trên mỗi dòng là phía bên phải của dòng bên dưới nó; tốt hơn, nó là hàm được trả về từ lệnh gọi trước đó (bên dưới) tới s.

Cũng cần phải rõ ràng rằng một lệnh gọi đến svới các đối số xylà ứng dụng (đầy đủ) của yứng dụng một phần của fđối số duy nhất x. Vì ứng dụng một phần của fto xcó thể được viết dưới dạng lambda (\z -> f z x), việc áp dụng đầy đủ ycho nó sẽ dẫn đến lambda (\z -> y (f z x)), trong trường hợp này tôi sẽ viết lại thành y . (\z -> f z x); dịch các từ thành một biểu thức để schúng tôi nhận được

s x y = y . (\z -> f z x)

(Điều này giống với s x y z = y (f z x), giống với sách, nếu bạn đổi tên các biến.)

Bit cuối cùng là: "giá trị" ban đầu? của bộ tích lũy là bao nhiêu? Sơ đồ trên có thể được viết lại bằng cách mở rộng các lệnh gọi lồng nhau để biến chúng thành chuỗi thành phần:

  s          s 1 (…) == (\z -> f z 4) . (\z -> f z 3) . (\z -> f z 2) . (\z -> f z 1)
 / \
1   s        s 2 (…) == (\z -> f z 4) . (\z -> f z 3) . (\z -> f z 2)
   / \
  2   s      s 3 (…) == (\z -> f z 4) . (\z -> f z 3)
     / \
    3   s    s 4  ?  == (\z -> f z 4)
       / \
      4   ? <--- what is the initial accumulator?

Chúng tôi ở đây thấy rằng sđơn giản là "cọc lên" các ứng dụng phần kế tiếp của f, nhưng ytrong s x y = y . (\z -> f z x)gợi ý rằng việc giải thích s 4 ?(và ngược lại, tất cả những người khác) bỏ lỡ một chức năng hàng đầu được sáng tác với lambda tận cùng bên trái.

Đó chỉ là ?chức năng của chúng tôi : đã đến lúc cho nó lý do tồn tại, bên cạnh việc chiếm một vị trí trong lời kêu gọi foldr. Chúng ta có thể chọn nó là gì, để không thay đổi các chức năng kết quả? Trả lời: id, các yếu tố bản sắc đối với các nhà điều hành thành phần với (.).

  s          s 1 (…) == id . (\z -> f z 4) . (\z -> f z 3) . (\z -> f z 2) . (\z -> f z 1)
 / \
1   s        s 2 (…) == id . (\z -> f z 4) . (\z -> f z 3) . (\z -> f z 2)
   / \
  2   s      s 3 (…) == id . (\z -> f z 4) . (\z -> f z 3)
     / \
    3   s    s 4 id  == id . (\z -> f z 4)
       / \
      4   id

Vì vậy, hàm được tìm kiếm là

myFoldl f z xs = foldr (\x g a -> g (f a x)) id xs z

1

Không có con đường Hoàng gia đến Toán học, thậm chí không qua Haskell. Để cho

h z = (foldr step id xs) z where   
     step x g =  \a -> g (f a x)

Cái quái h zgì vậy? Giả sử rằng xs = [x0, x1, x2].
Áp dụng định nghĩa của foldr:

h z = (step x0 (step x1 (step x2 id))) z 

Áp dụng định nghĩa của bước:

= (\a0 -> (\a1 -> (\a2 -> id (f a2 x2)) (f a1 x1)) (f a0 x0)) z

Thay thế vào các hàm lambda:

= (\a1 -> (\a2 -> id (f a2 x2)) (f a1 x1)) (f z x0)

= (\a2 -> id (f a2 x2)) (f (f z x0) x1)

= id (f (f (f z x0) x1) x2)

Áp dụng định nghĩa của id:

= f (f (f z x0) x1) x2

Áp dụng định nghĩa của nếp gấp:

= foldl f z [x0, x1, x2]

Nó là một con đường Hoàng gia hay là gì?


1

Điều này có thể hữu ích, tôi đã thử mở rộng theo một cách khác.

myFoldl (+) 0 [1,2,3] = 
foldr step id [1,2,3] 0 = 
foldr step (\a -> id (a+3)) [1,2] 0 = 
foldr step (\b -> (\a -> id (a+3)) (b+2)) [1] 0 = 
foldr step (\b -> id ((b+2)+3)) [1] 0 = 
foldr step (\c -> (\b -> id ((b+2)+3)) (c+1)) [] 0 = 
foldr step (\c -> id (((c+1)+2)+3)) [] 0 = 
(\c -> id (((c+1)+2)+3)) 0 = ...

1
foldr step zero (x:xs) = step x (foldr step zero xs)
foldr _ zero []        = zero

myFold f z xs = foldr step id xs z
  where step x g a = g (f a x)

myFold (+) 0 [1, 2, 3] =
  foldr step id [1, 2, 3] 0
  -- Expanding foldr function
  step 1 (foldr step id [2, 3]) 0
  step 1 (step 2 (foldr step id [3])) 0
  step 1 (step 2 (step 3 (foldr step id []))) 0
  -- Expanding step function if it is possible
  step 1 (step 2 (step 3 id)) 0
  step 2 (step 3 id) (0 + 1)
  step 3 id ((0 + 1) + 2)
  id (((0 + 1) + 2) + 3)

Chà, ít nhất, điều này đã giúp tôi. Thậm chí nó không hoàn toàn đúng.


trình tự thực tế là foldr step id [1, 2, 3] 0-> step 1 (foldr step id [2, 3]) 0-> (foldr step id [2, 3]) (0 + 1)-> step 2 (foldr step id [3]) (0 + 1)-> (foldr step id [3]) ((0 + 1) + 2)-> step 3 (foldr step id []) ((0 + 1) + 2)-> (foldr step id []) (((0 + 1) + 2) + 3)-> id (((0 + 1) + 2) + 3).
Will Ness

0

Câu trả lời này làm cho định nghĩa bên dưới dễ dàng hiểu trong ba bước.

-- file: ch04/Fold.hs
myFoldl :: (a -> b -> a) -> a -> [b] -> a

myFoldl f z xs = foldr step id xs z
    where step x g a = g (f a x)

Bước 1. chuyển đổi lần đánh giá hàm thành tổ hợp hàm

foldl f z [x1 .. xn] = z & f1 & .. & fn = fn . .. . f1 z. trong đó fi = \z -> f z xi.

(Bằng cách sử dụng z & f1 & f2 & .. & fnnó có nghĩa là fn ( .. (f2 (f1 z)) .. ).)

Bước 2. thể hiện tổ hợp chức năng foldrtheo cách

foldr (.) id [f1 .. fn] = (.) f1 (foldr (.) id [f2 .. fn]) = f1 . (foldr (.) id [f2 .. fn]). Mở phần còn lại để lấy foldr (.) id [f1 .. fn] = f1 . .. . fn.

Nhận thấy rằng dãy số bị đảo ngược, chúng ta nên sử dụng dạng đảo ngược của (.). Xác định rc f1 f2 = (.) f2 f1 = f2 . f1, sau đó foldr rc id [f1 .. fn] = rc f1 (foldr (.) id [f2 .. fn]) = (foldr (.) id [f2 .. fn]) . f1. Mở phần còn lại để lấy foldr rc id [f1 .. fn] = fn . .. . f1.

Bước 3. Biến đổi nếp gấp trên danh sách hàm thành nếp gấp trên danh sách toán hạng

Tìm stepđiều đó làm cho foldr step id [x1 .. xn] = foldr rc id [f1 .. fn]. Nó rất dễ dàng để tìm thấy step = \x g z -> g (f z x).

Trong 3 bước, định nghĩa về foldlviệc sử dụng foldrrất rõ ràng:

  foldl f z xs
= fn . .. . f1 z
= foldr rc id fs z
= foldr step id xs z

Chứng minh tính đúng đắn:

foldl f z xs = foldr (\x g z -> g (f z x)) id xs z
             = step x1 (foldr step id [x2 .. xn]) z
             = s1 (foldr step id [x2 .. xn]) z
             = s1 (step x2 (foldr step id [x3 .. xn])) z
             = s1 (s2 (foldr step id [x3 .. xn])) z
             = ..
             = s1 (s2 (.. (sn (foldr step id [])) .. )) z
             = s1 (s2 (.. (sn id) .. )) z
             = (s2 (.. (sn id) .. )) (f z x1)
             = s2 (s3 (.. (sn id) .. )) (f z x1)
             = (s3 (.. (sn id) .. )) (f (f z x1) x2)
             = ..
             = sn id (f (.. (f (f z x1) x2) .. ) xn-1)
             = id (f (.. (f (f z x1) x2) .. ) xn)
             = f (.. (f (f z x1) x2) .. ) xn

in which xs = [x1 .. xn], si = step xi = \g z -> g (f z xi)

Nếu bạn thấy có gì chưa rõ, vui lòng thêm vào bình luận. :)

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.