Misalkan saya ingin menulis perpustakaan yang berhubungan dengan vektor dan matriks. Apakah mungkin untuk memanggang dimensi ke dalam jenis, sehingga operasi dimensi yang tidak kompatibel menghasilkan kesalahan pada waktu kompilasi?
Misalnya saya ingin tanda tangan dari produk titik menjadi sesuatu seperti
dotprod :: Num a, VecDim d => Vector a d -> Vector a d -> a
di mana d
tipe berisi nilai integer tunggal (mewakili dimensi dari vektor-vektor ini).
Saya kira ini bisa dilakukan dengan mendefinisikan (dengan tangan) tipe terpisah untuk setiap integer, dan mengelompokkannya dalam tipe kelas yang disebut VecDim
. Apakah ada mekanisme untuk "menghasilkan" jenis seperti itu?
Atau mungkin cara yang lebih baik / lebih sederhana untuk mencapai hal yang sama?
haskell
type-systems
type-safety
mitchus
sumber
sumber
tensor
perpustakaan mencapai ini dengan sangat elegan menggunakandata
definisi rekursif : noaxiom.org/tensor-documentation#ordinalsJawaban:
Untuk memperluas jawaban @ KarlBielefeldt, berikut adalah contoh lengkap tentang cara mengimplementasikan Vektor - daftar dengan jumlah elemen yang diketahui secara statis - di Haskell. Pegang topimu ...
Seperti yang Anda lihat dari daftar panjang
LANGUAGE
arahan, ini hanya akan bekerja dengan versi terbaru GHC.Kita membutuhkan cara untuk merepresentasikan panjang dalam sistem tipe. Menurut definisi, bilangan alami adalah nol (
Z
) atau merupakan penerus beberapa bilangan alami lainnya (S n
). Jadi, misalnya, angka 3 akan ditulisS (S (S Z))
.Dengan ekstensi DataKinds ,
data
deklarasi ini memperkenalkan jenis yang disebutNat
dan dua tipe konstruktor yang disebutS
danZ
- dengan kata lain kita memiliki bilangan alami tipe-level . Perhatikan bahwa tipeS
danZ
tidak memiliki nilai anggota - hanya tipe*
yang dihuni oleh nilai.Sekarang kami memperkenalkan GADT yang mewakili vektor dengan panjang yang diketahui. Perhatikan jenis tanda tangan:
Vec
membutuhkan jenis jenisNat
(yaitu jenisZ
atau aS
) untuk mewakili panjangnya.Definisi vektor mirip dengan daftar tertaut, dengan beberapa informasi tingkat tipe tambahan tentang panjangnya. Vektor adalah salah satu
VNil
, dalam hal ini memiliki panjangZ
(ero), atau itu adalahVCons
sel yang menambahkan item ke vektor lain, dalam hal ini panjangnya adalah satu lebih dari vektor lainnya (S n
). Perhatikan bahwa tidak ada argumen tipe konstruktorn
. Ini hanya digunakan pada waktu kompilasi untuk melacak panjang, dan akan dihapus sebelum kompiler menghasilkan kode mesin.Kami telah mendefinisikan jenis vektor yang membawa sekitar pengetahuan statis panjangnya. Mari kita tanyakan tipe beberapa
Vec
untuk mengetahui bagaimana mereka bekerja:Produk titik dihasilkan seperti halnya untuk daftar:
vap
, yang 'zippily' menerapkan vektor fungsi ke vektor argumen, bersifatVec
aplikatif<*>
; Saya tidak memasukkannya ke dalamApplicative
contoh karena menjadi berantakan . Perhatikan juga bahwa saya menggunakanfoldr
instance dari compiler-generate dariFoldable
.Mari kita coba:
Besar! Anda mendapatkan kesalahan waktu kompilasi ketika Anda mencoba untuk
dot
vektor yang panjangnya tidak cocok.Berikut adalah upaya pada fungsi untuk menggabungkan vektor bersama:
Panjang vektor keluaran akan menjadi jumlah dari panjang dua vektor input. Kita perlu mengajari pemeriksa tipe cara menambahkan
Nat
s bersama-sama. Untuk ini kami menggunakan fungsi tipe-level :type family
Deklarasi ini memperkenalkan fungsi pada tipe yang disebut:+:
- dengan kata lain, itu adalah resep untuk pemeriksa tipe untuk menghitung jumlah dua bilangan alami. Itu didefinisikan secara rekursif - setiap kali operan kiri lebih besar dariZ
ero kita menambahkan satu ke output dan menguranginya dengan satu dalam panggilan rekursif. (Ini adalah latihan yang baik untuk menulis fungsi tipe yang mengalikan duaNat
s.) Sekarang kita dapat membuat+++
kompilasi:Begini cara Anda menggunakannya:
Sejauh ini sangat sederhana. Bagaimana kalau kita ingin melakukan kebalikan dari penggabungan dan membagi vektor menjadi dua? Panjang vektor keluaran tergantung pada nilai runtime argumen. Kami ingin menulis sesuatu seperti ini:
tapi sayangnya Haskell tidak akan membiarkan kita melakukan itu. Memungkinkan nilai dari
n
argumen untuk muncul dalam tipe kembali (ini biasa disebut fungsi tergantung atau jenis pi ) akan membutuhkan "full-spectrum" jenis tergantung, sedangkanDataKinds
hanya memberi kita dipromosikan jenis konstruktor. Dengan kata lain, ketik konstruktorS
danZ
tidak muncul pada tingkat nilai. Kita harus puas dengan nilai singleton untuk representasi run-time tertentuNat
. *Untuk jenis yang diberikan
n
(dengan jenisNat
), tepat ada satu istilah jenisNatty n
. Kita dapat menggunakan nilai singleton sebagai saksi run-time untukn
: belajar tentang aNatty
mengajarkan kita tentang itun
dan sebaliknya.Mari kita putar:
Pada contoh pertama, kami berhasil membagi vektor tiga elemen pada posisi 2; kemudian kami mendapat kesalahan ketik ketika kami mencoba untuk membagi vektor pada posisi melewati akhir. Lajang adalah teknik standar untuk membuat jenis bergantung pada nilai dalam Haskell.
*
singletons
Perpustakaan berisi beberapa pembantu Template Haskell untuk menghasilkan nilai tunggal sepertiNatty
untuk Anda.Contoh terakhir. Bagaimana ketika Anda tidak tahu dimensi vektor Anda secara statis? Misalnya, bagaimana jika kita mencoba membangun vektor dari data run-time dalam bentuk daftar? Anda memerlukan jenis vektor untuk bergantung pada panjang daftar input. Dengan kata lain, kita tidak dapat menggunakan
foldr VCons VNil
untuk membangun vektor karena jenis vektor keluaran berubah dengan setiap iterasi flip. Kita perlu merahasiakan panjang vektor dari kompiler.AVec
adalah tipe eksistensial : variabel tipen
tidak muncul dalam tipe kembalinyaAVec
konstruktor data. Kami menggunakannya untuk mensimulasikan pasangan dependen :fromList
tidak dapat memberi tahu Anda panjang vektor secara statis, tetapi dapat mengembalikan sesuatu yang dapat Anda sesuaikan dengan pola untuk mempelajari panjang vektor -Natty n
elemen pertama tuple . Seperti yang dikatakan Conor McBride dalam jawaban terkait , "Anda melihat satu hal, dan dengan demikian, belajarlah tentang hal lain".Ini adalah teknik umum untuk tipe yang dikuantifikasi secara eksistensial. Karena Anda tidak dapat benar-benar melakukan apa pun dengan data yang Anda tidak tahu jenisnya - coba tulis fungsi
data Something = forall a. Sth a
- keberadaan sering dibundel dengan bukti GADT yang memungkinkan Anda untuk memulihkan jenis aslinya dengan melakukan tes pencocokan pola. Pola umum lain untuk eksistensial termasuk mengemas fungsi untuk memproses tipe Anda (data AWayToGetTo b = forall a. HeresHow a (a -> b)
) yang merupakan cara yang rapi untuk melakukan modul kelas satu, atau membangun kamus tipe kelas (data AnOrd = forall a. Ord a => AnOrd a
) yang dapat membantu meniru polimorfisme subtipe.Pasangan tangguh berguna setiap kali sifat statis data bergantung pada informasi dinamis yang tidak tersedia pada waktu kompilasi. Ini
filter
untuk vektor:Untuk
dot
duaAVec
s, kita perlu membuktikan kepada GHC bahwa panjangnya sama.Data.Type.Equality
mendefinisikan GADT yang hanya dapat dibangun ketika argumen tipenya sama:Ketika Anda mencocokkan pola aktif
Refl
, GHC tahu itua ~ b
. Ada juga beberapa fungsi untuk membantu Anda bekerja dengan jenis ini: kami akan gunakangcastWith
untuk mengonversi antara jenis yang setara, danTestEquality
untuk menentukan apakah duaNatty
s sama.Untuk menguji kesamaan dua
Natty
s, kita akan perlu untuk membuat penggunaan fakta bahwa jika dua angka adalah sama, maka penerus mereka juga sama (:~:
adalah kongruen lebihS
):Pencocokan pola
Refl
di sebelah kiri membuat GHC mengetahui hal itun ~ m
. Dengan pengetahuan itu, itu sepeleS n ~ S m
, jadi GHC memungkinkan kita segera mengembalikan yang baruRefl
.Sekarang kita dapat menulis contoh
TestEquality
dengan rekursi langsung. Jika kedua angka tersebut nol, keduanya sama. Jika kedua nomor memiliki pendahulu, mereka sama jika pendahulunya sama. (Jika mereka tidak sama, kembalilahNothing
.)Sekarang kita dapat menempatkan potongan untuk
dot
sepasangAVec
s panjang tidak diketahui.Pertama, pencocokan pola pada
AVec
konstruktor untuk mengeluarkan representasi runtime dari panjang vektor. Sekarang gunakantestEquality
untuk menentukan apakah panjangnya sama. Jika ya, kita harusJust Refl
;gcastWith
akan menggunakan bukti kesetaraan untuk memastikan bahwadot u v
diketik dengan baik dengan menggunakann ~ m
asumsi implisitnya .Perhatikan bahwa, karena vektor tanpa pengetahuan statis panjangnya pada dasarnya adalah sebuah daftar, kami telah secara efektif mengimplementasikan kembali versi daftar
dot :: Num a => [a] -> [a] -> Maybe a
. Perbedaannya adalah bahwa versi ini diimplementasikan dalam hal vektordot
. Inilah intinya: sebelum pemeriksa tipe memungkinkan Anda menelepondot
, Anda harus menguji apakah daftar input memiliki panjang yang samatestEquality
. Saya cenderung mendapatkan-if
pernyataan yang salah, tetapi tidak dalam pengaturan yang diketik secara dependen!Anda tidak dapat menghindari menggunakan pembungkus eksistensial di tepi sistem Anda, ketika Anda berurusan dengan data runtime, tetapi Anda dapat menggunakan tipe dependen di mana saja di dalam sistem Anda dan menyimpan pembungkus eksistensial di tepi, ketika Anda melakukan validasi input.
Karena
Nothing
tidak terlalu informatif, Anda dapat lebih lanjut memperbaiki jenisdot'
untuk mengembalikan bukti bahwa panjangnya tidak sama (dalam bentuk bukti bahwa perbedaannya tidak 0) dalam kasus kegagalan. Ini sangat mirip dengan teknik standar Haskell menggunakanEither String a
untuk mungkin mengembalikan pesan kesalahan, meskipun istilah bukti jauh lebih bermanfaat secara komputasi daripada string!Dengan demikian mengakhiri tur peluit berhenti ini dari beberapa teknik yang umum dalam pemrograman Haskell yang diketik secara dependen. Pemrograman dengan tipe seperti ini di Haskell benar-benar keren, tetapi benar-benar canggung pada saat yang sama. Memecah semua data dependen Anda menjadi banyak representasi yang berarti hal yang sama -
Nat
jenis,Nat
jenis,Natty n
singleton - benar-benar sangat rumit, meskipun ada pembuat kode untuk membantu dengan pelat boiler. Saat ini juga ada batasan pada apa yang dapat dipromosikan ke tingkat tipe. Ini menggiurkan! Pikiran boggles pada kemungkinan - dalam literatur ada contoh dalam Haskell sangat diketikprintf
, antarmuka database, mesin tata letak UI ...Jika Anda ingin membaca lebih lanjut, ada banyak literatur tentang Haskell yang diketik secara dependen, baik yang diterbitkan maupun di situs-situs seperti Stack Overflow. Sebuah titik awal yang baik adalah yang Hasochism kertas - kertas melewati sangat contoh ini (antara lain), membahas bagian yang menyakitkan dalam beberapa detail. The lajang kertas menunjukkan teknik nilai tunggal (seperti
Natty
). Untuk informasi lebih lanjut tentang mengetik secara umum, tutorial Agda adalah tempat yang baik untuk memulai; juga, Idris adalah bahasa dalam pengembangan yang (secara kasar) dirancang untuk menjadi "Haskell dengan tipe bergantung".sumber
Itu disebut pengetikan dependen . Setelah Anda tahu namanya, Anda dapat menemukan lebih banyak informasi tentang itu daripada yang Anda harapkan. Ada juga bahasa haskell seperti menarik yang disebut Idris yang menggunakannya secara asli. Penulisnya telah melakukan beberapa presentasi yang sangat bagus tentang topik yang dapat Anda temukan di youtube.
sumber
newtype Vec2 a = V2 (a,a)
,newtype Vec3 a = V3 (a,a,a)
dan seterusnya, tetapi bukan itu yang ditanyakan oleh pertanyaan itu.)Pi (x : A). B
yang fungsi dariA
keB x
manax
adalah argumen dari fungsi. Di sini tipe kembalinya fungsi bergantung pada ekspresi yang diberikan sebagai argumen. Namun, semua ini dapat dihapus, ini waktu kompilasi saja