Apa yang dimaksud dengan sintaks "Just" di Haskell?


118

Saya telah menjelajahi internet untuk penjelasan sebenarnya tentang apa yang dilakukan kata kunci ini. Setiap tutorial Haskell yang saya lihat baru saja mulai menggunakannya secara acak dan tidak pernah menjelaskan apa yang dilakukannya (dan saya telah melihat banyak).

Berikut adalah potongan kode dasar dari Real World Haskell yang digunakan Just. Saya mengerti apa yang dilakukan kode, tetapi saya tidak mengerti apa tujuan atau fungsinya Just.

lend amount balance = let reserve    = 100
                      newBalance = balance - amount
                  in if balance < reserve
                     then Nothing
                     else Just newBalance

Dari apa yang saya amati, ini terkait dengan Maybemengetik, tetapi hanya itu yang berhasil saya pelajari.

Penjelasan yang baik tentang apa Justartinya akan sangat dihargai.

Jawaban:


211

Ini sebenarnya hanya konstruktor data normal yang kebetulan didefinisikan dalam Prelude , yang merupakan pustaka standar yang diimpor secara otomatis ke dalam setiap modul.

Apa Maybe itu, Secara Struktural

Definisi tersebut terlihat seperti ini:

data Maybe a = Just a
             | Nothing

Deklarasi itu mendefinisikan sebuah tipe, Maybe ayang diparameterisasi oleh variabel tipe a, yang berarti Anda dapat menggunakannya dengan tipe apa pun sebagai pengganti a.

Membangun dan Menghancurkan

Tipe ini memiliki dua konstruktor, Just adan Nothing. Ketika suatu tipe memiliki banyak konstruktor, itu berarti bahwa nilai tipe harus dibangun hanya dengan salah satu konstruktor yang memungkinkan. Untuk tipe ini, nilai dibangun melalui Justatau Nothing, tidak ada kemungkinan (non-error) lainnya.

Karena Nothingtidak memiliki tipe parameter, ketika digunakan sebagai konstruktor, ia menamai nilai konstan yang merupakan anggota tipe Maybe auntuk semua tipe a. Tetapi Justkonstruktor memiliki parameter tipe, yang berarti bahwa ketika digunakan sebagai konstruktor, ia bertindak seperti fungsi dari tipe ake Maybe a, yaitu memiliki tipea -> Maybe a

Jadi, konstruktor tipe membangun nilai tipe itu; sisi lain adalah ketika Anda ingin menggunakan nilai itu, dan di situlah pencocokan pola berperan. Tidak seperti fungsi, konstruktor dapat digunakan dalam ekspresi pengikatan pola, dan ini adalah cara di mana Anda dapat melakukan analisis kasus nilai yang termasuk dalam tipe dengan lebih dari satu konstruktor.

Untuk menggunakan Maybe anilai dalam pencocokan pola, Anda perlu memberikan pola untuk setiap konstruktor, seperti:

case maybeVal of
    Nothing   -> "There is nothing!"
    Just val  -> "There is a value, and it is " ++ (show val)

Dalam ekspresi kasus tersebut, pola pertama akan cocok jika nilainya adalah Nothing, dan pola kedua akan cocok jika nilai itu dibuat Just. Jika yang kedua cocok, itu juga mengikat nama valke parameter yang diteruskan ke Justkonstruktor ketika nilai yang Anda cocokkan dibuat.

What Maybe Means

Mungkin Anda sudah terbiasa dengan cara kerjanya; sebenarnya tidak ada keajaiban untuk Maybenilai, itu hanya Haskell Algebraic Data Type (ADT) normal. Tapi itu digunakan cukup banyak karena secara efektif "mengangkat" atau memperluas tipe, seperti Integerdari contoh Anda, ke dalam konteks baru di mana ia memiliki nilai ekstra ( Nothing) yang merepresentasikan kurangnya nilai! Sistem tipe kemudian mengharuskan Anda memeriksa nilai ekstra itu sebelum memungkinkan Anda mendapatkan nilai Integeryang mungkin ada di sana. Ini mencegah sejumlah besar bug.

Banyak bahasa saat ini menangani nilai "tanpa nilai" semacam ini melalui referensi NULL. Tony Hoare, seorang ilmuwan komputer terkemuka (ia menemukan Quicksort dan merupakan pemenang Turing Award), menganggap ini sebagai "kesalahan miliaran dolar" . Jenis Mungkin bukan satu-satunya cara untuk memperbaikinya, tetapi telah terbukti menjadi cara yang efektif untuk melakukannya.

Mungkin sebagai Functor

Ide untuk mengubah satu tipe ke tipe lain sehingga operasi pada tipe lama juga bisa diubah untuk bekerja pada tipe baru adalah konsep di balik kelas tipe Haskell yang dipanggil Functor, yang Maybe amemiliki instance berguna.

Functormenyediakan metode yang dipanggil fmap, yang memetakan fungsi yang berkisar pada nilai dari tipe dasar (seperti Integer) ke fungsi yang berkisar pada nilai dari tipe yang diangkat (seperti Maybe Integer). Sebuah fungsi yang diubah dengan fmapuntuk mengerjakan Maybenilai bekerja seperti ini:

case maybeVal of
  Nothing  -> Nothing         -- there is nothing, so just return Nothing
  Just val -> Just (f val)    -- there is a value, so apply the function to it

Jadi jika Anda memiliki Maybe Integernilai m_xdan Int -> Intfungsi f, Anda dapat fmap f m_xmenerapkan fungsi tersebut fsecara langsung ke Maybe Integertanpa khawatir apakah fungsi tersebut benar-benar bernilai atau tidak. Faktanya, Anda dapat menerapkan seluruh rangkaian Integer -> Integerfungsi yang diangkat ke Maybe Integernilai dan hanya perlu mengkhawatirkan pemeriksaan secara eksplisit untuk Nothingsekali saat Anda selesai.

Mungkin sebagai Monad

Saya tidak yakin seberapa akrab Anda dengan konsep a Monadyet, tetapi Anda setidaknya pernah menggunakan IO asebelumnya, dan tipe tanda tangan IO aterlihat sangat mirip Maybe a. Meskipun IOspesial karena tidak mengekspos konstruktornya kepada Anda dan dengan demikian hanya dapat "dijalankan" oleh sistem runtime Haskell, ia juga merupakan Functortambahan untuk menjadi a Monad. Faktanya, ada perasaan penting di mana a Monadhanyalah jenis khusus Functordengan beberapa fitur tambahan, tetapi ini bukan tempat untuk membahasnya.

Bagaimanapun, Monad menyukai IOtipe peta menjadi tipe baru yang merepresentasikan "komputasi yang menghasilkan nilai" dan Anda dapat mengangkat fungsi menjadi Monadtipe melalui fmapfungsi yang sangat mirip yang disebut liftMyang mengubah fungsi reguler menjadi "komputasi yang menghasilkan nilai yang diperoleh dengan mengevaluasi fungsi."

Anda mungkin telah menebak (jika Anda telah membaca sejauh ini) itu Maybejuga a Monad. Ini mewakili "perhitungan yang bisa gagal mengembalikan nilai". Sama seperti fmapcontoh, ini memungkinkan Anda melakukan banyak komputasi tanpa harus memeriksa kesalahan secara eksplisit setelah setiap langkah. Dan pada kenyataannya, cara Monadinstance dibuat, penghitungan Maybenilai berhenti segera setelah a Nothingditemukan, jadi ini seperti pembatalan langsung atau pengembalian tak berharga di tengah penghitungan.

Anda Mungkin Bisa Menulis

Seperti yang saya katakan sebelumnya, tidak ada yang melekat pada Maybetipe yang dimasukkan ke dalam sintaks bahasa atau sistem runtime. Jika Haskell tidak menyediakannya secara default, Anda dapat menyediakan semua fungsinya sendiri! Nyatanya, Anda tetap bisa menulisnya sendiri, dengan nama berbeda, dan mendapatkan fungsi yang sama.

Semoga Anda memahami Maybejenis dan konstruktornya sekarang, tetapi jika masih ada yang kurang jelas, beri tahu saya!


19
Jawaban yang luar biasa! Perlu disebutkan bahwa Haskell sering menggunakan di Maybemana bahasa lain akan digunakan nullatau nil(dengan kata jahat NullPointerExceptionbersembunyi di setiap sudut). Sekarang bahasa lain mulai menggunakan konstruksi ini juga: Scala as Option, dan bahkan Java 8 akan memiliki Optionaltipenya.
Landei

3
Ini adalah penjelasan yang sangat bagus. Banyak penjelasan yang telah saya baca mengisyaratkan gagasan bahwa Just adalah konstruktor untuk tipe Maybe, tetapi tidak ada yang benar-benar membuatnya eksplisit.
reem

@Landei, terima kasih atas sarannya. Saya membuat pengeditan untuk merujuk pada bahaya referensi nol.
Levi Pearson

@Landei Jenis opsi telah ada sejak ML di tahun 70-an, kemungkinan besar Scala mengambilnya dari sana karena Scala menggunakan konvensi penamaan ML, Opsi dengan konstruktor beberapa dan tidak ada.
stonemetal

@Landei Apple's Swift menggunakan opsi opsional juga
Jamin

37

Sebagian besar jawaban saat ini adalah penjelasan yang sangat teknis tentang bagaimana Justdan teman bekerja; Saya pikir saya mungkin akan mencoba menjelaskan untuk apa itu.

Banyak bahasa yang memiliki nilai seperti nullitu yang dapat digunakan sebagai pengganti nilai sebenarnya, setidaknya untuk beberapa jenis. Hal ini membuat banyak orang sangat marah dan secara luas dianggap sebagai tindakan yang buruk. Namun, terkadang berguna untuk memiliki nilai seperti nulluntuk menunjukkan tidak adanya sesuatu.

Haskell memecahkan masalah ini dengan membuat Anda secara eksplisit menandai tempat-tempat di mana Anda dapat memiliki Nothing(versinya a null). Pada dasarnya, jika fungsi Anda biasanya mengembalikan tipe Foo, itu seharusnya mengembalikan tipe Maybe Foo. Jika Anda ingin menunjukkan bahwa tidak ada nilai, kembalikan Nothing. Jika Anda ingin mengembalikan nilai bar, Anda harus mengembalikan Just bar.

Jadi pada dasarnya, jika Anda tidak bisa memiliki Nothing, Anda tidak perlu Just. Jika Anda dapat memiliki Nothing, Anda memang membutuhkan Just.

Tidak ada yang ajaib tentang Maybe; itu dibangun di atas sistem tipe Haskell. Itu berarti Anda dapat menggunakan semua trik pencocokan pola Haskell yang biasa dengannya.


1
Jawaban yang sangat bagus di sebelah jawaban lain, tetapi saya pikir itu masih akan mendapat manfaat dari contoh kode :)
PascalVKooten

13

Diberikan sebuah tipe t, nilai dari Just tadalah nilai tipe yang sudah ada t, dimana Nothingmerepresentasikan kegagalan untuk mencapai sebuah nilai, atau kasus dimana memiliki sebuah nilai akan menjadi tidak berarti.

Dalam contoh Anda, memiliki keseimbangan negatif tidak masuk akal, jadi jika hal seperti itu terjadi, diganti dengan Nothing.

Untuk contoh lain, ini bisa digunakan dalam pembagian, mendefinisikan fungsi pembagian yang mengambil adan b, dan mengembalikan Just a/bjika bbukan nol, dan Nothingsebaliknya. Ini sering digunakan seperti ini, sebagai alternatif yang nyaman untuk pengecualian, atau seperti contoh Anda sebelumnya, untuk mengganti nilai yang tidak masuk akal.


1
Jadi katakanlah dalam kode di atas saya menghapus Just, mengapa itu tidak berhasil? Apakah Anda Harus Memiliki Kapan Saja Anda Ingin Memiliki Tidak Ada? Apa yang terjadi pada ekspresi di mana fungsi di dalam ekspresi itu mengembalikan Nothing?
reem

7
Jika Anda menghapus Just, kode Anda tidak akan salah ketik. Alasannya Justadalah untuk mempertahankan tipe yang tepat. Ada tipe (sebenarnya A monad, tetapi lebih mudah untuk dianggap hanya sebagai tipe) Maybe t, yang terdiri dari elemen bentuk Just tdan Nothing. Karena Nothingmemiliki tipe Maybe t, ekspresi yang dapat mengevaluasi salah satu Nothingatau beberapa nilai tipe ttidak diketik dengan benar. Jika suatu fungsi kembali Nothingdalam beberapa kasus, ekspresi apa pun yang menggunakan fungsi itu harus memiliki beberapa cara untuk memeriksanya ( isJustatau pernyataan kasus), untuk menangani semua kemungkinan kasus.
qaphla

2
Jadi Just ada untuk menjadi konsisten dalam tipe Maybe karena sebuah t biasa tidak ada dalam tipe Maybe. Semuanya jauh lebih jelas sekarang. Terima kasih!
reem

3
@qaphla: Komentar Anda tentang "sebuah monad, sebenarnya, [...]" menyesatkan. Maybe t adalah hanya tipe. Fakta bahwa ada Monadcontoh untuk Maybetidak mengubahnya menjadi sesuatu yang bukan tipe.
Sarah

2

Fungsi total a-> b dapat menemukan nilai tipe b untuk setiap kemungkinan nilai tipe a.

Di Haskell tidak semua fungsi bersifat total. Dalam kasus khusus ini, fungsi lendtidak total - tidak ditentukan untuk kasus ketika saldo kurang dari cadangan (walaupun, menurut selera saya akan lebih masuk akal untuk tidak mengizinkan saldo baru menjadi kurang dari cadangan - sebagaimana adanya, Anda dapat meminjam 101 dari keseimbangan 100).

Desain lain yang berhubungan dengan fungsi non-total:

  • membuang pengecualian saat memeriksa nilai input tidak sesuai dengan kisaran
  • mengembalikan nilai khusus (tipe primitif): pilihan favorit adalah nilai negatif untuk fungsi bilangan bulat yang dimaksudkan untuk mengembalikan bilangan asli (misalnya, String.indexOf - ketika substring tidak ditemukan, indeks yang dikembalikan biasanya dirancang menjadi negatif)
  • mengembalikan nilai khusus (pointer): NULL atau semacamnya
  • pengembalian diam-diam tanpa melakukan apa pun: misalnya, lenddapat ditulis untuk mengembalikan saldo lama, jika kondisi pinjaman tidak terpenuhi
  • mengembalikan nilai khusus: Tidak ada (atau Kiri membungkus beberapa objek deskripsi kesalahan)

Ini adalah batasan desain yang diperlukan dalam bahasa yang tidak dapat memaksakan totalitas fungsi (misalnya, Agda dapat, tetapi mengarah ke komplikasi lain, seperti menjadi tidak lengkap).

Masalah dengan mengembalikan nilai khusus atau melempar pengecualian adalah mudah bagi pemanggil untuk mengabaikan penanganan kemungkinan seperti itu secara tidak sengaja.

Masalah dengan secara diam-diam membuang kegagalan juga jelas - Anda membatasi apa yang dapat dilakukan pemanggil dengan fungsi tersebut. Misalnya, jika lendsaldo lama dikembalikan, penelepon tidak dapat mengetahui apakah saldo telah berubah. Ini mungkin atau mungkin tidak menjadi masalah, tergantung pada tujuan yang dimaksudkan.

Solusi Haskell memaksa pemanggil dari fungsi parsial untuk menangani tipe seperti Maybe a, atau Either error akarena tipe kembalian fungsi.

Dengan cara ini, lendseperti yang didefinisikan, adalah fungsi yang tidak selalu menghitung keseimbangan baru - untuk beberapa keadaan, keseimbangan baru tidak ditentukan. Kami memberi sinyal keadaan ini kepada penelepon dengan mengembalikan nilai khusus Nothing, atau dengan membungkus saldo baru di Just. Penelepon sekarang memiliki kebebasan untuk memilih: menangani kegagalan untuk meminjamkan dengan cara khusus, atau mengabaikan dan menggunakan saldo lama - misalnya , maybe oldBalance id $ lend amount oldBalance.


-1

Fungsi if (cond :: Bool) then (ifTrue :: a) else (ifFalse :: a)harus memiliki jenis ifTruedan ifFalse.

Jadi, saat kita menulis then Nothing, kita harus menggunakan Maybe atype inelse f

if balance < reserve
       then (Nothing :: Maybe nb)         -- same type
       else (Just newBalance :: Maybe nb) -- same type

1
Saya yakin Anda bermaksud mengatakan sesuatu yang sangat dalam di sini
lihat

1
Haskell memiliki inferensi tipe. Tidak perlu menyebutkan jenis Nothingdan Just newBalancesecara eksplisit.
kanan

Untuk penjelasan ini bagi yang belum tahu, tipe eksplisit menjelaskan arti penggunaan Just.
philip
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.