Apakah ada ungkapan Haskell untuk mencoba beberapa fungsi dan berhenti segera setelah berhasil?

9

Di Haskell, saya bisa menggunakan tipe a -> Maybe buntuk memodelkan fungsi yang mengembalikan nilai tipe b, atau mengembalikan apa-apa (gagal).

Jika saya memiliki jenis a1, ..., a(n+1)dan fungsi f1, ..., fn, dengan fi :: ai -> Maybe a(i+1)untuk semua i, 1 <= i <= n, aku dapat rantai fungsi dengan menggunakan >>=operator Maybemonad dan menulis:

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

The >>=Memastikan operator yang masing-masing fungsi diterapkan selama pendahulunya telah kembali nilai yang berarti. Segera setelah fungsi dalam rantai gagal, seluruh rantai gagal (kembali Nothing) dan fungsi lebih lanjut dalam rantai tidak dievaluasi.

Saya memiliki pola yang agak mirip di mana saya ingin mencoba beberapa fungsi pada input yang sama, dan kembali segera setelah satu fungsi berhasil . Jika semua fungsi gagal (kembali Nothing), seluruh perhitungan harus gagal. Lebih tepatnya, saya memiliki fungsi f1, ..., fn :: a -> Maybe bdan saya mendefinisikan fungsi

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

Dalam arti tertentu, ini adalah ganda bagi Maybemonad karena perhitungan berhenti pada keberhasilan pertama dan bukan pada kegagalan pertama.

Tentu saja, saya dapat menggunakan fungsi yang saya tulis di atas, tetapi saya bertanya-tanya apakah ada cara yang lebih baik, mapan dan idiomatis untuk mengekspresikan pola ini di Haskell.

Giorgio
sumber
Bukan Haskell, tetapi dalam C #, Anda kadang-kadang akan melihat operator null-coalesce (??) yang digunakan seperti itu:return f1 ?? f2 ?? f3 ?? DefaultValue;
Telastyn
4
Ya itu - ini adalah Alternativeyang merupakan simbol operator infiks <|>dan didefinisikan dalam istilah Monoid
Jimmy Hoffa

Jawaban:

8

Diberikan set tertutup (jumlah elemen tetap) Sdengan elemen {a..z}dan operator biner *:

Ada elemen identitas tunggal isehingga:

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

Operator bersifat asosiatif sehingga:

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

Anda memiliki monoid.

Sekarang diberi monoid apa pun, Anda dapat mendefinisikan fungsi biner fsebagai:

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

Artinya adalah bahwa untuk contoh Maybemonoid ( Nothingapakah elemen identitas dilambangkan di atas sebagai 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

Anehnya, saya tidak dapat menemukan fungsi yang tepat ini di perpustakaan default, yang kemungkinan karena pengalaman saya sendiri. Jika ada orang lain yang bisa menjadi sukarelawan ini, saya akan sangat menghargainya.

Inilah implementasi yang saya simpulkan dari contoh di atas:

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

Contoh:

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

Kemudian jika Anda ingin menggunakan daftar fungsi sebagai input ...

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

contoh:

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
λ> 
Jimmy Hoffa
sumber
Saya tidak mengerti mengapa <|>memperlakukan identitas monoid dengan cara khusus. Tidak bisakah seseorang memilih elemen sewenang-wenang dari set arbitrer untuk memainkan peran khusus itu? Mengapa himpunan harus monoid dan elemen khusus <|>menunjukkan identitasnya?
Giorgio
1
@Iorgio mungkin itu sebabnya <|>tidak bergantung pada monoid dan saya memiliki semua ini bercampur? Itu bergantung pada Alternativetypeclass. Untuk memastikan - Aku sedang melihat jawaban saya sendiri dan menyadari itu tidak benar karena [1,2] <|> [3]memberikan terduga [1,2,3]sehingga segala sesuatu tentang menggunakan kelas jenis monoid untuk mengidentifikasi identitas yang benar - dan kunci lainnya adalah associativity adalah diperlukan untuk mendapatkan perilaku yang diharapkan , mungkin Alternativetidak memberikan perilaku yang saya pikir begitu saja ...
Jimmy Hoffa
@JimmyHoffa itu akan tergantung pada typclass Mungkin <|> akan berbeda dengan Daftar <|> tidak?
jk.
@Giorgio melihat 2 contoh terakhir saya funtuk melihat mengapa asosiatif adalah suatu keharusan.
Jimmy Hoffa
yaitu monoid untuk daftar adalah daftar kosong dan concat
jk.
5
import Data.Monoid

tryFunctions :: a -> [a -> Maybe b] -> Maybe b
tryFunctions x = getFirst . mconcat . map (First . ($ x))
Thomas Eding
sumber
Ini bersih dan sederhana tetapi tetap untuk Maybe... Solusi saya dibatasi oleh Eq, entah bagaimana saya merasa kita berdua kehilangan sesuatu yang tersedia secara Monoidumum ...
Jimmy Hoffa
@JimmyHoffa: Maksud Anda, Anda ingin menggeneralisasi solusi ini sehingga dapat bekerja dengan tipe data lain, misalnya either?
Giorgio
@Iorgio tepatnya. Saya berharap satu-satunya kendala bisa Monoidjadi apa pun dengan elemen identitas dapat memiliki satu set fungsi 2 (saya pikir ini adalah jenis bidang biner), di mana salah satu fungsi memilih identitas daripada yang lain, dan fungsi lainnya selalu memilih elemen non-identitas. Saya hanya tidak tahu bagaimana melakukan ini tanpa Eqmengetahui nilai mana identityatau tidak .. jelas dalam monoids aditif atau multiplikasi Anda mendapatkan fungsi tangga secara default (selalu memilih elemen non-identitas menggunakan fungsi biner monoids)
Jimmy Hoffa
foldMapdapat digunakan sebagai penggantimconcat . map
4castle
4

Ini kedengarannya seperti mengganti kegagalan dengan daftar keberhasilan

Anda berbicara tentang Maybe adaripada [a], tetapi pada kenyataannya mereka sangat mirip: kita dapat menganggapnya Maybe aseperti [a], kecuali itu dapat mengandung paling banyak satu elemen (yaitu. Nothing ~= []Dan Just x ~= [x]).

Dalam hal daftar, Anda tryFunctionsakan sangat sederhana: terapkan semua fungsi ke argumen yang diberikan kemudian gabungkan semua hasil bersama-sama. concatMapakan melakukan ini dengan baik:

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

Dengan cara ini, kita dapat melihat bahwa <|>operator untuk Maybebertindak seperti penggabungan untuk 'daftar dengan paling banyak satu elemen'.

Warbo
sumber
1

Dalam semangat Conal, pecah menjadi operasi sederhana yang lebih kecil.

Dalam hal ini, asumdari Data.Foldablemanakah bagian utama.

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

Atau, sebagai balasan ala Jimmy Hoffa Anda dapat menggunakan Monoidinstance untuk (->)tetapi kemudian Anda membutuhkan Monoidinstance untuk Maybedan yang standar tidak melakukan apa yang Anda inginkan. Anda inginkan Firstdari Data.Monoid.

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

(Atau mconcatuntuk versi yang lebih lama dan lebih terspesialisasi fold.)

Derek Elkins meninggalkan SE
sumber
Solusi menarik (+1). Saya menemukan yang pertama lebih intuitif.
Giorgio