Aplikasi menulis, monads tidak

110

Aplikasi menulis, monads tidak.

Apa maksud pernyataan di atas? Dan kapan yang satu lebih disukai daripada yang lain?

missingfaktor
sumber
5
Dari mana Anda mendapat pernyataan ini? Mungkin berguna untuk melihat beberapa konteks.
fuz
@FUZxxl: Saya telah mendengarnya berulang kali dari banyak orang yang berbeda, baru-baru ini dari debasishg di twitter.
missingfaktor
3
tetley @stephen: Catatan bahwa banyak seperti Applicatives sebenarnya seluruh keluarga dari Monads, yaitu satu untuk setiap "bentuk" dari struktur yang mungkin. ZipListbukan a Monad, tapi ZipLists dengan panjang tetap adalah. Readeradalah kasus khusus yang nyaman (atau apakah itu umum?) di mana ukuran "struktur" ditetapkan sebagai kardinalitas jenis lingkungan.
CA McCann
3
@CAMcCann Semua aplikasi yang lincah (baik terpotong atau pad) membatasi ke monad jika Anda memperbaiki bentuk sedemikian rupa sehingga Readermonad hingga isomorfisme. Setelah Anda memperbaiki bentuk wadah, itu secara efektif mengkodekan fungsi dari posisi, seperti memo trie. Peter Hancock menyebut fungsionalitas seperti itu "Naperian", karena mereka mematuhi hukum logaritma.
pigworker
4
@stephen tetley: Contoh lain termasuk aplikatif monoid-konstan (yang merupakan komposisi monad tetapi bukan monad), dan aplikatif penundaan unit (yang sebaiknya tidak menerima penggabungan).
pigworker

Jawaban:

115

Jika kita membandingkan jenisnya

(<*>) :: Applicative a => a (s -> t) -> a s -> a t
(>>=) :: Monad m =>       m s -> (s -> m t) -> m t

kami mendapatkan petunjuk tentang apa yang memisahkan kedua konsep tersebut. Bahwa (s -> m t)dalam tipe (>>=)menunjukkan bahwa nilai dalam sdapat menentukan perilaku komputasi dalam m t. Monads memungkinkan interferensi antara nilai dan lapisan komputasi. The (<*>)Operator memungkinkan tidak ada gangguan seperti: fungsi dan argumen perhitungan tidak bergantung pada nilai-nilai. Ini benar-benar menggigit. Membandingkan

miffy :: Monad m => m Bool -> m x -> m x -> m x
miffy mb mt mf = do
  b <- mb
  if b then mt else mf

yang menggunakan hasil dari beberapa efek untuk memutuskan antara dua perhitungan (mis. meluncurkan rudal dan menandatangani gencatan senjata), sedangkan

iffy :: Applicative a => a Bool -> a x -> a x -> a x
iffy ab at af = pure cond <*> ab <*> at <*> af where
  cond b t f = if b then t else f

yang menggunakan nilai abuntuk memilih di antara nilai-nilai dari dua perhitungan atdan af, setelah melakukan keduanya, mungkin menghasilkan efek yang tragis.

Versi monadik pada dasarnya bergantung pada kekuatan ekstra (>>=)untuk memilih penghitungan dari suatu nilai, dan itu bisa jadi penting. Namun, mendukung kekuatan tersebut membuat monad sulit untuk dibuat. Jika kita mencoba membangun 'ikatan ganda'

(>>>>==) :: (Monad m, Monad n) => m (n s) -> (s -> m (n t)) -> m (n t)
mns >>>>== f = mns >>-{-m-} \ ns -> let nmnt = ns >>= (return . f) in ???

kita sampai sejauh ini, tapi sekarang lapisan kita semua campur aduk. Kami memiliki n (m (n t)), jadi kami harus menyingkirkan bagian luarnya n. Seperti yang dikatakan Alexandre C, kita bisa melakukannya jika kita punya yang cocok

swap :: n (m t) -> m (n t)

untuk mengubah ke ndalam dan joinke yang lain n.

'Penerapan ganda' yang lebih lemah jauh lebih mudah untuk didefinisikan

(<<**>>) :: (Applicative a, Applicative b) => a (b (s -> t)) -> a (b s) -> a (b t)
abf <<**>> abs = pure (<*>) <*> abf <*> abs

karena tidak ada interferensi antar lapisan.

Sejalan dengan itu, ada baiknya untuk mengenali kapan Anda benar-benar membutuhkan kekuatan ekstra Monads, dan kapan Anda bisa lolos dengan struktur komputasi kaku yang Applicativemendukung.

Perhatikan, ngomong-ngomong, meskipun menyusun monad itu sulit, itu mungkin lebih dari yang Anda butuhkan. Jenis tersebut m (n v)menunjukkan komputasi dengan m-efek, kemudian komputasi dengan n-efek ke v-nilai, di mana m-efek selesai sebelum n-efek dimulai (karena itu diperlukan swap). Jika Anda hanya ingin mmenyisipkan -efek dengan n-efek, maka komposisi mungkin terlalu banyak untuk ditanyakan!

pekerja babi
sumber
3
Untuk contoh yang meragukan, Anda menyatakan bahwa "menggunakan nilai ab untuk memilih di antara nilai-nilai dari dua penghitungan at dan af, setelah melakukan keduanya, mungkin menghasilkan efek yang tragis." Bukankah sifat malas Haskell melindungi Anda dari hal ini? Jika saya memiliki list = (\ btf -> if b then t else f): [] dan kemudian jalankan pernyataan: list <*> pure True <*> pure "hello" <*> pure (error "bad"). ... Saya mendapat "halo" dan kesalahan tidak pernah terjadi. Kode ini hampir tidak aman atau terkontrol seperti monad, tetapi pos tersebut sepertinya menyarankan agar pelamar menyebabkan evaluasi yang ketat. Secara keseluruhan, posting yang bagus! Terima kasih!
shj
7
Anda masih mendapatkan efek keduanya, tetapi murni (kesalahan "buruk") tidak ada. Jika, di sisi lain, Anda mencoba iffy (True murni) (murni "halo") (kesalahan "buruk"), Anda mendapatkan kesalahan yang miffy hindari. Selain itu, jika Anda mencoba sesuatu seperti iffy (Pure True) (pure 0) [1,2], Anda akan mendapatkan [0,0], bukan [0]. Aplikatif memiliki semacam keketatan tentangnya, karena mereka membangun urutan komputasi yang tetap, tetapi nilai yang dihasilkan dari komputasi tersebut masih digabungkan dengan malas, seperti yang Anda amati.
pigworker
Apakah benar, bahwa untuk setiap monad mdan nAnda selalu dapat menulis trafo monad mt, dan beroperasi dalam n (m t)penggunaan mt n t? Jadi Anda selalu dapat membuat monad, hanya saja lebih rumit, menggunakan transformer?
ron
4
Trafo semacam itu sering ada, tetapi sejauh yang saya tahu, tidak ada cara kanonik untuk menghasilkannya. Seringkali ada pilihan asli tentang bagaimana menyelesaikan efek interleaved dari monad yang berbeda, contoh klasiknya adalah pengecualian dan status. Haruskah pengecualian mengembalikan status berubah atau tidak? Kedua pilihan itu ada tempatnya. Karena itu, ada "monad bebas" yang menyatakan "interleaving sewenang-wenang". data Free f x = Ret x | Do (f (Free f x)), lalu data (:+:) f g x = Inl (f x) | Tnr (g x), dan pertimbangkan Free (m :+: n). Itu menunda pilihan bagaimana menjalankan interleavings.
pigworker
@pigworker Tentang debat yang malas / ketat. Saya pikir dengan aplikatif Anda tidak dapat mengontrol efek dari dalam komputasi, tetapi efek-lapisan dapat dengan baik memutuskan untuk tidak mengevaluasi nilai-nilai selanjutnya. Untuk pengurai (aplikatif) ini berarti bahwa jika pengurai gagal lebih awal, pengurai berikutnya tidak dievaluasi / diterapkan ke masukan. Untuk Maybeini berarti bahwa suatu awal Nothingakan menekan evaluasi dari yang akemudian / selanjutnya Just a. Apakah ini benar?
ziggystar
75

Aplikasi menulis, monads tidak.

Monad memang menulis, tetapi hasilnya mungkin bukan monad. Sebaliknya, komposisi dua aplikatif tentu saja aplikatif. Saya curiga maksud dari pernyataan asli adalah bahwa "sifat aplikatif membuat, sedangkan monadness tidak." Diucapkan ulang, " Applicativeditutup di bawah komposisi, dan Monadtidak."

Conal
sumber
24
Selain itu, dua aplikatif apa pun dibuat dengan cara yang sepenuhnya mekanis, sedangkan monad yang dibentuk oleh komposisi dua monad dikhususkan untuk komposisi itu.
Apocalisp
12
Terlebih lagi monad menyusun dengan cara lain, produk dari dua monad adalah monad, hanya produk bersama yang membutuhkan semacam hukum distributif.
Edward KMETT
Dengan @Apocalisp, komentar disertakan, ini adalah jawaban terbaik dan paling ringkas.
Paul Draper
39

Jika Anda memiliki aplikatif A1dan A2, maka jenisnya data A3 a = A3 (A1 (A2 a))juga aplikatif (Anda dapat menulis contoh seperti itu dengan cara yang umum).

Sebaliknya, jika Anda memiliki monad M1dan M2jenisnya data M3 a = M3 (M1 (M2 a))belum tentu monad (tidak ada implementasi umum yang masuk akal untuk >>=atau joinuntuk komposisi).

Salah satu contoh dapat berupa tipe [Int -> a](di sini kita membuat konstruktor tipe []dengan (->) Int, keduanya adalah monad). Anda dapat dengan mudah menulis

app :: [Int -> (a -> b)] -> [Int -> a] -> [Int -> b]
app f x = (<*>) <$> f <*> x

Dan itu menggeneralisasi untuk setiap aplikatif:

app :: (Applicative f, Applicative f1) => f (f1 (a -> b)) -> f (f1 a) -> f (f1 b)

Tetapi tidak ada definisi yang masuk akal tentang

join :: [Int -> [Int -> a]] -> [Int -> a]

Jika Anda tidak yakin akan hal ini, pertimbangkan ungkapan ini:

join [\x -> replicate x (const ())]

Panjang daftar yang dikembalikan harus ditetapkan sebelumnya sebelum bilangan bulat diberikan, tetapi panjang yang benar tergantung pada bilangan bulat yang disediakan. Jadi, tidak ada joinfungsi yang benar untuk tipe ini.

Rotsor
sumber
1
... jadi hindari monad ketika suatu fungsi akan dilakukan?
andrew cooke
2
@ Andrew, jika yang Anda maksud functor, maka ya, functor lebih sederhana dan harus digunakan jika memadai. Perhatikan bahwa tidak selalu demikian. Misalnya IOtanpa program Monadakan sangat sulit. :)
Rotsor
17

Sayangnya, tujuan kami yang sebenarnya, komposisi monad, agak lebih sulit. .. Faktanya, kami sebenarnya dapat membuktikan bahwa, dalam arti tertentu, tidak ada cara untuk membangun fungsi gabungan dengan tipe di atas hanya menggunakan operasi dua monad (lihat lampiran untuk garis besar pembuktian). Oleh karena itu, satu-satunya cara yang kita harapkan untuk membentuk komposisi adalah jika ada beberapa konstruksi tambahan yang menghubungkan kedua komponen tersebut.

Menulis monad, http://web.cecs.pdx.edu/~mpj/pubs/RR-1004.pdf

Landei
sumber
4
Tl; dr untuk pembaca yang tidak sabar: Anda dapat membuat monad jika (f?) Anda dapat memberikan transformasi alamiswap : N M a -> M N a
Alexandre C.
@Alexandre C .: Hanya "jika", saya curiga. Tidak semua trafo monad dijelaskan oleh komposisi fungsi langsung. Misalnya, ContT r m atidak m (Cont r a)nor Cont r (m a), dan StateT s m asecara kasar Reader s (m (Writer s a)).
CA McCann
@ CA McCann: Sepertinya saya tidak bisa berpindah dari (M monad, N monad, MN monad, NM monad) ke (ada swap: MN -> NM natural). Jadi mari kita tetap berpegang pada "jika" untuk saat ini (mungkin jawabannya ada di koran, saya harus akui saya mencarinya dengan cepat)
Alexandre C.
1
@Alexandre C .: Hanya menetapkan bahwa komposisi adalah monad mungkin tidak cukup - Anda juga memerlukan beberapa cara untuk menghubungkan dua bagian dengan keseluruhan. Keberadaan swapmenyiratkan bahwa komposisi memungkinkan keduanya "bekerja sama" entah bagaimana. Juga, perhatikan bahwa sequencekasus khusus "swap" untuk beberapa monad. Jadi flipsebenarnya.
CA McCann
7
Untuk menulisnya, swap :: N (M x) -> M (N x)menurut saya Anda dapat menggunakan returns( fmapped yang sesuai ) untuk memasukkan sebuah Mdi depan dan Ndi belakang, pergi dari N (M x) -> M (N (M (N x))), kemudian gunakan joinkomposit untuk mendapatkan milik Anda M (N x).
pigworker
7

Solusi hukum distributif l: MN -> NM sudah cukup

untuk menjamin monadisitas NM. Untuk melihat ini, Anda memerlukan unit dan mult. saya akan fokus pada mult (unitnya adalah unit_N unitM)

NMNM - l -> NNMM - mult_N mult_M -> NM

Ini tidak menjamin bahwa MN adalah monad.

Pengamatan penting bagaimanapun, mulai berlaku ketika Anda memiliki solusi hukum distributif

l1 : ML -> LM
l2 : NL -> LN
l3 : NM -> MN

jadi, LM, LN dan MN adalah monad. Timbul pertanyaan apakah LMN adalah monad (baik oleh

(MN) L -> L (MN) atau oleh N (LM) -> (LM) N

Kami memiliki struktur yang cukup untuk membuat peta ini. Namun, seperti yang diamati oleh Eugenia Cheng , kita membutuhkan kondisi heksagonal (yang sama dengan presentasi persamaan Yang-Baxter) untuk menjamin monadisitas konstruksi. Padahal, dengan kondisi heksagonal, kedua monad yang berbeda itu bertepatan.

pengguna278559
sumber
9
Ini mungkin jawaban yang bagus, tapi itu meleset di atas kepalaku.
Dan Burton
1
Itu karena, menggunakan istilah Applicative dan tag haskell, ini adalah pertanyaan tentang haskell tetapi dengan jawaban dengan notasi yang berbeda.
codeshot