F-algebras dan F-coalgebras adalah struktur matematika yang berperan dalam penalaran tentang tipe induktif (atau tipe rekursif ).
F-aljabar
Kami akan mulai dulu dengan F-algebras. Saya akan berusaha sesederhana mungkin.
Saya kira Anda tahu apa itu tipe rekursif. Misalnya, ini adalah tipe untuk daftar bilangan bulat:
data IntList = Nil | Cons (Int, IntList)
Jelas bahwa itu adalah rekursif - memang, definisi itu merujuk pada dirinya sendiri. Definisinya terdiri dari dua konstruktor data, yang memiliki tipe berikut:
Nil :: () -> IntList
Cons :: (Int, IntList) -> IntList
Perhatikan bahwa saya telah menulis tipe Nilas () -> IntList, bukan hanya IntList. Ini sebenarnya adalah tipe yang setara dari sudut pandang teoretis, karena ()tipe hanya memiliki satu penduduk.
Jika kita menulis tanda tangan dari fungsi-fungsi ini dengan cara yang lebih teoritis, kita akan mendapatkannya
Nil :: 1 -> IntList
Cons :: Int × IntList -> IntList
di mana 1satu set unit (set dengan satu elemen) dan A × Boperasi adalah produk silang dari dua set Adan B(yaitu, set pasangan (a, b)mana amelewati semua elemen Adan bmelewati semua elemen dari B).
Disjoint penyatuan dua set Adan Bmerupakan set A | Byang merupakan penyatuan set {(a, 1) : a in A}dan {(b, 2) : b in B}. Pada dasarnya itu adalah satu set semua elemen dari keduanya Adan B, tetapi dengan masing-masing elemen ini 'ditandai' sebagai milik salah satu Aatau B, jadi ketika kita mengambil elemen dari A | Bkita akan segera tahu apakah elemen ini berasal Aatau dari B.
Kami dapat 'bergabung' Nildan Consfungsi, sehingga mereka akan membentuk satu fungsi yang bekerja pada set 1 | (Int × IntList):
Nil|Cons :: 1 | (Int × IntList) -> IntList
Memang, jika Nil|Consfungsi diterapkan pada ()nilai (yang, jelas, milik 1 | (Int × IntList)set), maka berperilaku seolah-olah itu Nil; jika Nil|Consditerapkan pada nilai tipe apa pun (Int, IntList)(nilai tersebut juga ada di set 1 | (Int × IntList), itu berlaku sebagai Cons.
Sekarang pertimbangkan tipe data lain:
data IntTree = Leaf Int | Branch (IntTree, IntTree)
Ini memiliki konstruktor berikut:
Leaf :: Int -> IntTree
Branch :: (IntTree, IntTree) -> IntTree
yang juga bisa digabung menjadi satu fungsi:
Leaf|Branch :: Int | (IntTree × IntTree) -> IntTree
Dapat dilihat bahwa kedua joinedfungsi ini memiliki tipe yang sama: keduanya terlihat seperti
f :: F T -> T
di mana Fadalah jenis transformasi yang mengambil tipe kita dan memberikan tipe yang lebih kompleks, yang terdiri dari xdan |operasi, penggunaan Tdan mungkin tipe lainnya. Misalnya, untuk IntListdan IntTree Fterlihat sebagai berikut:
F1 T = 1 | (Int × T)
F2 T = Int | (T × T)
Kita dapat segera melihat bahwa semua jenis aljabar dapat ditulis dengan cara ini. Memang, itulah sebabnya mereka disebut 'aljabar': mereka terdiri dari sejumlah 'jumlah' (serikat pekerja) dan 'produk' (produk silang) dari jenis lain.
Sekarang kita dapat mendefinisikan aljabar F. Aljabar F hanyalah sepasang (T, f), di mana Tada beberapa jenis dan fmerupakan fungsi dari jenis f :: F T -> T. Dalam contoh-contoh kami F-aljabar adalah (IntList, Nil|Cons)dan (IntTree, Leaf|Branch). Namun, perlu diketahui bahwa meskipun jenis ffungsinya sama untuk masing-masing F, Tdan fdirinya sendiri dapat arbitrer. Misalnya, (String, g :: 1 | (Int x String) -> String)atau (Double, h :: Int | (Double, Double) -> Double)untuk beberapa gdan hjuga F-aljabar untuk F. yang sesuai
Setelah itu kita dapat memperkenalkan homomorfisme aljabar F-aljabar dan kemudian memulai aljabar F-aljabar , yang memiliki sifat yang sangat berguna. Sebenarnya, (IntList, Nil|Cons)adalah aljabar F1 awal, dan (IntTree, Leaf|Branch)merupakan aljabar F2 awal. Saya tidak akan menyajikan definisi yang tepat dari istilah dan properti ini karena mereka lebih kompleks dan abstrak daripada yang dibutuhkan.
Meskipun demikian, fakta bahwa, katakanlah, (IntList, Nil|Cons)aljabar-F memungkinkan kita untuk mendefinisikan foldfungsi seperti pada tipe ini. Seperti yang Anda ketahui, fold adalah semacam operasi yang mengubah beberapa tipe data rekursif dalam satu nilai terbatas. Misalnya, kita bisa melipat daftar bilangan bulat menjadi nilai tunggal yang merupakan jumlah semua elemen dalam daftar:
foldr (+) 0 [1, 2, 3, 4] -> 1 + 2 + 3 + 4 = 10
Dimungkinkan untuk menggeneralisasi operasi semacam itu pada setiap tipe data rekursif.
Berikut ini adalah tanda tangan foldrfungsi:
foldr :: ((a -> b -> b), b) -> [a] -> b
Perhatikan bahwa saya telah menggunakan kawat gigi untuk memisahkan dua argumen pertama dari yang terakhir. Ini bukan foldrfungsi nyata , tetapi isomorfis dengannya (yaitu, Anda dapat dengan mudah mendapatkan satu dari yang lain dan sebaliknya). Sebagian diterapkan foldrakan memiliki tanda tangan berikut:
foldr ((+), 0) :: [Int] -> Int
Kita dapat melihat bahwa ini adalah fungsi yang mengambil daftar bilangan bulat dan mengembalikan bilangan bulat tunggal. Mari kita definisikan fungsi seperti itu dalam hal IntListtipe kita .
sumFold :: IntList -> Int
sumFold Nil = 0
sumFold (Cons x xs) = x + sumFold xs
Kita melihat bahwa fungsi ini terdiri dari dua bagian: bagian pertama mendefinisikan perilaku fungsi ini pada Nilbagian dari IntList, dan bagian kedua mendefinisikan perilaku fungsi pada Consbagian.
Sekarang anggaplah kita memprogram bukan dalam Haskell tetapi dalam beberapa bahasa yang memungkinkan penggunaan tipe-tipe aljabar secara langsung dalam tipe tanda tangan (yah, secara teknis Haskell memungkinkan penggunaan tipe-tipe aljabar melalui tupel dan Either a btipe data, tetapi ini akan mengarah pada verbositas yang tidak perlu). Pertimbangkan fungsi:
reductor :: () | (Int × Int) -> Int
reductor () = 0
reductor (x, s) = x + s
Dapat dilihat bahwa reductorini adalah fungsi dari tipe F1 Int -> Int, seperti dalam definisi aljabar F! Memang, pasangan (Int, reductor)adalah aljabar F1.
Karena IntListmerupakan F1-aljabar awal, untuk setiap jenis Tdan untuk setiap fungsi r :: F1 T -> Tterdapat fungsi, yang disebut catamorphism untuk r, yang bertobat IntListuntuk T, dan fungsi tersebut adalah unik. Memang, dalam contoh kita, katamorfisme reductoradalah sumFold. Perhatikan bagaimana reductordan sumFoldmirip: mereka memiliki struktur yang hampir sama! Dalam reductordefinisi sparameter, penggunaan (tipe yang sesuai dengan T) sesuai dengan penggunaan hasil perhitungan sumFold xsdalam sumFolddefinisi.
Hanya untuk membuatnya lebih jelas dan membantu Anda melihat polanya, berikut adalah contoh lain, dan kita kembali mulai dari fungsi lipat yang dihasilkan. Pertimbangkan appendfungsi yang menambahkan argumen pertama ke argumen kedua:
(append [4, 5, 6]) [1, 2, 3] = (foldr (:) [4, 5, 6]) [1, 2, 3] -> [1, 2, 3, 4, 5, 6]
Ini tampilannya pada kita IntList:
appendFold :: IntList -> IntList -> IntList
appendFold ys () = ys
appendFold ys (Cons x xs) = x : appendFold ys xs
Sekali lagi, mari kita coba menuliskan reduktor:
appendReductor :: IntList -> () | (Int × IntList) -> IntList
appendReductor ys () = ys
appendReductor ys (x, rs) = x : rs
appendFoldadalah katamorfisme appendReductoryang ditransformasikan IntListmenjadi IntList.
Jadi, pada dasarnya, F-algebras memungkinkan kita untuk mendefinisikan 'lipatan' pada struktur data rekursif, yaitu operasi yang mengurangi struktur kita ke beberapa nilai.
F-coalgebras
F-coalgebras adalah istilah yang disebut 'ganda' untuk F-algebras. Mereka memungkinkan kita untuk menentukan unfoldstipe data rekursif, yaitu cara untuk membangun struktur rekursif dari beberapa nilai.
Misalkan Anda memiliki tipe berikut:
data IntStream = Cons (Int, IntStream)
Ini adalah aliran bilangan bulat tanpa batas. Satu-satunya konstruktor memiliki jenis berikut:
Cons :: (Int, IntStream) -> IntStream
Atau, dalam hal set
Cons :: Int × IntStream -> IntStream
Haskell memungkinkan Anda untuk mencocokkan pola pada konstruktor data, sehingga Anda dapat menentukan fungsi berikut yang bekerja pada IntStreams:
head :: IntStream -> Int
head (Cons (x, xs)) = x
tail :: IntStream -> IntStream
tail (Cons (x, xs)) = xs
Anda secara alami dapat 'menggabungkan' fungsi-fungsi ini menjadi satu fungsi tipe IntStream -> Int × IntStream:
head&tail :: IntStream -> Int × IntStream
head&tail (Cons (x, xs)) = (x, xs)
Perhatikan bagaimana hasil fungsi bertepatan dengan representasi aljabar dari IntStreamtipe kita . Hal serupa juga dapat dilakukan untuk tipe data rekursif lainnya. Mungkin Anda sudah memperhatikan polanya. Saya mengacu pada keluarga fungsi tipe
g :: T -> F T
dimana Tbeberapa tipe. Mulai sekarang kita akan mendefinisikan
F1 T = Int × T
Sekarang, F-coalgebra adalah pasangan (T, g), di mana Tadalah tipe dan gmerupakan fungsi dari tipe g :: T -> F T. Sebagai contoh, (IntStream, head&tail)adalah F1-coalgebra. Sekali lagi, seperti dalam F-algebras, gdan Tdapat berubah-ubah, misalnya, (String, h :: String -> Int x String)juga merupakan F1-coalgebra untuk beberapa jam.
Di antara semua F-coalgebras ada yang disebut terminal F-coalgebras , yang merupakan dwi-aljabar F-coal. Sebagai contoh, IntStreamadalah terminal F-coalgebra. Ini berarti bahwa untuk setiap jenis Tdan untuk setiap fungsi p :: T -> F1 Tterdapat fungsi, yang disebut anamorphism , yang dikonversi Tmenjadi IntStream, dan fungsi tersebut adalah unik.
Pertimbangkan fungsi berikut, yang menghasilkan aliran bilangan bulat berturut-turut mulai dari yang diberikan:
nats :: Int -> IntStream
nats n = Cons (n, nats (n+1))
Sekarang mari kita periksa fungsi natsBuilder :: Int -> F1 Int, yaitu natsBuilder :: Int -> Int × Int:
natsBuilder :: Int -> Int × Int
natsBuilder n = (n, n+1)
Sekali lagi, kita dapat melihat beberapa kesamaan antara natsdan natsBuilder. Ini sangat mirip dengan koneksi yang telah kami amati dengan reduktor dan lipatan sebelumnya. natsadalah anamorphism untuk natsBuilder.
Contoh lain, fungsi yang mengambil nilai dan fungsi dan mengembalikan aliran aplikasi berturut-turut fungsi ke nilai:
iterate :: (Int -> Int) -> Int -> IntStream
iterate f n = Cons (n, iterate f (f n))
Fungsi pembangunnya adalah sebagai berikut:
iterateBuilder :: (Int -> Int) -> Int -> Int × Int
iterateBuilder f n = (n, f n)
Maka iteratemerupakan anamorphism untuk iterateBuilder.
Kesimpulan
Jadi, singkatnya, F-aljabar memungkinkan untuk menentukan lipatan, yaitu operasi yang mengurangi struktur rekursif menjadi nilai tunggal, dan F-coalgebra memungkinkan untuk melakukan yang sebaliknya: membangun struktur [berpotensi] tak terbatas dari nilai tunggal.
Faktanya dalam Haskell F-algebras dan F-coalgebras bertepatan. Ini adalah properti yang sangat bagus yang merupakan konsekuensi dari keberadaan nilai 'bawah' di setiap jenis. Jadi di Haskell lipatan dan lipatan dapat dibuat untuk setiap jenis rekursif. Namun, model teoritis di balik ini lebih kompleks daripada yang saya sebutkan di atas, jadi saya sengaja menghindarinya.
Semoga ini membantu.