F-cebirleri ve F-kömürler, endüktif tipler (veya özyinelemeli tipler ) hakkında akıl yürütmede etkili olan matematiksel yapılardır .
F-cebiri
İlk önce F-cebirleriyle başlayacağız. Mümkün olduğunca basit olmaya çalışacağım.
Sanırım yinelemeli tipin ne olduğunu biliyorsun. Örneğin, bu tamsayıların listesi için bir türdür:
data IntList = Nil | Cons (Int, IntList)
Özyinelemeli olduğu açıktır - gerçekten de tanımı kendini ifade eder. Tanımı, aşağıdaki türlere sahip iki veri yapıcısından oluşur:
Nil :: () -> IntList
Cons :: (Int, IntList) -> IntList
Ben sadece Nilas () -> IntListdeğil yazdım IntList. Bunlar aslında teorik açıdan eşdeğer türlerdir, çünkü ()türün sadece bir sakini vardır.
Bu işlevlerin imzalarını daha kuramsal bir şekilde yazarsak,
Nil :: 1 -> IntList
Cons :: Int × IntList -> IntList
burada 1bir birim grubu (bir elemanı ile ayarlanır) ve A × Bişlem iki set enine bir üründür Ave B(olduğundan, çift grubu tüm elemanları geçer ve tüm elemanları geçer(a, b)aAbB ).
İki kümenin ayrık birlik Ave Bkümesidir A | Bsetleri birleşmedir {(a, 1) : a in A}ve {(b, 2) : b in B}. Esasen her iki tüm unsurların bütünüdür Ave Bmensup ya olarak değil, 'işaretlenmiş' bu unsurların her biri ile Aya da B, bu yüzden biz herhangi bir unsur almaya gelirken A | Bbiz hemen bu eleman gelip gelmediğini bilecek Aveya gelenB .
'Birleştirebiliriz' Nilve Consfonksiyonlar, böylece bir set üzerinde çalışan tek bir fonksiyon oluşturacaklar 1 | (Int × IntList):
Nil|Cons :: 1 | (Int × IntList) -> IntList
Gerçekten de, Nil|Consfonksiyon ()değere uygulanırsa (ki bu açık bir şekilde 1 | (Int × IntList)kümeye aittir ), sanki sanki öyle davranır Nil; Nil|Consherhangi bir tür değere uygulanırsa (Int, IntList)(bu değerler kümede de yer alıyorsa) olarak 1 | (Int × IntList)davranır Cons.
Şimdi başka bir veri türü düşünün:
data IntTree = Leaf Int | Branch (IntTree, IntTree)
Aşağıdaki kuruculara sahiptir:
Leaf :: Int -> IntTree
Branch :: (IntTree, IntTree) -> IntTree
ayrıca tek bir fonksiyonda birleştirilebilir:
Leaf|Branch :: Int | (IntTree × IntTree) -> IntTree
Bu joinedfonksiyonların her ikisinin de benzer tipte olduğu görülebilir : her ikisi de
f :: F T -> T
burada Feden türü alır ve aşağıdakilerden oluşmaktadır daha karmaşık tip verir dönüşümün bir tür xve |işlemleri, kullanımları Tve muhtemelen diğer türleri. Örneğin, IntListve IntTree Faşağıdaki gibi görünür:
F1 T = 1 | (Int × T)
F2 T = Int | (T × T)
Herhangi bir cebirsel türün bu şekilde yazılabileceğini hemen fark edebiliriz. Aslında, bu yüzden 'cebir' olarak adlandırılırlar: diğer türlerin bir dizi 'toplamı' (birliği) ve 'ürünleri' (çapraz ürünler) içerirler.
Şimdi F-cebirini tanımlayabiliriz. F-cebiri sadece bir çifttir (T, f), burada Tbir türdür ve tipin fbir fonksiyonudur f :: F T -> T. Örneklerimizde F-cebirleri (IntList, Nil|Cons)ve (IntTree, Leaf|Branch). Bununla birlikte, bu tür ffonksiyonlara rağmen her bir F için aynı olduğunu Tve fkendilerinin keyfi olabileceğini unutmayın. Örneğin (String, g :: 1 | (Int x String) -> String)veya (Double, h :: Int | (Double, Double) -> Double)bazıları için gve hkarşılık gelen F için F-cebiridir.
Daha sonra F-cebir homomorfizmlerini ve daha sonra çok faydalı özelliklere sahip olan ilk F-cebirlerini tanıtabiliriz . Aslında, (IntList, Nil|Cons)bir ilk F1 cebiridir ve (IntTree, Leaf|Branch)bir ilk F2 cebiridir. Gerektiğinden daha karmaşık ve soyut oldukları için bu terimlerin ve özelliklerin tam tanımlarını sunmayacağım.
Bununla birlikte, örneğin, (IntList, Nil|Cons)F-cebiri olduğu gerçeği fold, bu tipte-benzeri bir fonksiyon tanımlamamıza izin verir . Bildiğiniz gibi, katlama, bazı özyinelemeli veri tipini sonlu bir değere dönüştüren bir işlemdir. Örneğin, bir tamsayı listesini listedeki tüm öğelerin toplamı olan tek bir değere katlayabiliriz:
foldr (+) 0 [1, 2, 3, 4] -> 1 + 2 + 3 + 4 = 10
Bu işlemi herhangi bir özyinelemeli veri türü üzerinde genelleştirmek mümkündür.
Aşağıdakiler foldrişlevin bir imzasıdır :
foldr :: ((a -> b -> b), b) -> [a] -> b
İlk iki argümanı sonuncusundan ayırmak için kaşlı ayraç kullandığımı unutmayın. Bu gerçek bir foldrişlev değildir , ancak onun için izomorfiktir (yani, kolayca diğerinden alabilirsiniz ve tersi). Kısmen başvuruda foldrşu imza bulunur:
foldr ((+), 0) :: [Int] -> Int
Bunun tamsayıların listesini alıp tek bir tamsayı döndüren bir işlev olduğunu görebiliriz. Bu işlevi türümüze göre tanımlayalım IntList.
sumFold :: IntList -> Int
sumFold Nil = 0
sumFold (Cons x xs) = x + sumFold xs
Bu fonksiyonun iki kısımdan oluştuğunu görüyoruz: ilk kısım bu fonksiyonun bir Nilkısmındaki davranışını, IntListikinci kısım fonksiyonun bir Conskısmındaki davranışını tanımlar .
Şimdi, Haskell'de değil, cebirsel tiplerin doğrudan tip imzalarında kullanılmasına izin veren bir dilde programladığımızı varsayalım (iyi, teknik olarak Haskell, cebirsel tiplerin tuples ve Either a bveri tipi aracılığıyla kullanılmasına izin veriyor , ancak bu gereksiz ayrıntılara yol açacak). Bir işlev düşünün:
reductor :: () | (Int × Int) -> Int
reductor () = 0
reductor (x, s) = x + s
F-cebirinin tanımında olduğu gibi reductortipin bir fonksiyonu olduğu görülebilir F1 Int -> Int! Aslında, çift (Int, reductor)bir F1 cebiridir.
Çünkü IntListbir başlangıç F1 cebir her tür için, olduğu Tve her bir işlev için r :: F1 T -> Tadında bir işlevi vardır vardır, catamorphism için r, dönüştürür IntListiçin T, ve bu fonksiyon benzersizdir. Gerçekten de, bizim örneğimizde bir catamorphism reductorolduğunu sumFold. Nasıl reductorve sumFoldbenzer olduklarına dikkat edin: neredeyse aynı yapıya sahiptirler! Olarak reductortanımlı sparametre kullanım (tip olan tekabül Thesaplama sonucunun kullanımına tekabül) sumFold xsiçinde sumFoldtanımı.
Sadece daha açık hale getirmek ve deseni görmenize yardımcı olmak için, başka bir örnek ve yine ortaya çıkan katlama işlevinden başlıyoruz. appendİlk argümanını ikincisine ekleyen işlevi düşünün :
(append [4, 5, 6]) [1, 2, 3] = (foldr (:) [4, 5, 6]) [1, 2, 3] -> [1, 2, 3, 4, 5, 6]
Şu şekilde görünüyor IntList:
appendFold :: IntList -> IntList -> IntList
appendFold ys () = ys
appendFold ys (Cons x xs) = x : appendFold ys xs
Yine, redüktörü yazmaya çalışalım:
appendReductor :: IntList -> () | (Int × IntList) -> IntList
appendReductor ys () = ys
appendReductor ys (x, rs) = x : rs
appendFoldbir catamorphism olan appendReductorolan dönüşümleri IntListiçine IntList.
Yani, esasen, F-cebirleri, yinelemeli veri yapıları, yani yapılarımızı bir değere indiren operasyonlar üzerinde 'kıvrımlar' tanımlamamıza izin verir.
F-coalgebras
F-kömürgebras, F-cebirleri için 'ikili' terimdir. unfoldsYinelemeli veri türleri, yani bir değerden yinelemeli yapılar inşa etmenin bir yolunu tanımlamamıza izin veriyorlar .
Aşağıdaki türe sahip olduğunuzu varsayalım:
data IntStream = Cons (Int, IntStream)
Bu sonsuz bir tamsayı akışıdır. Tek kurucusu aşağıdaki tiptedir:
Cons :: (Int, IntStream) -> IntStream
Veya setler açısından
Cons :: Int × IntStream -> IntStream
Haskell veri yapıcıları üzerinde desen eşleşmesine izin verir, böylece IntStreams üzerinde çalışan aşağıdaki işlevleri tanımlayabilirsiniz :
head :: IntStream -> Int
head (Cons (x, xs)) = x
tail :: IntStream -> IntStream
tail (Cons (x, xs)) = xs
Doğal olarak bu işlevleri tek tip işlevde 'birleştirebilirsiniz' IntStream -> Int × IntStream:
head&tail :: IntStream -> Int × IntStream
head&tail (Cons (x, xs)) = (x, xs)
Fonksiyonun sonucunun tipimizin cebirsel temsiliyle nasıl çakıştığına dikkat edin IntStream. Benzer şey diğer özyinelemeli veri türleri için de yapılabilir. Belki de deseni zaten fark ettiniz. Tür fonksiyonlardan oluşan bir aileden söz ediyorum
g :: T -> F T
bir tür nerede T. Şu andan itibaren tanımlayacağız
F1 T = Int × T
Şimdi, F-coalgebra bir çift (T, g), Tbir tür ve gtip bir fonksiyonudur g :: T -> F T. Örneğin (IntStream, head&tail), bir F1 kömürüdür. Yine, tıpkı F-cebirlerinde olduğu gibi, gveT keyfi olabilir, örneğin, (String, h :: String -> Int x String)bazı h için de F1-kömürgebradır.
Tüm F-kömürler arasında ilk F-cebirlerine çift olan terminal F-kömürler de denir . Örneğin IntStream, bir terminal F kömürüdür. Bu, her tür Tve her işlev için p :: T -> F1 T, anamorfizm adı verilen ve dönüştüren bir fonksiyonun var olduğu anlamına gelir.T için IntStreamve böyle işlevini benzersizdir.
Verilen işlevden başlayarak ardışık bir tamsayı akışı oluşturan aşağıdaki işlevi göz önünde bulundurun:
nats :: Int -> IntStream
nats n = Cons (n, nats (n+1))
Şimdi bir işlevi inceleyelim natsBuilder :: Int -> F1 Int, yani natsBuilder :: Int -> Int × Int:
natsBuilder :: Int -> Int × Int
natsBuilder n = (n, n+1)
Yine, natsve arasında bazı benzerlikler görebiliriz natsBuilder. Daha önce redüktörler ve katlar ile gözlemlediğimiz bağlantıya çok benzer.natsiçin bir anamorfizmdir natsBuilder.
Başka bir örnek, bir değeri ve işlevi alan ve işlevin birbirini izleyen uygulamalarının akışını değere döndüren bir işlevdir:
iterate :: (Int -> Int) -> Int -> IntStream
iterate f n = Cons (n, iterate f (f n))
Oluşturucu işlevi şu şekildedir:
iterateBuilder :: (Int -> Int) -> Int -> Int × Int
iterateBuilder f n = (n, f n)
O zaman iterateiçin bir anamorfizm iterateBuilder.
Sonuç
Kısacası, F-cebirleri kıvrımların tanımlanmasına, yani özyinelemeli yapıyı tek bir değere indirgeyen operasyonlara izin verir ve F-kömürgebras bunun tersini yapmasına izin verir: tek bir değerden [potansiyel olarak] sonsuz bir yapı inşa eder.
Aslında Haskell'de F-cebirleri ve F-kömürgebrasları çakışır. Bu, her tipte 'alt' değerin varlığının bir sonucu olan çok güzel bir özelliktir. Yani Haskell'de her kıvrımlı tip için hem kıvrımlar hem de katlar oluşturulabilir. Bununla birlikte, bunun arkasındaki teorik model, yukarıda sunduğumdan daha karmaşıktır, bu yüzden kasten kaçındım.
Bu yardımcı olur umarım.