Saya mempelajari Haskell dari learnyouahaskell.com . Saya kesulitan memahami konstruktor tipe dan konstruktor data. Misalnya, saya tidak begitu mengerti perbedaan antara ini:
data Car = Car { company :: String
, model :: String
, year :: Int
} deriving (Show)
dan ini:
data Car a b c = Car { company :: a
, model :: b
, year :: c
} deriving (Show)
Saya mengerti bahwa yang pertama hanya menggunakan satu konstruktor ( Car
) untuk membangun tipe data Car
. Saya tidak begitu mengerti yang kedua.
Juga, bagaimana tipe data didefinisikan seperti ini:
data Color = Blue | Green | Red
cocok dengan semua ini?
Dari apa yang saya mengerti, contoh ketiga ( Color
) adalah jenis yang dapat di tiga negara: Blue
, Green
atau Red
. Tapi itu bertentangan dengan bagaimana saya memahami dua contoh pertama: apakah tipe Car
hanya bisa dalam satu keadaan Car
, yang dapat mengambil berbagai parameter untuk dibangun? Jika ya, bagaimana contoh kedua cocok?
Pada dasarnya, saya mencari penjelasan yang menyatukan tiga contoh / konstruksi kode di atas.
Car
merupakan konstruktor tipe (di sisi kiri=
) dan konstruktor data (di sisi kanan). Pada contoh pertama,Car
konstruktor tipe tidak membutuhkan argumen, pada contoh kedua dibutuhkan tiga argumen. Dalam kedua contoh tersebut,Car
konstruktor data mengambil tiga argumen (tetapi tipe argumen tersebut dalam satu kasus diperbaiki dan dalam kasus lain diparameterisasi).Car :: String -> String -> Int -> Car
) untuk membangun tipe dataCar
. yang kedua hanya menggunakan satu konstruktor data (Car :: a -> b -> c -> Car a b c
) untuk membangun tipe dataCar a b c
.Jawaban:
Dalam
data
deklarasi, konstruktor tipe adalah benda di sisi kiri dari tanda sama dengan. The Data konstruktor (s) adalah hal-hal di sisi kanan tanda sama. Anda menggunakan konstruktor tipe di mana sebuah tipe diharapkan, dan Anda menggunakan konstruktor data di mana sebuah nilai diharapkan.Konstruktor data
Untuk menyederhanakannya, kita bisa mulai dengan contoh tipe yang mewakili warna.
Di sini, kami memiliki tiga konstruktor data.
Colour
adalah tipe, danGreen
merupakan konstruktor yang berisi nilai tipeColour
. Demikian pula,Red
danBlue
keduanya merupakan konstruktor yang membangun nilai tipeColour
. Kita bisa membayangkan membumbuinya!Kami masih memiliki tipe
Colour
, tetapiRGB
bukan nilai - ini adalah fungsi yang mengambil tiga Ints dan mengembalikan nilai!RGB
memiliki tipeRGB
adalah konstruktor data yang merupakan fungsi yang mengambil beberapa nilai sebagai argumennya, dan kemudian menggunakannya untuk membuat nilai baru. Jika Anda pernah melakukan pemrograman berorientasi objek, Anda harus mengenali ini. Dalam OOP, konstruktor juga mengambil beberapa nilai sebagai argumen dan mengembalikan nilai baru!Dalam hal ini, jika kita menerapkan
RGB
ke tiga nilai, kita mendapatkan nilai warna!Kami telah membangun nilai tipe
Colour
dengan menerapkan konstruktor data. Konstruktor data berisi nilai seperti variabel, atau menggunakan nilai lain sebagai argumennya dan membuat nilai baru . Jika Anda telah melakukan pemrograman sebelumnya, konsep ini seharusnya tidak terlalu aneh bagi Anda.Istirahat
Jika Anda ingin membangun pohon biner untuk menyimpan
String
, Anda dapat membayangkan melakukan sesuatu sepertiApa yang kita lihat di sini adalah tipe
SBTree
yang berisi dua konstruktor data. Dengan kata lain, ada dua fungsi (yaituLeaf
danBranch
) yang akan membentuk nilai dariSBTree
tipe tersebut. Jika Anda tidak terbiasa dengan cara kerja pohon biner, bertahanlah di sana. Anda sebenarnya tidak perlu tahu cara kerja pohon biner, hanya yang satu ini menyimpanString
dengan cara tertentu.Kita juga melihat bahwa kedua konstruktor data mengambil
String
argumen - ini adalah String yang akan mereka simpan di pohon.Tapi! Bagaimana jika kita juga ingin bisa menyimpan
Bool
, kita harus membuat pohon biner baru. Ini bisa terlihat seperti ini:Ketik konstruktor
Keduanya
SBTree
danBBTree
merupakan konstruktor tipe. Tapi ada masalah yang mencolok. Apakah Anda melihat betapa miripnya mereka? Itu pertanda bahwa Anda benar-benar menginginkan parameter di suatu tempat.Jadi kita bisa melakukan ini:
Sekarang kami memperkenalkan variabel tipe
a
sebagai parameter ke tipe konstruktor. Dalam deklarasi ini,BTree
telah menjadi fungsi. Ini mengambil tipe sebagai argumennya dan mengembalikan tipe baru .Jika kita meneruskan, katakanlah,
Bool
sebagai argumen keBTree
, ia mengembalikan tipeBTree Bool
, yang merupakan pohon biner yang menyimpanBool
s. Gantikan setiap kemunculan variabel tipea
dengan tipeBool
, dan Anda dapat melihat sendiri bagaimana itu benar.Jika mau, Anda dapat melihat
BTree
sebagai fungsi dengan jenisJenis agak mirip jenis - yang
*
menunjukkan jenis beton, jadi kami katakanBTree
adalah dari jenis beton ke jenis beton.Membungkus
Mundur sejenak ke sini dan catat persamaannya.
Sebuah konstruktor Data adalah "fungsi" yang mengambil 0 atau lebih nilai-nilai dan memberikan Anda kembali nilai baru.
Sebuah tipe konstruktor adalah "fungsi" yang mengambil 0 atau lebih jenis dan memberikan Anda kembali jenis baru.
Konstruktor data dengan parameter itu keren jika kita ingin sedikit variasi dalam nilai kita - kita menempatkan variasi dalam parameter dan membiarkan orang yang membuat nilai memutuskan argumen apa yang akan mereka masukkan. Dalam pengertian yang sama, jenis konstruktor dengan parameter itu keren jika kita ingin sedikit variasi dalam tipe kita! Kami menempatkan variasi tersebut sebagai parameter dan membiarkan orang yang membuat jenis memutuskan argumen apa yang akan mereka masukkan.
Studi kasus
Sebagai peregangan rumah di sini, kita dapat mempertimbangkan
Maybe a
jenisnya. Definisinya adalahDi sini,
Maybe
adalah tipe konstruktor yang mengembalikan tipe beton.Just
adalah konstruktor data yang mengembalikan nilai.Nothing
adalah konstruktor data yang berisi nilai. Jika kita melihat jenisnyaJust
, kita melihatnyaDengan kata lain,
Just
mengambil nilai tipea
dan mengembalikan nilai tipeMaybe a
. Jika kita melihat jenisnyaMaybe
, kita melihatnyaDengan kata lain,
Maybe
mengambil tipe konkret dan mengembalikan tipe konkret.Sekali lagi! Perbedaan antara tipe beton dan fungsi tipe konstruktor. Anda tidak dapat membuat daftar
Maybe
s - jika Anda mencoba untuk mengeksekusiAnda akan mendapatkan kesalahan. Namun Anda dapat membuat daftar
Maybe Int
, atauMaybe a
. Itu karenaMaybe
merupakan fungsi konstruktor tipe, tetapi daftar harus berisi nilai dari tipe konkret.Maybe Int
danMaybe a
merupakan tipe konkret (atau jika Anda ingin, panggilan ke fungsi konstruktor tipe yang mengembalikan tipe konkret.)sumber
data Colour = Red | Green | Blue
"kami tidak memiliki konstruktor sama sekali" jelas salah. Konstruktor tipe dan konstruktor data tidak perlu mengambil argumen, lihat misalnya haskell.org/haskellwiki/Constructor yang menunjukkan bahwa didata Tree a = Tip | Node a (Tree a) (Tree a)
, "ada dua konstruktor data, Tip dan Node".-XEmptyDataDecls
) yang memungkinkan Anda melakukan itu. Karena, seperti yang Anda katakan, tidak ada nilai dengan tipe itu, sebuah fungsif :: Int -> Z
mungkin misalnya tidak pernah kembali (karena apa yang akan dikembalikannya?) Namun fungsi tersebut dapat berguna saat Anda menginginkan tipe tetapi tidak terlalu peduli dengan nilai .:k Z
dan itu memberi saya bintang.Haskell memiliki tipe data aljabar , yang sangat sedikit dimiliki bahasa lain. Ini mungkin yang membingungkan Anda.
Dalam bahasa lain, Anda biasanya dapat membuat "record", "struct" atau sejenisnya, yang memiliki sekumpulan kolom bernama yang menampung berbagai jenis data. Kadang-kadang Anda juga dapat membuat "enumerasi", yang memiliki satu set (kecil) nilai yang mungkin tetap (misalnya, Anda
Red
,Green
danBlue
).Di Haskell, Anda dapat menggabungkan keduanya secara bersamaan. Aneh, tapi benar!
Mengapa disebut "aljabar"? Nah, para nerd berbicara tentang "jenis jumlah" dan "jenis produk". Sebagai contoh:
Sebuah
Eg1
nilai pada dasarnya adalah baik integer atau string. Jadi himpunan semuaEg1
nilai yang mungkin adalah "jumlah" dari himpunan semua kemungkinan nilai integer dan semua kemungkinan nilai string. Jadi, para 'nerd' menyebutEg1
sebagai "tipe jumlah". Di samping itu:Setiap
Eg2
nilai terdiri dari baik integer dan string. Jadi himpunan semuaEg2
nilai yang mungkin adalah produk Cartesian dari himpunan semua bilangan bulat dan himpunan semua string. Kedua set tersebut "dikalikan" bersama-sama, jadi ini adalah "tipe produk".Tipe aljabar Haskell adalah tipe penjumlahan dari tipe produk . Anda memberi konstruktor beberapa bidang untuk membuat tipe produk, dan Anda memiliki beberapa konstruktor untuk menjumlahkan (produk).
Sebagai contoh mengapa itu mungkin berguna, misalkan Anda memiliki sesuatu yang mengeluarkan data sebagai XML atau JSON, dan itu membutuhkan catatan konfigurasi - tetapi jelas, pengaturan konfigurasi untuk XML dan JSON sama sekali berbeda. Jadi Anda mungkin melakukan sesuatu seperti ini:
(Dengan beberapa bidang yang sesuai di sana, tentunya.) Anda tidak dapat melakukan hal-hal seperti ini dalam bahasa pemrograman normal, itulah sebabnya kebanyakan orang tidak terbiasa dengannya.
sumber
union
s, dengan disiplin tag. :)union
, orang melihat saya seperti "siapa sih yang pernah menggunakan itu ??" ;-)union
penggunaan dalam karir C. Tolong jangan membuatnya terdengar tidak perlu karena bukan itu masalahnya.Mulailah dengan kasus paling sederhana:
Ini mendefinisikan "tipe konstruktor"
Color
yang tidak membutuhkan argumen - dan memiliki tiga "konstruktor data"Blue
,,Green
danRed
. Tidak ada konstruktor data yang membutuhkan argumen apa pun. Ini berarti bahwa ada tiga jenisColor
:Blue
,Green
danRed
.Sebuah konstruktor data digunakan saat Anda perlu membuat semacam nilai. Suka:
membuat nilai
myFavoriteColor
menggunakanGreen
konstruktor data - danmyFavoriteColor
akan berjenisColor
karena itulah jenis nilai yang dihasilkan oleh konstruktor data.Konstruktor tipe digunakan ketika Anda perlu membuat tipe dari beberapa jenis. Ini biasanya terjadi saat menulis tanda tangan:
Dalam kasus ini, Anda memanggil
Color
konstruktor tipe (yang tidak membutuhkan argumen).Masih bersamaku?
Sekarang, bayangkan Anda tidak hanya ingin membuat nilai merah / hijau / biru tetapi Anda juga ingin menentukan "intensitas". Seperti, nilai antara 0 dan 256. Anda dapat melakukannya dengan menambahkan argumen ke setiap konstruktor data, sehingga Anda mendapatkan:
Sekarang, masing-masing dari tiga konstruktor data mengambil argumen tipe
Int
. Type constructor (Color
) masih tidak mengambil argumen apa pun. Jadi, warna favorit saya adalah hijau tua, saya bisa menulisDan lagi, itu memanggil
Green
konstruktor data dan saya mendapatkan nilai tipeColor
.Bayangkan jika Anda tidak ingin mendikte bagaimana orang mengekspresikan intensitas warna. Beberapa mungkin menginginkan nilai numerik seperti yang baru saja kita lakukan. Orang lain mungkin baik-baik saja dengan hanya boolean yang menunjukkan "cerah" atau "tidak begitu cerah". Solusi untuk ini adalah dengan tidak melakukan hardcode
Int
di konstruktor data melainkan menggunakan variabel tipe:Sekarang, konstruktor tipe kami mengambil satu argumen (tipe lain yang baru saja kami panggil
a
!) Dan semua konstruktor data akan mengambil satu argumen (nilai!) Dari tipe itua
. Jadi Anda bisa melakukannyaatau
Perhatikan bagaimana kita memanggil
Color
konstruktor tipe dengan argumen (tipe lain) untuk mendapatkan tipe "efektif" yang akan dikembalikan oleh konstruktor data. Ini menyentuh konsep jenis yang mungkin ingin Anda baca sambil minum kopi.Sekarang kita menemukan apa itu konstruktor data dan konstruktor tipe, dan bagaimana konstruktor data dapat mengambil nilai lain sebagai argumen dan konstruktor tipe dapat menggunakan tipe lain sebagai argumen. HTH.
sumber
->
tanda tangan.a
indata Color a = Red a
.a
adalah placeholder untuk tipe arbitrer. Anda dapat memiliki yang sama dalam fungsi biasa, misalnya fungsi tipe(a, b) -> a
mengambil tupel dari dua nilai (tipea
danb
) dan menghasilkan nilai pertama. Ini adalah fungsi "generik" yang tidak menentukan jenis elemen tupel - hanya menentukan bahwa fungsi tersebut menghasilkan nilai dengan jenis yang sama seperti elemen tupel pertama.Now, our type constructor takes one argument (another type which we just call a!) and all of the data constructors will take one argument (a value!) of that type a.
Ini sangat membantu.Seperti yang ditunjukkan orang lain, polimorfisme tidak terlalu berguna di sini. Mari kita lihat contoh lain yang mungkin sudah Anda kenal:
Tipe ini memiliki dua konstruktor data.
Nothing
agak membosankan, tidak berisi data yang berguna. Di sisi lainJust
mengandung nilaia
- apapun jenisnyaa
. Mari kita tulis fungsi yang menggunakan tipe ini, misalnya mendapatkan kepala dari sebuahInt
daftar, jika ada (saya harap Anda setuju ini lebih berguna daripada membuat kesalahan):Jadi dalam kasus ini
a
adalahInt
, tetapi akan bekerja dengan baik untuk jenis lainnya. Faktanya Anda dapat membuat fungsi kami berfungsi untuk setiap jenis daftar (bahkan tanpa mengubah implementasinya):Di sisi lain, Anda dapat menulis fungsi yang hanya menerima jenis tertentu
Maybe
, misalnyaSingkat cerita, dengan polimorfisme Anda memberi tipe Anda fleksibilitas untuk bekerja dengan nilai dari tipe lain yang berbeda.
Dalam contoh Anda, Anda mungkin memutuskan di beberapa titik yang
String
tidak cukup untuk mengidentifikasi perusahaan, tetapi perlu memiliki jenisnya sendiriCompany
(yang menyimpan data tambahan seperti negara, alamat, akun belakang, dll). Penerapan pertama Anda dariCar
perlu diubah untuk digunakan,Company
bukanString
untuk nilai pertamanya. Implementasi kedua Anda baik-baik saja, Anda menggunakannya sebagaimana adanyaCar Company String Int
dan akan berfungsi seperti sebelumnya (tentu saja fungsi yang mengakses data perusahaan perlu diubah).sumber
data Color = Blue ; data Bright = Color
? Saya mencobanya di ghci, dan tampaknya Color dalam konstruktor tipe tidak ada hubungannya dengan konstruktor data Warna dalam definisi Bright. Hanya ada 2 konstruktor Warna, yang satu adalah Data dan yang lainnya adalah Jenis.data
ataunewtype
(mis.data Bright = Bright Color
), Atau Anda dapat menggunakantype
untuk mendefinisikan sinonim (mistype Bright = Color
.).Yang kedua memiliki pengertian "polimorfisme" di dalamnya.
The
a b c
dapat dari jenis apa pun. Misalnya,a
dapat menjadi[String]
,b
dapat[Int]
danc
dapat[Char]
.Sedangkan tipe yang pertama tetap: perusahaan adalah a
String
, model adalah aString
dan tahunInt
.Contoh Mobil mungkin tidak menunjukkan pentingnya penggunaan polimorfisme. Tapi bayangkan data Anda adalah tipe daftar. Sebuah daftar bisa berisi
String, Char, Int ...
Dalam situasi tersebut, Anda akan membutuhkan cara kedua untuk mendefinisikan data Anda.Untuk cara ketiga saya rasa tidak perlu menyesuaikan dengan tipe sebelumnya. Ini hanyalah satu cara lain untuk mendefinisikan data di Haskell.
Ini adalah pendapat saya yang sederhana sebagai pemula.
Btw: Pastikan Anda melatih otak Anda dengan baik dan merasa nyaman untuk ini. Itu kunci untuk memahami Monad nanti.
sumber
Ini tentang jenis : Dalam kasus pertama, Anda mengatur jenis
String
(untuk perusahaan dan model) danInt
untuk tahun. Dalam kasus kedua, Anda lebih umum.a
,,b
danc
mungkin jenis yang sama seperti di contoh pertama, atau sesuatu yang sama sekali berbeda. Misalnya, mungkin berguna untuk memberikan tahun sebagai string daripada integer. Dan jika Anda mau, Anda bahkan dapat menggunakanColor
tipe Anda .sumber