Có một thành ngữ Haskell để thử một số chức năng và dừng lại ngay khi một thành công?


9

Trong Haskell, tôi có thể sử dụng kiểu a -> Maybe bđể mô hình hóa một hàm trả về giá trị của kiểu bhoặc không trả về gì (nó không thành công).

Nếu tôi có các loại a1, ..., a(n+1)và chức năng f1, ..., fn, với fi :: ai -> Maybe a(i+1)cho tất cả i, 1 <= i <= ntôi có thể chuỗi các chức năng bằng cách sử dụng các >>=nhà điều hành của các Maybeđơn nguyên, ghi:

f1 x >>= f2 >>= f3 >>=... >>= fn

Các >>=Đảm bảo hành mà mỗi chức năng được áp dụng càng lâu càng tiền nhiệm của nó đã trở lại một giá trị có ý nghĩa. Ngay khi một chức năng trong chuỗi thất bại, toàn bộ chuỗi thất bại (trả về Nothing) và các chức năng tiếp theo trong chuỗi không được đánh giá.

Tôi có một mô hình hơi giống nhau trong đó tôi muốn thử một số chức năng trên cùng một đầu vào và quay lại ngay khi một chức năng thành công . Nếu tất cả các hàm thất bại (trả về Nothing), toàn bộ tính toán sẽ thất bại. Chính xác hơn, tôi có chức năng f1, ..., fn :: a -> Maybe bvà tôi xác định chức năng

tryFunctions :: [a -> Maybe b] -> a -> Maybe b
tryFunctions []       _ = Nothing
tryFunctions (f : fs) x = case f x of
                            Nothing    -> tryFunctions fs x
                            r@(Just _) -> r

Theo một nghĩa nào đó, điều này là kép đối với Maybeđơn nguyên ở chỗ một tính toán dừng lại ở thành công đầu tiên thay vì ở thất bại đầu tiên.

Tất nhiên, tôi có thể sử dụng chức năng mà tôi đã viết ở trên nhưng tôi đã tự hỏi liệu có cách nào tốt hơn, được thiết lập tốt và thành ngữ để thể hiện mẫu này trong Haskell không.


Không phải Haskell, nhưng trong C #, thỉnh thoảng bạn sẽ thấy toán tử hợp nhất null (??) được sử dụng như thế:return f1 ?? f2 ?? f3 ?? DefaultValue;
Telastyn

4
Đúng vậy - đây là Alternativetoán tử infix biểu tượng <|>và được định nghĩa theo thuật ngữ Monoid
Jimmy Hoffa

Câu trả lời:


8

Cho một tập đóng (số phần tử cố định) Svới các phần tử {a..z}và toán tử nhị phân *:

Có một yếu tố nhận dạng duy nhất isao cho:

forall x in S: i * x = x = x * i

Toán tử là kết hợp sao cho:

forall a, b, c in S: a * (b * c) = (a * b) * c

Bạn có một monoid.

Bây giờ được cung cấp bất kỳ monoid nào bạn có thể định nghĩa một hàm nhị phân flà:

f(i, x) = x
f(x, _) = x

Điều này có nghĩa là đối với ví dụ về Maybemonoid ( Nothinglà phần tử nhận dạng được ký hiệu ở trên là i):

f(Nothing, Just 5) = Just 5
f(Just 5, Nothing) = Just 5
f(Just 5, Just 10) = Just 5
f(Nothing, f(Nothing, Just 5)) = Just 5
f(Nothing, f(Just 5, Nothing)) = Just 5

Đáng ngạc nhiên, tôi không thể tìm thấy chức năng chính xác này trong các thư viện mặc định, có thể là do thiếu kinh nghiệm của riêng tôi. Nếu bất cứ ai khác có thể tình nguyện này, tôi sẽ chân thành đánh giá cao nó.

Đây là cách thực hiện mà tôi đã rút ra từ ví dụ trên:

(<||>) :: (Monoid a, Eq a) => a -> a -> a
x <||> y
     | x == mempty = y
     | True = x

Thí dụ:

λ> [] <||> [1,2] <||> [3,4]
[1,2]
λ> Just "foo" <||> Nothing <||> Just "bar"
Just "foo"
λ> Nothing <||> Just "foo" <||> Just "bar"
Just "foo"
λ> 

Sau đó, nếu bạn muốn sử dụng danh sách các chức năng làm đầu vào ...

tryFunctions x funcs = foldl1 (<||>) $ map ($ x) funcs

thí dụ:

instance Monoid Bool where
         mempty = False
         mconcat = or
         mappend = (||)

λ> tryFunctions 8 [odd, even]
True
λ> tryFunctions 8 [odd, odd]
False
λ> tryFunctions 8 [odd, odd, even]
True
λ> 

Tôi không hiểu tại sao <|>đối xử với danh tính của một monoid theo một cách đặc biệt. Không thể chọn một yếu tố tùy ý của một tập hợp tùy ý để đóng vai trò đặc biệt đó? Tại sao bộ phải là một monoid và phần tử đặc biệt ghi <|>nhận dạng của nó?
Giorgio

1
@Giorgio có lẽ đó là lý do tại sao <|>không dựa vào monoid và tôi có tất cả những điều này lẫn lộn? Nó dựa vào Alternativetypeclass. Chắc chắn - Tôi đang xem câu trả lời của chính mình và nhận ra điều đó không hoàn toàn đúng vì [1,2] <|> [3]không mong đợi [1,2,3]nên mọi thứ về việc sử dụng lớp loại đơn để xác định danh tính là đúng - và chìa khóa khác là sự kết hợp cần thiết để có được hành vi mong đợi , có lẽ Alternativekhông đưa ra hành vi mà tôi nghĩ ra tay ...
Jimmy Hoffa

@JimmyHoffa nó sẽ phụ thuộc vào kiểu chữ Có lẽ <|> sẽ khác với Danh sách <|> không?
jk.

@Giorgio nhìn vào 2 ví dụ cuối cùng của tôi fđể xem tại sao sự kết hợp là cần thiết.
Jimmy Hoffa

tức là monoid cho danh sách là danh sách trống và concat
jk.

5
import Data.Monoid

tryFunctions :: a -> [a -> Maybe b] -> Maybe b
tryFunctions x = getFirst . mconcat . map (First . ($ x))

Điều này sạch sẽ và đơn giản nhưng cố định với Maybe... Giải pháp của tôi bị hạn chế bởi Eq, bằng cách nào đó tôi cảm thấy như cả hai chúng ta đều thiếu một thứ gì đó có sẵn bằng cách Monoidnói chung ...
Jimmy Hoffa

@JimmyHoffa: Bạn có nghĩa là bạn muốn khái quát hóa giải pháp này để nó có thể hoạt động với các loại dữ liệu khác, ví dụ either?
Giorgio

@Giorgio chính xác. Tôi ước rằng ràng buộc duy nhất có thể là Monoidbất cứ thứ gì có thành phần nhận dạng đều có thể có một bộ gồm 2 hàm (tôi nghĩ đây là một loại trường nhị phân), trong đó một trong các hàm chọn danh tính trên tất cả các hàm khác và hàm kia luôn chọn yếu tố phi bản sắc. Tôi chỉ không biết làm thế nào để làm điều này mà không cần Eqbiết giá trị nào là identityhay không .. rõ ràng là trong các đơn thức cộng gộp hoặc nhân, bạn có chức năng bậc thang theo mặc định (luôn chọn các phần tử không nhận dạng bằng hàm nhị phân đơn sắc)
Jimmy Hoffa

foldMapcó thể được sử dụng thay chomconcat . map
4 Castle

4

Điều này nghe có vẻ giống như thay thế thất bại bằng một danh sách thành công

Bạn đang nói về Maybe achứ không phải [a], nhưng trên thực tế chúng rất giống nhau: chúng ta có thể nghĩ Maybe agiống như thế [a], ngoại trừ nó có thể chứa nhiều nhất một yếu tố (ví dụ: Nothing ~= []Just x ~= [x]).

Trong trường hợp danh sách, bạn tryFunctionssẽ rất đơn giản: áp dụng tất cả các hàm cho đối số đã cho sau đó nối tất cả các kết quả lại với nhau. concatMapsẽ làm điều này độc đáo:

tryFunctions :: [a -> [b]] -> a -> [b]
tryFunctions fs x = concatMap ($ x) fs

Theo cách này, chúng ta có thể thấy rằng <|>toán tử cho Maybecác hành động như ghép nối cho 'danh sách có nhiều nhất một phần tử'.


1

Theo tinh thần của Conal, chia nó thành các hoạt động đơn giản nhỏ hơn.

Trong trường hợp này, asumtừ Data.Foldablephần chính.

tryFunction fs x = asum (map ($ x) fs)

Ngoài ra, câu trả lời của a la Hoffa bạn có thể sử dụng Monoid ví dụ (->)nhưng sau đó bạn cần một Monoidví dụ Maybevà tiêu chuẩn không làm những gì bạn muốn. Bạn muốn Firsttừ Data.Monoid.

tryFunction = fmap getFirst . fold . map (fmap First)

(Hoặc mconcatcho phiên bản cũ hơn, chuyên dụng hơn fold.)


Giải pháp thú vị (+1). Tôi tìm thấy đầu tiên trực quan hơn.
Giorgio
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.