Ketika menjelaskan kombinator Y dalam konteks Haskell, biasanya dicatat bahwa implementasi straight-forward tidak akan mengetik-cek di Haskell karena tipe rekursifnya.
Misalnya, dari Rosettacode :
The obvious definition of the Y combinator in Haskell canot be used
because it contains an infinite recursive type (a = a -> b). Defining
a data type (Mu) allows this recursion to be broken.
newtype Mu a = Roll { unroll :: Mu a -> a }
fix :: (a -> a) -> a
fix = \f -> (\x -> f (unroll x x)) $ Roll (\x -> f (unroll x x))
Dan memang, definisi "jelas" tidak mengetik centang:
λ> let fix f g = (\x -> \a -> f (x x) a) (\x -> \a -> f (x x) a) g
<interactive>:10:33:
Occurs check: cannot construct the infinite type:
t2 = t2 -> t0 -> t1
Expected type: t2 -> t0 -> t1
Actual type: (t2 -> t0 -> t1) -> t0 -> t1
In the first argument of `x', namely `x'
In the first argument of `f', namely `(x x)'
In the expression: f (x x) a
<interactive>:10:57:
Occurs check: cannot construct the infinite type:
t2 = t2 -> t0 -> t1
In the first argument of `x', namely `x'
In the first argument of `f', namely `(x x)'
In the expression: f (x x) a
(0.01 secs, 1033328 bytes)
Batasan yang sama ada di Ocaml:
utop # let fix f g = (fun x a -> f (x x) a) (fun x a -> f (x x) a) g;;
Error: This expression has type 'a -> 'b but an expression was expected of type 'a
The type variable 'a occurs inside 'a -> 'b
Namun, dalam Ocaml, seseorang dapat memungkinkan tipe rekursif dengan melewati -rectypes
saklar:
-rectypes
Allow arbitrary recursive types during type-checking. By default, only recursive
types where the recursion goes through an object type are supported.
Dengan menggunakan -rectypes
, semuanya berfungsi:
utop # let fix f g = (fun x a -> f (x x) a) (fun x a -> f (x x) a) g;;
val fix : (('a -> 'b) -> 'a -> 'b) -> 'a -> 'b = <fun>
utop # let fact_improver partial n = if n = 0 then 1 else n*partial (n-1);;
val fact_improver : (int -> int) -> int -> int = <fun>
utop # (fix fact_improver) 5;;
- : int = 120
Karena penasaran dengan sistem tipe dan inferensi tipe, ini menimbulkan beberapa pertanyaan yang masih belum bisa saya jawab.
- Pertama, bagaimana pemeriksa tipe muncul dengan tipe
t2 = t2 -> t0 -> t1
? Setelah muncul dengan tipe itu, saya kira masalahnya adalah tipe (t2
) merujuk pada dirinya sendiri di sisi kanan? - Kedua, dan mungkin yang paling menarik, apa alasan sistem tipe Haskell / Ocaml untuk melarang ini? Saya kira ada adalah alasan yang baik karena Ocaml juga tidak akan membiarkan hal itu secara default bahkan jika itu dapat menangani jenis rekursif jika diberi
-rectypes
switch.
Jika ini adalah topik yang sangat besar, saya akan menghargai petunjuk untuk literatur yang relevan.
Di OCaml, Anda harus meneruskan
-rectypes
sebagai parameter ke kompiler (atau masukkan#rectypes;;
di tingkat atas). Secara kasar, ini akan mematikan "terjadi pemeriksaan" selama penyatuan. SituasiThe type variable 'a occurs inside 'a -> 'b
tidak lagi menjadi masalah. Sistem tipe masih akan "benar" (suara, dll.), Pohon tanpa batas yang muncul sebagai jenis kadang-kadang disebut "pohon rasional". Tipe sistem semakin lemah, yaitu menjadi tidak mungkin untuk mendeteksi beberapa kesalahan programmer.Lihat kuliah saya di lambda-calculus (mulai dari slide 27) untuk informasi lebih lanjut tentang operator fixpoint dengan contoh-contoh di OCaml.
sumber