delete
là tìm kiếm phương thức. Nó có hai chế độ hoạt động khác nhau - cho dù nó đã được tìm thấy kết quả hay chưa. Bạn có thể sử dụng foldr
để tạo một hàm chuyển trạng thái xuống dưới dòng khi mỗi phần tử được chọn. Vì vậy, trong trường hợp của delete
, trạng thái có thể là đơn giản Bool
. Nó không phải là loại tốt nhất, nhưng nó sẽ làm.
Sau khi bạn đã xác định loại trạng thái, bạn có thể bắt đầu làm việc với việc xây dựng foldr
. Tôi sẽ đi qua tìm ra nó theo cách tôi đã làm. Tôi sẽ cho phép ScopedTypeVariables
chỉ để tôi có thể chú thích loại biểu thức phụ tốt hơn. Một bạn biết loại trạng thái, bạn biết bạn muốn foldr
để tạo một hàm lấy giá trị của loại đó và trả về giá trị của loại cuối cùng mong muốn. Đó là đủ để bắt đầu phác thảo mọi thứ.
{-# LANGUAGE ScopedTypeVariables #-}
delete :: forall a. Eq a => a -> [a] -> [a]
delete a xs = foldr f undefined xs undefined
where
f :: a -> (Bool -> [a]) -> (Bool -> [a])
f x g = undefined
Bắt đầu. Ý nghĩa chính xác của g
là một chút khó khăn ở đây. Nó thực sự là chức năng để xử lý phần còn lại của danh sách. Đó là chính xác để xem nó như là một sự tiếp nối, trên thực tế. Nó hoàn toàn đại diện cho phần còn lại của gấp, với bất kỳ trạng thái nào bạn chọn để vượt qua. Cho rằng, đó là thời gian để tìm ra những gì để đưa vào một số trong những nơi undefined
.
{-# LANGUAGE ScopedTypeVariables #-}
delete :: forall a. Eq a => a -> [a] -> [a]
delete a xs = foldr f undefined xs undefined
where
f :: a -> (Bool -> [a]) -> (Bool -> [a])
f x g found | x == a && not found = g True
| otherwise = x : g found
Điều đó có vẻ tương đối đơn giản. Nếu phần tử hiện tại là phần tử đang được tìm kiếm, và nó vẫn chưa được tìm thấy, không xuất nó, và tiếp tục với trạng thái được đặt là True
, cho biết nó đã được tìm thấy. otherwise
, xuất giá trị hiện tại và tiếp tục với trạng thái hiện tại.Điều này chỉ để lại phần còn lại của các đối số cho foldr
. Cái cuối cùng là trạng thái ban đầu. Một trong những khác là chức năng nhà nước cho một danh sách trống. Ok, những thứ đó cũng không quá tệ.
{-# LANGUAGE ScopedTypeVariables #-}
delete :: forall a. Eq a => a -> [a] -> [a]
delete a xs = foldr f (const []) xs False
where
f :: a -> (Bool -> [a]) -> (Bool -> [a])
f x g found | x == a && not found = g True
| otherwise = x : g found
Bất kể trạng thái là gì, tạo danh sách trống khi danh sách trống gặp phải. Và trạng thái ban đầu là phần tử được tìm kiếm vẫn chưa được tìm thấy.
Kỹ thuật này cũng được áp dụng trong các trường hợp khác. Ví dụ: foldl
có thể được viết dưới dạng foldr
theo cách này. Nếu bạn nhìn vào foldl
như một hàm liên tục biến đổi một bộ tích lũy ban đầu, bạn có thể đoán đó là hàm đang được tạo ra - cách chuyển đổi giá trị ban đầu.
{-# LANGUAGE ScopedTypeVariables #-}
foldl :: forall a b. (a -> b -> a) -> a -> [b] -> a
foldl f z xs = foldr g id xs z
where
g :: b -> (a -> a) -> (a -> a)
g x cont acc = undefined
Các trường hợp cơ sở không quá khó khăn để tìm thấy khi các vấn đề được định nghĩa là thao tác accumulator ban đầu, tên z
đó. Danh sách trống là chuyển đổi nhận dạng, id
và giá trị được chuyển đến hàm được tạo là z
.
Việc triển khai g
là phức tạp hơn. Nó không thể được thực hiện một cách mù quáng trên các loại, bởi vì có hai triển khai khác nhau sử dụng tất cả các giá trị mong đợi và kiểm tra kiểu. Đây là trường hợp các loại không đủ, và bạn cần phải xem xét ý nghĩa của các hàm sẵn có.
Hãy bắt đầu với khoảng không quảng cáo của các giá trị có vẻ như chúng nên được sử dụng và loại của chúng. Những thứ có vẻ như chúng phải được sử dụng trong phần thân của g
là f :: a -> b -> a
, x :: b
, cont :: (a -> a)
và acc :: a
. f
rõ ràng sẽ lấy x
làm đối số thứ hai của nó, nhưng có một câu hỏi về địa điểm thích hợp để sử dụng cont
. Để tìm ra nơi nó đi, hãy nhớ rằng nó đại diện cho hàm chuyển đổi được trả về bằng cách xử lý phần còn lại của danh sách và rằng foldl
xử lý phần tử hiện tại và sau đó chuyển kết quả của quá trình đó đến phần còn lại của danh sách.
{-# LANGUAGE ScopedTypeVariables #-}
foldl :: forall a b. (a -> b -> a) -> a -> [b] -> a
foldl f z xs = foldr g id xs z
where
g :: b -> (a -> a) -> (a -> a)
g x cont acc = cont $ f acc x
này cũng cho thấy rằng foldl'
có thể được viết theo cách này chỉ với một thay đổi nhỏ:
{-# LANGUAGE ScopedTypeVariables #-}
foldl' :: forall a b. (a -> b -> a) -> a -> [b] -> a
foldl' f z xs = foldr g id xs z
where
g :: b -> (a -> a) -> (a -> a)
g x cont acc = cont $! f acc x
Sự khác biệt là ($!)
được sử dụng để đề nghị thẩm định của f acc x
trước khi nó được truyền cho cont
. (Tôi nói "gợi ý" vì có một số trường hợp cạnh nơi ($!)
không buộc đánh giá thậm chí xa như WHNF.)
Tôi đánh giá cao câu trả lời của bạn và thấy nó hữu ích. Tôi sẽ upvote nếu tôi có đủ danh tiếng, nhưng tôi không chấp nhận được bởi vì câu trả lời của bạn không phải là khá những gì tôi đang tìm kiếm. Nhưng đó là lỗi của tôi vì không đưa ra câu hỏi chính xác hơn, tôi sẽ xem liệu tôi có thể chỉnh sửa hay không. Tôi quan tâm nhiều hơn đến cách tiếp cận sử dụng foldr để tạo ra một hàm. Bạn nói đúng rằng triển khai đầu tiên của bạn là khá nhiều những gì tôi đã thử. – user168064
@ user168064 Ok. Sau khi dành ~ 3 giờ để học chủ đề, tôi cũng có thể trả lời câu hỏi của bạn. Tôi nghĩ rằng nó xứng đáng là một câu trả lời thứ hai hơn là một bản chỉnh sửa cho câu trả lời này, vì vậy câu trả lời khác sẽ đến. – Carl