2009-05-08 28 views
23

Lớp bộ lọc của hàm có điều kiện (a -> Bool) và áp dụng nó khi lọc.Làm thế nào để bạn kết hợp các điều kiện lọc

Cách tốt nhất để sử dụng bộ lọc khi bạn có nhiều điều kiện là gì?

Sử dụng hàm ứng dụng liftA2 thay vì nângM2 vì tôi vì một lý do nào đó không hiểu cách liftM2 hoạt động trong mã thuần túy.

Trả lời

29

Các liftM2 combinator có thể được sử dụng trong Reader monad để làm điều này theo cách 'chức năng hơn':

import Control.Monad 
import Control.Monad.Reader 

-- .... 

filter (liftM2 (&&) odd (> 100)) [1..200] 

Lưu ý rằng nhập khẩu là quan trọng; Control.Monad.Reader cung cấp cá thể Monad (e ->) mà làm cho tất cả các công việc này.

Lý do hoạt động này là đơn vị đọc chỉ là (e ->) đối với một số môi trường e. Do đó, một biến vị ngữ boolean là một hàm đơn thuần 0-ary trả về bool trong một môi trường tương ứng với đối số của nó. Sau đó chúng tôi có thể sử dụng liftM2 để phân phối môi trường trên hai vị từ như vậy.

Hoặc, trong điều kiện đơn giản, liftM2 sẽ đóng vai trò một chút như thế này khi các loại hoạt động ra:

liftM2 f g h a = f (g a) (h a) 

Bạn cũng có thể định nghĩa một combinator mới nếu bạn muốn để có thể chuỗi những cách dễ dàng, và/hoặc không muốn gây rối với liftM2:

(.&&.) :: (a -> Bool) -> (a -> Bool) -> (a -> Bool) 
(.&&.) f g a = (f a) && (g a) 
-- or, in points-free style: 
(.&&.) = liftM2 (&&)  

filter (odd .&&. (> 5) .&&. (< 20)) [1..100] 
+3

Cả hai ví dụ làm việc trên GHC 7.6.3 thậm chí nếu bạn không nhập 'Control.Monad.Reader'. – sjakobi

+2

Tôi 'liftM2' có thể (ngày nay) được thay thế như thế này:' filter ((&&) <$> lẻ <*> (> 100)) [1.200] '. Đó là như nhau, nhưng đẹp hơn. :) Nó cũng chỉ yêu cầu 'Control.Applicative', và không có monads đầy đủ. … Mặc dù tôi vẫn tự hỏi toán tử nào cho phép ANDING nhiều hơn hai hàm Boolean… – Evi1M4chine

15

Vâng, bạn có thể kết hợp chức năng tuy nhiên bạn muốn trong Haskell (miễn là các loại là chính xác) và sử dụng lambdas bạn thậm chí không phải đặt tên chức năng vị ngữ của bạn, ví dụ:

filter (\x -> odd x && x > 100) [1..200] 
9

Hãy nói rằng điều kiện của bạn được lưu trữ trong một danh sách gọi là conditions. Danh sách này có loại [a -> Bool].

Để áp dụng tất cả các điều kiện để một giá trị x, bạn có thể sử dụng map:

map ($ x) conditions 

này áp dụng mỗi điều kiện để x và trả về một danh sách các Bool. Để giảm danh sách này vào một boolean đơn, True nếu tất cả các yếu tố này là True, và False nếu không, bạn có thể sử dụng chức năng and:

and $ map ($ x) conditions 

Bây giờ bạn có một chức năng kết hợp tất cả các điều kiện. Hãy cung cấp cho nó một cái tên:

combined_condition x = and $ map ($ x) conditions 

Chức năng này có kiểu a -> Bool, vì vậy chúng ta có thể sử dụng nó trong một cuộc gọi đến filter:

filter combined_condition [1..10] 
+2

Cẩn thận với ($ x) trái ngược với ($ x), như thể bạn bật Mẫu Haskell vì một số lý do khác $ x sẽ đột nhiên trông giống như một mối nối . –

+8

Bạn đã phát hiện hàm 'all':' bộ lọc (tất cả các điều kiện) [1..10] ' –

+1

Cũng có chức năng bất kỳ, tùy thuộc vào cách bạn muốn kết hợp các biến vị ngữ: bất kỳ p = hoặc. bản đồ; tất cả p = và. bản đồ; –

2

Nếu bạn có một danh sách các chức năng lọc của loại a -> Bool và muốn kết hợp chúng thành một hàm lọc ngắn gọn cùng loại, chúng ta có thể viết các hàm để thực hiện. Chức năng nào dưới đây mà bạn sử dụng sẽ phụ thuộc vào hành vi lọc mà bạn cần.

anyfilt :: [(a -> Bool)] -> (a -> Bool) 
anyfilt fns = \el -> any (\fn -> fn el) fns 

allfilt :: [(a -> Bool)] -> (a -> Bool) 
allfilt fns = \el -> all (\fn -> fn el) fns 

anyfilt sẽ trở lại đúng nếu bất kỳ của các chức năng lọc trả về true và sai nếu tất cả các chức năng lọc trả về false. allfilt sẽ trả về true nếu tất cả các hàm lọc trả về true và sai nếu bất kỳ hàm lọc nào trả về false. Lưu ý rằng bạn không thể giảm either hoặc chức năng vì các tham chiếu đến fns trên RHS nằm trong các chức năng ẩn danh.

Sử dụng nó như thế này:

filterLines :: [String] -> [String] 
filterLines = let 
    isComment = isPrefixOf "# " 
    isBlank = (==) "" 
    badLine = anyfilt([isComment, isBlank]) 
    in filter (not . badLine) 

main = mapM_ putStrLn $ filterLines ["# comment", "", "true line"] 
--> "true line" 
Các vấn đề liên quan