Ada sejumlah cara bagus untuk melihat ini. Hal yang paling mudah bagi saya adalah berpikir tentang hubungan antara "Induktif" dan "definisi Coinductive"
Definisi induktif dari satu set seperti ini.
Set "Nat" didefinisikan sebagai set terkecil sehingga "Nol" berada di Nat, dan jika n ada di Nat "Succ n" ada di Nat.
Yang sesuai dengan Ocaml berikut
type nat = Zero | Succ of nat
Satu hal yang perlu diperhatikan tentang definisi ini adalah angka
omega = Succ(omega)
BUKAN anggota dari set ini. Mengapa? Asumsikan itu, sekarang pertimbangkan himpunan N yang memiliki semua elemen yang sama dengan Nat kecuali tidak memiliki omega. Jelas Zero ada di N, dan jika y ada di N, Succ (y) ada di N, tapi N lebih kecil dari Nat yang merupakan kontradiksi. Jadi, omega tidak ada di Nat.
Atau, mungkin lebih bermanfaat bagi ilmuwan komputer:
Diberikan beberapa himpunan "a", set "Daftar a" didefinisikan sebagai himpunan terkecil sehingga "Nil" ada dalam Daftar a, dan bahwa jika xs ada dalam Daftar a dan x adalah dalam "Cons x xs" ada dalam Daftar a.
Yang sesuai dengan sesuatu seperti
type 'a list = Nil | Cons of 'a * 'a list
Kata yang digunakan di sini adalah "terkecil". Jika kita tidak mengatakan "terkecil" kita tidak akan memiliki cara untuk mengatakan jika set Nat mengandung pisang!
Lagi,
zeros = Cons(Zero,zeros)
bukan definisi yang valid untuk daftar nats, seperti omega bukan Nat yang valid.
Mendefinisikan data secara induktif seperti ini memungkinkan kita untuk mendefinisikan fungsi yang bekerja dengannya menggunakan rekursi
let rec plus a b = match a with
| Zero -> b
| Succ(c) -> let r = plus c b in Succ(r)
kita kemudian dapat membuktikan fakta tentang ini, seperti "plus a Nol = a" menggunakan induksi (khusus, induksi struktural)
Bukti kami hasil dengan induksi struktural pada a.
Untuk kasing dasar, biarkan nol. plus Zero Zero = match Zero with |Zero -> Zero | Succ(c) -> let r = plus c b in Succ(r)
jadi kami tahu plus Zero Zero = Zero
. Biarkan a
menjadi nat. Asumsikan hipotesis induktif itu plus a Zero = a
. Kami sekarang menunjukkan bahwa plus (Succ(a)) Zero = Succ(a)
ini jelas sejak plus (Succ(a)) Zero = match a with |Zero -> Zero | Succ(a) -> let r = plus a Zero in Succ(r) = let r = a in Succ(r) = Succ(a)
demikian, dengan induksi plus a Zero = a
untuk semua a
dalam nat
Tentu saja kita dapat membuktikan hal-hal yang lebih menarik, tetapi ini adalah gagasan umum.
Sejauh ini kami telah menangani data yang terdefinisi secara induktif yang kami dapatkan dengan membiarkannya menjadi set "terkecil". Jadi sekarang kita ingin bekerja dengan codata yang didefinisikan secara coinductivly yang kita dapatkan dengan membiarkannya menjadi set terbesar.
Begitu
Biarkan satu set. Himpunan "Aliran a" didefinisikan sebagai himpunan terbesar sehingga untuk setiap x dalam aliran a, x terdiri dari pasangan berurutan (kepala, ekor) sehingga kepala berada di dalam dan ekor berada dalam aliran dari
Dalam Haskell kita akan menyatakan ini sebagai
data Stream a = Stream a (Stream a) --"data" not "newtype"
Sebenarnya, di Haskell kita menggunakan daftar bawaan secara normal, yang bisa berupa pasangan yang dipesan atau daftar kosong.
data [a] = [] | a:[a]
Pisang juga bukan anggota jenis ini, karena pisang bukan pasangan yang dipesan atau daftar kosong. Tapi, sekarang bisa kita katakan
ones = 1:ones
dan ini adalah definisi yang benar-benar valid. Terlebih lagi, kita dapat melakukan rekursi bersama pada data bersama ini. Sebenarnya, dimungkinkan untuk suatu fungsi bersifat ko-rekursif dan rekursif. Sementara rekursi didefinisikan oleh fungsi yang memiliki domain yang terdiri dari data, rekursi hanya berarti memiliki domain bersama (juga disebut rentang) yaitu data bersama. Rekursi primitif berarti selalu "memanggil diri sendiri" pada data yang lebih kecil hingga mencapai beberapa data terkecil. Rekursi bersama primitif selalu "menyebut dirinya" pada data yang lebih besar atau sama dengan apa yang Anda miliki sebelumnya.
ones = 1:ones
secara primitif bersifat rekursif. Sementara fungsi map
(semacam "pendahuluan" dalam bahasa imperatif) keduanya bersifat rekursif primitif (semacam) dan rekursif secara primitif.
map :: (a -> b) -> [a] -> [b]
map f [] = []
map f (x:xs) = (f x):map f xs
Hal yang sama berlaku untuk fungsi zipWith
yang mengambil fungsi dan sepasang daftar dan menggabungkannya menggunakan fungsi itu.
zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
zipWith f (a:as) (b:bs) = (f a b):zipWith f as bs
zipWith _ _ _ = [] --base case
contoh klasik dari bahasa fungsional adalah deret Fibonacci
fib 0 = 0
fib 1 = 1
fib n = (fib (n-1)) + (fib (n-2))
yang secara primitif bersifat rekursif, tetapi dapat diekspresikan secara lebih elegan sebagai daftar tanpa batas
fibs = 0:1:zipWith (+) fibs (tail fibs)
fib' n = fibs !! n --the !! is haskell syntax for index at
contoh menarik dari induksi / coinduction membuktikan bahwa kedua definisi ini menghitung hal yang sama. Ini dibiarkan sebagai latihan untuk pembaca.