Di sini, "nilai", "tipe", dan "jenis" memiliki arti formal, jadi mempertimbangkan penggunaan umum dalam bahasa Inggris atau analogi untuk mengklasifikasikan mobil hanya akan membuat Anda sejauh ini.
Jawaban saya berkaitan dengan makna formal dari istilah-istilah ini dalam konteks Haskell secara khusus; makna ini didasarkan pada (meskipun tidak benar-benar identik dengan) makna yang digunakan dalam matematika / CS "teori tipe". Jadi, ini bukan jawaban "ilmu komputer" yang sangat bagus, tetapi seharusnya berfungsi sebagai jawaban Haskell yang cukup bagus.
Dalam Haskell (dan bahasa lainnya), pada akhirnya membantu untuk menetapkan jenis ekspresi program yang menjelaskan kelas nilai yang diizinkan dimiliki oleh ekspresi tersebut. Saya berasumsi di sini bahwa Anda telah melihat cukup banyak contoh untuk memahami mengapa akan berguna untuk mengetahui bahwa dalam ekspresi sqrt (a**2 + b**2)
, variabel a
dan b
akan selalu menjadi nilai tipe Double
dan tidak, katakanlah, String
dan Bool
masing - masing. Pada dasarnya, memiliki tipe membantu kita dalam menulis ekspresi / program yang akan bekerja dengan benar pada berbagai nilai .
Sekarang, sesuatu yang mungkin tidak Anda sadari adalah bahwa tipe Haskell, seperti yang muncul dalam tanda tangan tipe:
fmap :: Functor f => (a -> b) -> f a -> f b
sebenarnya sendiri ditulis dalam sub-bahasa Haskell tipe-level. Teks program Functor f => (a -> b) -> f a -> f b
adalah - secara harfiah - ekspresi tipe yang ditulis dalam subbahasa ini. Sub-bahasa termasuk operator (misalnya, ->
adalah operator infiks asosiatif yang tepat dalam bahasa ini), variabel (misalnya, f
, a
, dan b
), dan "aplikasi" dari satu jenis ekspresi yang lain (misalnya, f a
yang f
diterapkan untuk a
).
Apakah saya menyebutkan bagaimana membantu dalam banyak bahasa untuk menetapkan tipe ekspresi program untuk menggambarkan kelas nilai ekspresi? Nah, dalam subtitle bahasa tingkat-jenis ini, ekspresi mengevaluasi ke tipe (bukan nilai ) dan pada akhirnya membantu untuk menetapkan jenis untuk mengetik ekspresi untuk menggambarkan kelas tipe yang diizinkan untuk mereka wakili. Pada dasarnya, memiliki jenis membantu kita dalam menulis ekspresi tipe yang akan bekerja dengan benar pada berbagai jenis .
Jadi, nilai adalah tipe karena tipe adalah jenis , dan tipe membantu kita menulis program tingkat- nilai sementara jenis membantu kita menulis program tipe- tingkat.
Apa ini jenis terlihat seperti? Nah, pertimbangkan jenis tanda tangan:
id :: a -> a
Jika ekspresi jenis a -> a
adalah sah, apa jenis dari jenis yang harus kita mengizinkan variabel a
untuk menjadi? Nah, jenis ekspresi:
Int -> Int
Bool -> Bool
terlihat valid, jadi tipe Int
dan Bool
jelas dari jenis yang tepat . Tetapi jenis yang lebih rumit seperti:
[Double] -> [Double]
Maybe [(Double,Int)] -> Maybe [(Double,Int)]
terlihat valid Bahkan, karena kita harus dapat memanggil id
fungsi, bahkan:
(a -> a) -> (a -> a)
terlihat baik-baik saja. Jadi, Int
, Bool
, [Double]
, Maybe [(Double,Int)]
, dan a -> a
semua terlihat seperti jenis hak jenis .
Dengan kata lain, sepertinya hanya ada satu jenis , sebut saja *
seperti wildcard Unix, dan setiap jenis memiliki jenis yang sama *
, akhir cerita.
Kanan?
Ya tidak cukup. Ternyata Maybe
, dengan sendirinya, adalah ekspresi tipe yang valid Maybe Int
(dalam banyak hal yang sama sqrt
, semua dengan sendirinya, sama validnya dengan ekspresi nilai sqrt 25
). Namun , ekspresi tipe berikut ini tidak valid:
Maybe -> Maybe
Karena, sementara Maybe
adalah ekspresi jenis, itu tidak mewakili jenis dari jenis yang dapat memiliki nilai-nilai. Jadi, itulah bagaimana kita harus mendefinisikan *
- itu adalah jenis dari jenis yang memiliki nilai-nilai; itu termasuk tipe "lengkap" seperti Double
atau Maybe [(Double,Int)]
tetapi tidak termasuk tipe tidak lengkap dan tidak berharga seperti Either String
. Untuk kesederhanaan, saya akan menyebut tipe lengkap ini sebagai *
"tipe konkret", meskipun terminologi ini tidak universal, dan "tipe konkret" mungkin berarti sesuatu yang sangat berbeda dengan, katakanlah, seorang programmer C ++.
Sekarang, dalam ekspresi jenis a -> a
, selama jenis a
memiliki jenis *
(jenis jenis beton), hasil dari ekspresi jenis jugaa -> a
akan memiliki jenis (yaitu, jenis jenis beton). *
Jadi, apa jenis dari jenis ini Maybe
? Nah, Maybe
bisa diterapkan pada jenis beton untuk menghasilkan jenis beton lain. Jadi, Maybe
tampak seperti sedikit seperti fungsi jenis-tingkat yang mengambil jenis dari jenis *
dan mengembalikan jenis dari jenis *
. Jika kita memiliki fungsi tingkat nilai yang mengambil nilai dari jenis Int
dan kembali nilai dari jenis Int
, kami akan memberikan jenis tanda tangan Int -> Int
, sehingga dengan analogi kita harus memberikan Maybe
sebuah jenis tanda tangan * -> *
. GHCi setuju:
> :kind Maybe
Maybe :: * -> *
Kembali ke:
fmap :: Functor f => (a -> b) -> f a -> f b
Dalam jenis tanda tangan ini, variabel f
memiliki jenis * -> *
dan variabel a
dan b
jenis *
; operator bawaan ->
memiliki jenis * -> * -> *
(dibutuhkan jenis *
di sebelah kiri dan satu di kanan dan mengembalikan jenis juga *
). Dari ini dan aturan inferensi jenis, Anda dapat menyimpulkan bahwa itu a -> b
adalah jenis yang valid dengan jenis *
, f a
dan f b
juga jenis yang valid dengan jenis *
, dan (a -> b) -> f a -> f b
jenis jenis yang valid *
.
Dengan kata lain, kompiler dapat "jenis memeriksa" ekspresi tipe (a -> b) -> f a -> f b
untuk memverifikasi itu valid untuk jenis variabel dari jenis yang tepat dengan cara yang sama "ketik memeriksa" sqrt (a**2 + b**2)
untuk memverifikasi itu valid untuk variabel dari jenis yang tepat.
Alasan untuk menggunakan istilah terpisah untuk "jenis" versus "jenis" (yaitu, tidak berbicara tentang "jenis jenis") sebagian besar hanya untuk menghindari kebingungan. The jenis di atas tampilan yang sangat berbeda dari jenis dan, setidaknya pada awalnya, tampaknya berperilaku cukup berbeda. (Sebagai contoh, dibutuhkan beberapa waktu untuk membungkus kepala Anda sekitar gagasan bahwa setiap "normal" jenis memiliki jenis yang sama *
dan jenis a -> b
yang *
tidak * -> *
.)
Beberapa di antaranya juga historis. Ketika GHC Haskell telah berevolusi, perbedaan antara nilai, tipe, dan jenis sudah mulai kabur. Hari-hari ini, nilai-nilai dapat "dipromosikan" ke dalam tipe, dan tipe dan jenis adalah hal yang sama. Jadi, di Haskell modern, nilai keduanya memiliki tipe dan tipe ARE (hampir), dan jenis-jenisnya hanyalah tipe yang lebih banyak.
@ user21820 meminta beberapa penjelasan tambahan tentang "jenis dan jenisnya adalah hal yang sama". Untuk menjadi sedikit lebih jelas, dalam GHC Haskell modern (sejak versi 8.0.1, saya pikir), jenis dan jenis diperlakukan secara seragam di sebagian besar kode kompiler. Kompiler melakukan upaya dalam pesan kesalahan untuk membedakan antara "jenis" dan "jenis", tergantung pada apakah itu mengeluh tentang jenis nilai atau jenis jenis, masing-masing.
Juga, jika tidak ada ekstensi yang diaktifkan, ekstensi tersebut mudah dibedakan dalam bahasa permukaan. Sebagai contoh, tipe (nilai) memiliki representasi dalam sintaks (misalnya, dalam tipe tanda tangan), tetapi jenis (tipe) adalah - saya pikir - sepenuhnya tersirat, dan tidak ada sintaksis eksplisit di mana mereka muncul.
Tetapi, jika Anda mengaktifkan ekstensi yang sesuai, perbedaan antara jenis dan jenis sebagian besar menghilang. Sebagai contoh:
{-# LANGUAGE GADTs, TypeInType #-}
data Foo where
Bar :: Bool -> * -> Foo
Di sini, Bar
adalah (baik nilai dan) tipe. Sebagai jenis, jenisnya adalah Bool -> * -> Foo
, yang merupakan fungsi tipe-tingkat yang mengambil jenis jenis Bool
(yang merupakan jenis, tetapi juga jenis) dan jenis jenis *
dan menghasilkan jenis jenis Foo
. Begitu:
type MyBar = Bar True Int
memeriksa dengan benar.
Sebagai @AndrejBauer menjelaskan dalam jawabannya, kegagalan untuk membedakan antara jenis dan jenis tidak aman - memiliki jenis / jenis *
yang jenis / jenisnya itu sendiri (yang merupakan kasus di Haskell modern) mengarah ke paradoks. Namun, sistem tipe Haskell sudah penuh dengan paradoks karena non-terminasi, sehingga tidak dianggap sebagai masalah besar.