Haskell: Bagaimana cara <*> diucapkan? [Tutup]

109

Bagaimana Anda mengucapkan fungsi-fungsi ini di kelas tipe Aplikatif:

(<*>) :: f (a -> b) -> f a -> f b
(*>)  :: f a -> f b -> f b
(<*)  :: f a -> f b -> f a

(Artinya, jika mereka bukan operator, mereka akan dipanggil apa?)

Sebagai catatan tambahan, jika Anda dapat mengganti nama puremenjadi sesuatu yang lebih bersahabat dengan non-matematikawan, apa yang akan Anda sebut?

J Cooper
sumber
6
@J Cooper ... bisakah Anda mendengar bagaimana kami mengucapkannya? :) Anda mungkin ingin mengirim permintaan ke meta.stackoverflow.com untuk fitur perekaman dan pemutaran suara :).
Kiril
8
Diucapkan "Astaga, mereka benar-benar kehabisan operator, bukan?" Juga, nama yang bagus untuk puremungkin makeApplicative.
Chuck
@Lirik, Yah, saya kira dengan mengucapkan yang saya maksud "whaddya panggil hal ini" :) @Chuck, posting puresaran Anda sebagai jawaban dan saya akan memberi suara positif
J Cooper
6
(<*>) adalah versi Control.Applicative dari "ap" Control.Monad, jadi "ap" mungkin adalah nama yang paling tepat.
Edward KMETT
11
saya akan menyebutnya cyclops, tapi itu hanya saya.
RCIX

Jawaban:

245

Maaf, saya tidak terlalu paham matematika saya, jadi saya ingin tahu bagaimana cara mengucapkan fungsi di kelas tipe Aplikatif

Mengetahui matematika Anda, atau tidak, sebagian besar tidak relevan di sini, menurut saya. Seperti yang mungkin Anda sadari, Haskell meminjam beberapa bit terminologi dari berbagai bidang matematika abstrak, terutama Teori Kategori , dari mana kita mendapatkan functor dan monad. Penggunaan istilah-istilah ini di Haskell agak menyimpang dari definisi matematika formal, tetapi mereka biasanya cukup dekat untuk menjadi istilah deskriptif yang baik.

Kelas Applicativetipe berada di antara Functordan Monad, jadi orang akan mengharapkannya memiliki dasar matematika yang serupa. Dokumentasi untuk Control.Applicativemodul dimulai dengan:

Modul ini menjelaskan struktur perantara antara functor dan monad: modul ini menyediakan ekspresi dan urutan murni, tetapi tidak ada pengikatan. (Secara teknis, fungsi monoidal lemah yang kuat.)

Hmm.

class (Functor f) => StrongLaxMonoidalFunctor f where
    . . .

Tidak semenarik itu Monad, menurut saya.

Apa yang pada dasarnya semua ini intinya adalah itu Applicativetidak sesuai dengan konsep apa pun yang sangat menarik secara matematis, jadi tidak ada istilah siap pakai yang menangkap cara penggunaannya di Haskell. Jadi, kesampingkan matematika untuk saat ini.


Jika kita ingin tahu apa yang disebut (<*>), mungkin membantu untuk mengetahui apa artinya pada dasarnya.

Jadi apa dengan Applicative, pula, dan mengapa tidak kita menyebutnya itu?

Apa yang Applicativeberjumlah dalam praktek adalah cara untuk mengangkat sewenang-wenang fungsi menjadi Functor. Pertimbangkan kombinasi Maybe(bisa dibilang yang paling sederhana non-sepele Functor) dan Bool(juga tipe data non-sepele yang paling sederhana).

maybeNot :: Maybe Bool -> Maybe Bool
maybeNot = fmap not

Fungsinya fmapmemungkinkan kita mengangkat notdari bekerja Boolke mengerjakan Maybe Bool. Tetapi bagaimana jika kita ingin mengangkat (&&)?

maybeAnd' :: Maybe Bool -> Maybe (Bool -> Bool)
maybeAnd' = fmap (&&)

Nah, itu bukan apa yang kita inginkan sama sekali ! Faktanya, itu sangat tidak berguna. Kita bisa mencoba menjadi pandai dan menyelinap Boolmasuk Maybemelalui belakang ...

maybeAnd'' :: Maybe Bool -> Bool -> Maybe Bool
maybeAnd'' x y = fmap ($ y) (fmap (&&) x)

... tapi itu tidak bagus. Untuk satu hal, itu salah. Untuk hal lain, itu jelek . Kami dapat terus mencoba, tetapi ternyata tidak ada cara untuk mengangkat fungsi dari beberapa argumen untuk bekerja secara sembaranganFunctor . Menyebalkan!

Di sisi lain, kita bisa melakukannya dengan mudah jika kita menggunakan Maybe's Monadcontoh:

maybeAnd :: Maybe Bool -> Maybe Bool -> Maybe Bool
maybeAnd x y = do x' <- x
                  y' <- y
                  return (x' && y')

Nah, itu sangat merepotkan hanya untuk menerjemahkan fungsi sederhana - itulah sebabnya Control.Monadmenyediakan fungsi untuk melakukannya secara otomatis , liftM2. Angka 2 dalam namanya mengacu pada fakta bahwa ia bekerja pada fungsi tepat dua argumen; fungsi serupa ada untuk 3, 4, dan 5 fungsi argumen. Fungsi-fungsi ini lebih baik , tetapi tidak sempurna, dan menentukan jumlah argumen itu jelek dan kikuk.

Yang membawa kita ke makalah yang memperkenalkan kelas tipe Aplikatif . Di dalamnya, pada dasarnya penulis melakukan dua pengamatan:

  • Mengangkat fungsi multi-argumen menjadi a Functoradalah hal yang sangat wajar untuk dilakukan
  • Melakukannya tidak memerlukan kemampuan penuh dari a Monad

Aplikasi fungsi normal ditulis dengan penjajaran istilah yang sederhana, sehingga untuk membuat "aplikasi yang diangkat" sesederhana dan sealami mungkin, makalah ini memperkenalkan operator infix untuk menggantikan aplikasi, diangkat ke dalamFunctor , dan kelas tipe untuk menyediakan apa yang dibutuhkan untuk itu .

Semuanya membawa kita ke poin berikut: (<*>)hanya mewakili aplikasi fungsi - jadi mengapa mengucapkannya secara berbeda dari yang Anda lakukan pada "operator penjajaran" spasi kosong?

Tetapi jika itu tidak terlalu memuaskan, kita dapat mengamati bahwa Control.Monadmodul juga menyediakan fungsi yang melakukan hal yang sama untuk monad:

ap :: (Monad m) => m (a -> b) -> m a -> m b

Dimana ap, tentu saja, singkatan dari "melamar". Karena apapun Monadbisa Applicative, dan aphanya membutuhkan subset dari fitur yang ada pada yang terakhir, kita mungkin bisa mengatakan bahwa jika (<*>)bukan operator, itu harus dipanggil ap.


Kita juga bisa mendekati sesuatu dari arah lain. The Functoroperasi pengangkatan disebut fmapkarena itu generalisasi dari mapoperasi pada daftar. Seperti apa fungsi pada daftar yang akan berfungsi (<*>)? Ada apa yang apada di daftar, tentu saja, tapi itu tidak terlalu berguna.

Faktanya, mungkin ada interpretasi yang lebih alami untuk daftar. Apa yang terlintas dalam pikiran saat Anda melihat tanda tangan tipe berikut?

listApply :: [a -> b] -> [a] -> [b]

Ada sesuatu yang begitu menggoda tentang gagasan melapisi daftar secara paralel, menerapkan setiap fungsi di bagian pertama ke elemen yang sesuai di kedua. Sayangnya bagi teman lama kita Monad, operasi sederhana ini melanggar hukum monad jika panjang daftarnya berbeda. Tapi itu membuat denda Applicative, dalam hal ini (<*>)menjadi cara merangkai versi umum zipWith, jadi mungkin kita bisa membayangkan menyebutnya fzipWith?


Ide membuat ritsleting ini benar-benar memberi kita lingkaran penuh. Ingat hal matematika sebelumnya, tentang fungsi monoid? Seperti namanya, berikut adalah cara untuk menggabungkan struktur monoid dan fungsi, keduanya adalah kelas tipe Haskell yang sudah dikenal:

class Functor f where
    fmap :: (a -> b) -> f a -> f b

class Monoid a where
    mempty :: a
    mappend :: a -> a -> a

Akan terlihat seperti apa jika Anda memasukkannya ke dalam kotak dan menggoyangnya sedikit? Dari Functorkita akan menjaga gagasan tentang struktur tidak tergantung pada parameter tipenya , dan dari Monoidkita akan mempertahankan bentuk fungsi secara keseluruhan:

class (Functor f) => MonoidalFunctor f where
    mfEmpty :: f ?
    mfAppend :: f ? -> f ? -> f ?

Kami tidak ingin berasumsi bahwa ada cara untuk membuat yang benar-benar "kosong" Functor, dan kami tidak dapat membayangkan nilai dari sembarang tipe, jadi kami akan memperbaiki tipe mfEmptysebagai f ().

Kami juga tidak ingin memaksa mfAppenduntuk membutuhkan parameter tipe yang konsisten, jadi sekarang kami memiliki ini:

class (Functor f) => MonoidalFunctor f where
    mfEmpty :: f ()
    mfAppend :: f a -> f b -> f ?

Untuk apa jenis hasil tersebut mfAppend? Kami memiliki dua tipe arbitrer yang tidak kami ketahui, jadi kami tidak memiliki banyak pilihan. Hal yang paling masuk akal adalah menyimpan keduanya:

class (Functor f) => MonoidalFunctor f where
    mfEmpty :: f ()
    mfAppend :: f a -> f b -> f (a, b)

Pada titik mana mfAppendsekarang jelas merupakan versi umum dari zipdaftar, dan kami dapat merekonstruksi Applicativedengan mudah:

mfPure x = fmap (\() -> x) mfEmpty
mfApply f x = fmap (\(f, x) -> f x) (mfAppend f x)

Ini juga menunjukkan kepada kita yang pureterkait dengan elemen identitas a Monoid, jadi nama bagus lainnya untuk itu mungkin apa pun yang menyarankan nilai unit, operasi nol, atau semacamnya.


Itu sangat panjang, jadi untuk meringkas:

  • (<*>) hanyalah sebuah aplikasi fungsi yang dimodifikasi, jadi Anda dapat membacanya sebagai "ap" atau "apply", atau elide seluruhnya seperti yang Anda lakukan pada aplikasi fungsi normal.
  • (<*>)juga secara kasar menggeneralisasi zipWithdaftar, sehingga Anda dapat membacanya sebagai "fungsi zip dengan", mirip dengan membaca fmapsebagai "memetakan fungsi dengan".

Yang pertama lebih dekat dengan maksud dari Applicativekelas tipe - seperti namanya - jadi itulah yang saya rekomendasikan.

Faktanya, saya mendorong penggunaan liberal, dan non-pengucapan, dari semua operator aplikasi yang diangkat :

  • (<$>), yang mengangkat fungsi argumen tunggal menjadi a Functor
  • (<*>), yang menghubungkan fungsi multi-argumen melalui Applicative
  • (=<<), yang mengikat fungsi yang memasukkan a Monadke komputasi yang sudah ada

Ketiganya, pada intinya, hanya aplikasi fungsi biasa, dibumbui sedikit.

CA McCann
sumber
6
@ Colin Cochrane: Apakah Anda yakin tidak salah mengeja "bertele-tele" di sana? :) Tapi hei, aku akan menerimanya! Saya selalu merasa itu Applicativedan gaya idiomatik fungsional yang dipromosikannya tidak mendapatkan cukup cinta, jadi saya tidak bisa menahan kesempatan untuk sedikit memuji kebajikannya sebagai sarana untuk menjelaskan bagaimana saya (tidak) mengucapkannya (<*>).
CA McCann
+1! Jangan ragu untuk bertele-tele serupa di stackoverflow.com/questions/2104446/…
Greg Bacon
6
Akankah Haskell memiliki gula sintaks untuk Applicative's! Sesuatu seperti [| f a b c d |](seperti yang disarankan oleh kertas asli). Maka kami tidak memerlukan <*>kombinator dan Anda akan merujuk ke ekspresi seperti itu sebagai contoh "aplikasi fungsi dalam konteks fungsi"
Tom Crockett
1
@FredOverflow: Tidak, maksud saya Monad. Atau Functoratau Monoidapa pun yang memiliki istilah mapan yang melibatkan kurang dari tiga kata sifat. "Aplikatif" hanyalah nama yang tidak menginspirasi, meskipun cukup deskriptif, yang ditampar ke sesuatu yang agak dibutuhkan.
CA McCann
1
@pelotom: lihat [ stackoverflow.com/questions/12014524/… di mana orang-orang yang baik hati menunjukkan dua cara untuk mendapatkan hampir notasi itu.
AndrewC
21

Karena saya tidak memiliki ambisi untuk meningkatkan jawaban teknis CA McCann , saya akan menangani jawaban yang lebih halus:

Jika Anda dapat mengubah nama puremenjadi sesuatu yang lebih bersahabat dengan podunks seperti saya, Anda akan menyebutnya apa?

Sebagai alternatif, terutama karena tidak ada akhir dari seruan terus menerus yang dipenuhi kecemasan dan pengkhianatan terhadap Monadversi, yang disebut " return", saya mengusulkan nama lain, yang menyarankan fungsinya dengan cara yang dapat memuaskan pemrogram yang paling penting , dan yang paling fungsional ... baik, mudah-mudahan, semua orang bisa mengeluh sama tentang: inject.

Ambil nilai. "Inject" ke dalam Functor, Applicative, Monad, atau apa-Anda-. Saya memilih " inject", dan saya menyetujui pesan ini.

BMeph
sumber
4
Saya biasanya condong ke sesuatu seperti "unit" atau "lift", tetapi itu sudah memiliki terlalu banyak arti lain di Haskell. injectadalah nama yang bagus dan mungkin lebih baik dari nama saya, meskipun sebagai catatan kecil, "inject" digunakan dalam - menurut saya - Smalltalk dan Ruby untuk metode lipatan kiri. Aku tidak pernah mengerti pilihan nama itu, meskipun ...
CA McCann
3
Ini adalah utas yang sangat lama, tetapi saya pikir injectdi Ruby & Smalltalk digunakan karena ini seperti Anda "menyuntikkan" operator di antara setiap elemen dalam daftar. Setidaknya, itulah yang selalu saya pikirkan.
Jonathan Sterling
1
Untuk sekali lagi mengambil side-thread lama itu: Anda tidak menyuntikkan operator, Anda mengganti (menghilangkan) konstruktor yang sudah ada. (Dilihat sebaliknya, Anda memasukkan data lama ke tipe baru.) Untuk daftar, eliminasi itu adil foldr. (Anda mengganti (:)dan [], di mana (:)mengambil 2 argumen dan []merupakan konstanta, maka foldr (+) 0 (1:2:3:[])1+2+3+0.) Di Boolatasnya hanya if- then- else(dua konstanta, pilih satu) dan untuk Maybeitu disebut maybe... Haskell tidak memiliki nama / fungsi tunggal untuk ini, karena semua memiliki tipe yang berbeda (secara umum elim hanya rekursi / induksi)
tidak ada
@CAMcCann Smalltalk mendapatkan nama itu dari lagu Arlo Guthrie tentang draft perang Vietnam, di mana pemuda malang dikumpulkan, dipilih, terkadang ditolak, dan sebaliknya, disuntikkan.
Tom Anderson
7

Secara singkat:

  • <*>Anda bisa menyebutnya berlaku . Jadi Maybe f <*> Maybe adapat diucapkan sebagai berlaku Maybe fatasMaybe a .

  • Anda dapat mengganti nama puremenjadi of, seperti yang dilakukan banyak pustaka JavaScript. Di JS Anda dapat membuat Maybedengan Maybe.of(a).

Juga, wiki Haskell memiliki halaman tentang pengucapan operator bahasa di sini

Marcelo Lazaroni
sumber
3
(<*>) -- Tie Fighter
(*>)  -- Right Tie
(<*)  -- Left Tie
pure  -- also called "return"

Sumber: Pemrograman Haskell dari First Principles , oleh Chris Allen dan Julie Moronuki

dmvianna
sumber
Nama-nama itu belum begitu populer, sejauh yang saya tahu.
dfeuer
@dfeuer tunggu generasi penerus Haskeller yang menggunakan buku itu sebagai bahan belajar utama mereka.
dmvianna
1
Itu mungkin terjadi. Nama-nama itu jelek, karena tidak ada hubungannya dengan artinya.
dfeuer
1
@ pdfeuer, saya tidak melihat solusi yang baik di mana pun. "ap" / "apply" sama kaburnya dengan "tie fighter". Semuanya adalah aplikasi fungsi. Namun, nama yang muncul tiba-tiba bisa mendapatkan makna melalui penggunaan. "Apple" adalah contoh yang bagus. Omong-omong, pengembalian Monad adalah Aplikatif murni. Tidak ada penemuan di sini.
dmvianna