Masalah
Pertimbangkan masalah desain berikut ini di Haskell. Saya memiliki, EDSL simbolis sederhana di mana saya ingin mengekspresikan variabel dan ekspresi umum (polinomial multivariat) seperti x^2 * y + 2*z + 1
. Selain itu, saya ingin mengekspresikan persamaan simbolik tertentu atas ekspresi, katakanlah x^2 + 1 = 1
, serta definisi , seperti x := 2*y - 2
.
Tujuannya adalah untuk:
- Memiliki tipe terpisah untuk variabel dan ekspresi umum - fungsi tertentu mungkin diterapkan pada variabel dan bukan ekspresi kompleks. Misalnya, operator definisi
:=
mungkin bertipe(:=) :: Variable -> Expression -> Definition
dan seharusnya tidak mungkin untuk melewatkan ekspresi kompleks sebagai parameter sisi kiri (meskipun harus mungkin untuk melewatkan variabel sebagai parameter sisi kanannya, tanpa casting eksplisit ) . - Buatlah ekspresi dari instance
Num
, sehingga dimungkinkan untuk mempromosikan literal integer ke ekspresi dan menggunakan notasi yang nyaman untuk operasi aljabar umum seperti penambahan atau perkalian tanpa memperkenalkan beberapa operator pembungkus tambahan.
Dengan kata lain, saya ingin memiliki tipe cast (paksaan) variabel implisit dan statis untuk ekspresi. Sekarang, saya tahu bahwa dengan demikian, tidak ada gips tipe implisit di Haskell. Namun demikian, konsep pemrograman berorientasi objek tertentu (pewarisan sederhana, dalam hal ini) dapat diekspresikan dalam sistem tipe Haskell, baik dengan atau tanpa ekstensi bahasa. Bagaimana saya bisa memenuhi kedua poin di atas sambil mempertahankan sintaksis yang ringan? Apakah itu mungkin?
Diskusi
Jelas bahwa masalah utama di sini adalah Num
pembatasan jenis, misalnya
(+) :: Num a => a -> a -> a
Pada prinsipnya, dimungkinkan untuk menulis tipe data aljabar tunggal (umum) untuk variabel dan ekspresi. Kemudian, orang dapat menulis :=
sedemikian rupa, sehingga ungkapan sisi kiri didiskriminasi dan hanya konstruktor variabel yang diterima, dengan kesalahan run-time sebaliknya. Namun itu bukan solusi yang bersih, statis (mis. Waktu kompilasi) ...
Contoh
Idealnya, saya ingin mencapai sintaks yang ringan seperti
computation = do
x <- variable
t <- variable
t |:=| x^2 - 1
solve (t |==| 0)
Secara khusus, saya ingin melarang notasi seperti
t + 1 |:=| x^2 - 1
karena :=
harus memberikan definisi variabel dan bukan seluruh ekspresi sisi kiri.
class FromVar e
dengan metodefromVar :: Variable -> e
dan memberikan contoh untukExpression
danVariable
, kemudian variabel Anda memiliki tipe polimorfikx :: FromVar e => e
dll. Saya belum menguji seberapa baik ini bekerja karena saya di ponsel saya sekarang.FromVar
typeclass akan membantu. Saya ingin menghindari gips secara eksplisit sambil menjagaExpr
instanceNum
. Saya mengedit pertanyaan dengan menambahkan contoh notasi yang ingin saya capai.Jawaban:
Untuk meningkatkan polimorfisme daripada subtyping (karena itu yang Anda miliki di Haskell), jangan berpikir "variabel adalah ekspresi", tetapi "kedua variabel dan ekspresi memiliki beberapa operasi yang sama". Operasi-operasi itu dapat dimasukkan ke dalam kelas tipe:
Kemudian, alih-alih melempar sesuatu, buatlah sesuatu yang polimorfik. Jika sudah
v :: forall e. HasVar e => e
, itu bisa digunakan baik sebagai ekspresi maupun sebagai variabel.Skeleton untuk membuat kode di bawah ini ketiklah: https://gist.github.com/Lysxia/da30abac357deb7981412f1faf0d2103
sumber
V
. Awalnya bukan itu yang saya inginkan, tapi mungkin saya terlalu cepat untuk mengabaikannya ... Mungkin saya tidak bisa menghilangkan buramV
. Pada catatan terkait, bagaimana saya bisa membuat instance dariV (forall e . HasVar e => e)
? Dalam Coq, saya akan menggunakan tipe komputasi dan pencocokan pola pada tipe induktif, tetapi tidak jelas bagaimana mencapainya di Haskell.w :: Variable
entah bagaimana dan menerapkanfromVar
untuk itu:variable = (\w -> V (fromVar w)) <$> (_TODO_ :: Solver Variable)
.V
bisa dihindari dengan tipe impredikatif, tapi itu masih WIP. Atau kita bisavariable
mengambil kelanjutan dengan argumen polimorfik secara eksplisit, bukan secara tidak langsung melalui(>>=)
.