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?
id
là chức năng nhận diện , id x = x
và đượ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ừ suml
ví dụ và xem xét toán tử chuẩn foldl
xử 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ị v
là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, suml
có 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 reverse
có thể được định nghĩa lại bằng cách sử dụng foldl
như 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 suml
cho thấy cách xác định lại hàm foldl
theo 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 fold
về mặt foldl
, do thực tế
foldl
là chặt chẽ ở phần đuôi của đối số danh sách của nó nhưng fold
không phải vậy. Có một số 'định lý đối ngẫu' hữu ích liên quan fold
và foldl
cũ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
là (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 foldl
thành foldr
trong 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 v
bê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ề g
mộ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 fold
nhà điều hành có nguồn gốc từ lý thuyết đệ quy (Kleene, 1952), trong khi việc sử dụng fold
như 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, fold
toá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 f
kiểu α → β → β
và một giá trị v
kiểu β
, hàm
fold f v
xử 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ị v
và mỗi hàm tạo (:)
trong danh sách bằng chức năng f
. Theo cách này, fold
toá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 g
hà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 g
xử 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 đó g
phải tương đương với hai phương trình, đối với một số k
và v
:
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
<=> g xs (f v x) = k x (g xs) v
<= g' (f v x) = k x g' v
<=> k = \x g' -> (\a -> g' (f v x))
Điều này, thay thế các định nghĩa được tính toán của chúng tôi về k
và v
tạ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 f
mỗ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
step = curry $ uncurry (&) <<< (flip f) *** (.)