F-algebras và F-thangebras là các cấu trúc toán học là công cụ lý luận về các loại quy nạp (hoặc các loại đệ quy ).
Đại số F
Chúng ta sẽ bắt đầu đầu tiên với F-algebras. Tôi sẽ cố gắng đơn giản nhất có thể.
Tôi đoán bạn biết thế nào là một kiểu đệ quy. Ví dụ: đây là loại cho danh sách các số nguyên:
data IntList = Nil | Cons (Int, IntList)
Rõ ràng là nó là đệ quy - thực sự, định nghĩa của nó đề cập đến chính nó. Định nghĩa của nó bao gồm hai hàm tạo dữ liệu, có các loại sau:
Nil :: () -> IntList
Cons :: (Int, IntList) -> IntList
Lưu ý rằng tôi đã viết loại Nil
như () -> IntList
, không chỉ đơn giản IntList
. Trên thực tế đây là những loại tương đương theo quan điểm lý thuyết, bởi vì()
loại chỉ có một cư dân.
Nếu chúng ta viết chữ ký của các hàm này theo cách lý thuyết tập hợp hơn, chúng ta sẽ nhận được
Nil :: 1 -> IntList
Cons :: Int × IntList -> IntList
nơi 1
được một bộ phận (thiết lập với một phần tử) và A × B
hoạt động là một sản phẩm chéo của hai bộ A
và B
(có nghĩa là, bộ đôi (a, b)
nơi a
đi qua tất cả các yếu tố của A
và b
đi qua tất cả các yếu tố củaB
).
Liên hiệp hai tập hợp A
và B
là một tập hợp A | B
là tập hợp của tập hợp {(a, 1) : a in A}
và {(b, 2) : b in B}
. Về cơ bản nó là một tập hợp của tất cả các yếu tố từ cả hai A
và B
, nhưng với mỗi người trong số các yếu tố này 'đánh dấu' là thuộc về một trong hai A
hoặc B
, vì vậy khi chúng ta chọn bất kỳ phần tử từ A | B
chúng tôi sẽ ngay lập tức biết yếu tố này xuất phát từ A
hoặc từB
.
Chúng ta có thể 'tham gia' Nil
và các Cons
chức năng, vì vậy chúng sẽ tạo thành một chức năng duy nhất hoạt động trên một tập hợp 1 | (Int × IntList)
:
Nil|Cons :: 1 | (Int × IntList) -> IntList
Thật vậy, nếu Nil|Cons
hàm được áp dụng cho ()
giá trị (mà, rõ ràng, thuộc về 1 | (Int × IntList)
tập hợp), thì nó hoạt động như thể nó là Nil
; nếu Nil|Cons
được áp dụng cho bất kỳ giá trị nào của loại (Int, IntList)
(các giá trị đó cũng nằm trong tập hợp 1 | (Int × IntList)
, nó hoạt động như Cons
.
Bây giờ hãy xem xét một kiểu dữ liệu khác:
data IntTree = Leaf Int | Branch (IntTree, IntTree)
Nó có các hàm tạo sau:
Leaf :: Int -> IntTree
Branch :: (IntTree, IntTree) -> IntTree
cũng có thể được nối vào một chức năng:
Leaf|Branch :: Int | (IntTree × IntTree) -> IntTree
Có thể thấy rằng cả hai joined
chức năng này có loại tương tự nhau: cả hai đều trông giống như
f :: F T -> T
trong đó F
một loại biến đổi lấy loại của chúng ta và đưa ra loại phức tạp hơn, bao gồm x
và các |
hoạt động, tập quán T
và có thể các loại khác. Ví dụ, cho IntList
và IntTree
F
trông như sau:
F1 T = 1 | (Int × T)
F2 T = Int | (T × T)
Chúng ta có thể nhận thấy ngay rằng bất kỳ loại đại số nào cũng có thể được viết theo cách này. Thật vậy, đó là lý do tại sao chúng được gọi là 'đại số': chúng bao gồm một số 'tổng' (công đoàn) và 'sản phẩm' (sản phẩm chéo) thuộc các loại khác.
Bây giờ chúng ta có thể định nghĩa đại số F. Đại số F chỉ là một cặp (T, f)
, trong đó T
có một số loại và f
là một hàm của loại f :: F T -> T
. Trong ví dụ của chúng tôi, đại số F là (IntList, Nil|Cons)
và (IntTree, Leaf|Branch)
. Tuy nhiên, lưu ý rằng mặc dù loại f
chức năng đó là giống nhau cho mỗi F T
và f
bản thân chúng có thể tùy ý. Ví dụ, (String, g :: 1 | (Int x String) -> String)
hoặc (Double, h :: Int | (Double, Double) -> Double)
đối với một số g
và h
cũng là đại số F cho F. tương ứng
Sau đó, chúng tôi có thể giới thiệu đồng cấu F-đại số và sau đó là đại số F ban đầu , có tính chất rất hữu ích. Trong thực tế, (IntList, Nil|Cons)
là một đại số F1 ban đầu, và(IntTree, Leaf|Branch)
là một đại số F2 ban đầu. Tôi sẽ không trình bày các định nghĩa chính xác về các điều khoản và tính chất này vì chúng phức tạp và trừu tượng hơn mức cần thiết.
Tuy nhiên, thực tế, giả sử, (IntList, Nil|Cons)
là đại số F cho phép chúng ta xác định fold
hàm giống như trên loại này. Như bạn đã biết, Fold là một loại hoạt động biến đổi một số kiểu dữ liệu đệ quy trong một giá trị hữu hạn. Ví dụ: chúng ta có thể gấp một danh sách số nguyên thành một giá trị duy nhất là tổng của tất cả các phần tử trong danh sách:
foldr (+) 0 [1, 2, 3, 4] -> 1 + 2 + 3 + 4 = 10
Có thể khái quát hóa hoạt động như vậy trên bất kỳ kiểu dữ liệu đệ quy nào.
Sau đây là chữ ký của foldr
chức năng:
foldr :: ((a -> b -> b), b) -> [a] -> b
Lưu ý rằng tôi đã sử dụng dấu ngoặc nhọn để tách hai đối số đầu tiên khỏi đối số cuối cùng. Đây không phải là foldr
chức năng thực sự , nhưng nó là đẳng cấu với nó (nghĩa là bạn có thể dễ dàng lấy cái này từ cái kia và ngược lại). Áp dụng một phần foldr
sẽ có chữ ký sau:
foldr ((+), 0) :: [Int] -> Int
Chúng ta có thể thấy rằng đây là một hàm lấy danh sách các số nguyên và trả về một số nguyên duy nhất. Hãy xác định chức năng như vậy theo IntList
loại của chúng tôi .
sumFold :: IntList -> Int
sumFold Nil = 0
sumFold (Cons x xs) = x + sumFold xs
Chúng ta thấy rằng hàm này bao gồm hai phần: phần thứ nhất xác định hành vi của hàm này trên Nil
một phần IntList
và phần thứ hai xác định hành vi của hàm trên Cons
một phần.
Bây giờ giả sử rằng chúng ta đang lập trình không phải bằng Haskell mà bằng một số ngôn ngữ cho phép sử dụng các loại đại số trực tiếp trong chữ ký loại (tốt, về mặt kỹ thuật, Haskell cho phép sử dụng các loại đại số thông qua tuples và Either a b
datatype, nhưng điều này sẽ dẫn đến tính dài dòng không cần thiết). Hãy xem xét một chức năng:
reductor :: () | (Int × Int) -> Int
reductor () = 0
reductor (x, s) = x + s
Có thể thấy đó reductor
là một hàm của kiểu F1 Int -> Int
, giống như trong định nghĩa của đại số F! Thật vậy, cặp (Int, reductor)
là một đại số F1.
Bởi vì IntList
là một đại số F1 ban đầu, cho mỗi loại T
và cho mỗi hàm r :: F1 T -> T
tồn tại một hàm, được gọi là catamorphism cho r
, chuyển đổi IntList
thành T
và hàm đó là duy nhất. Thật vậy, trong ví dụ của chúng tôi một catamorphism cho reductor
là sumFold
. Lưu ý cách reductor
và sumFold
tương tự nhau: chúng có cấu trúc gần như giống nhau! Trong reductor
định nghĩa s
sử dụng tham số (loại tương ứng T
) tương ứng với việc sử dụng kết quả tính toán sumFold xs
trong sumFold
định nghĩa.
Để làm cho nó rõ ràng hơn và giúp bạn nhìn thấy mẫu, đây là một ví dụ khác, và chúng ta lại bắt đầu từ chức năng gấp kết quả. Xem xét append
hàm nối thêm đối số thứ nhất của nó với đối số thứ hai:
(append [4, 5, 6]) [1, 2, 3] = (foldr (:) [4, 5, 6]) [1, 2, 3] -> [1, 2, 3, 4, 5, 6]
Đây là cách nó trông như thế nào IntList
:
appendFold :: IntList -> IntList -> IntList
appendFold ys () = ys
appendFold ys (Cons x xs) = x : appendFold ys xs
Một lần nữa, chúng ta hãy thử viết ra reductor:
appendReductor :: IntList -> () | (Int × IntList) -> IntList
appendReductor ys () = ys
appendReductor ys (x, rs) = x : rs
appendFold
là một dị hình appendReductor
mà biến IntList
thành IntList
.
Vì vậy, về cơ bản, đại số F cho phép chúng ta xác định 'nếp gấp' trên các cơ sở dữ liệu đệ quy, nghĩa là các hoạt động làm giảm cấu trúc của chúng ta xuống một số giá trị.
F-thangebras
F-thangebras được gọi là thuật ngữ 'kép' cho F-algebras. Chúng cho phép chúng ta định nghĩa unfolds
cho các kiểu dữ liệu đệ quy, nghĩa là một cách để xây dựng các cấu trúc đệ quy từ một số giá trị.
Giả sử bạn có loại sau:
data IntStream = Cons (Int, IntStream)
Đây là một dòng vô tận của số nguyên. Phương thức khởi tạo duy nhất của nó có loại sau:
Cons :: (Int, IntStream) -> IntStream
Hoặc, về mặt bộ
Cons :: Int × IntStream -> IntStream
Haskell cho phép bạn khớp mẫu trên các hàm tạo dữ liệu, do đó bạn có thể xác định các hàm sau hoạt động trên IntStream
s:
head :: IntStream -> Int
head (Cons (x, xs)) = x
tail :: IntStream -> IntStream
tail (Cons (x, xs)) = xs
Bạn có thể tự nhiên 'tham gia' các chức năng này thành một chức năng loại IntStream -> Int × IntStream
:
head&tail :: IntStream -> Int × IntStream
head&tail (Cons (x, xs)) = (x, xs)
Lưu ý rằng kết quả của hàm trùng với biểu diễn đại số của IntStream
loại của chúng tôi . Điều tương tự cũng có thể được thực hiện cho các loại dữ liệu đệ quy khác. Có lẽ bạn đã nhận thấy mô hình. Tôi đang đề cập đến một họ các loại chức năng
g :: T -> F T
nơi T
là một số loại. Từ bây giờ chúng tôi sẽ xác định
F1 T = Int × T
Bây giờ, F-thangebra là một cặp (T, g)
, trong đó T
là một loại và g
là một chức năng của loại g :: T -> F T
. Ví dụ, (IntStream, head&tail)
là một con ngựa vằn F1. Một lần nữa, giống như trong F-algebras, g
và T
có thể tùy ý, chẳng hạn, (String, h :: String -> Int x String)
cũng là một đại số F1 cho một số h.
Trong số tất cả các F-thangebras có cái gọi là F-thangebras cuối , là kép của F-algebras ban đầu. Ví dụ, IntStream
là một F-thang thiết bị đầu cuối. Điều này có nghĩa là đối với mọi loại T
và cho mọi chức năng đều p :: T -> F1 T
tồn tại một hàm, được gọi là biến hình , chuyển đổi T
thành IntStream
và hàm đó là duy nhất.
Hãy xem xét hàm sau, tạo ra một luồng các số nguyên liên tiếp bắt đầu từ một số đã cho:
nats :: Int -> IntStream
nats n = Cons (n, nats (n+1))
Bây giờ hãy kiểm tra một chức năng natsBuilder :: Int -> F1 Int
, đó là natsBuilder :: Int -> Int × Int
:
natsBuilder :: Int -> Int × Int
natsBuilder n = (n, n+1)
Một lần nữa, chúng ta có thể thấy một số điểm tương đồng giữa nats
và natsBuilder
. Nó rất giống với kết nối mà chúng ta đã quan sát được với các bộ khử và nếp gấp trước đó. nats
là một sự biến thái cho natsBuilder
.
Một ví dụ khác, một hàm lấy một giá trị và một hàm và trả về một luồng các ứng dụng kế tiếp của hàm cho giá trị:
iterate :: (Int -> Int) -> Int -> IntStream
iterate f n = Cons (n, iterate f (f n))
Hàm xây dựng của nó là hàm sau:
iterateBuilder :: (Int -> Int) -> Int -> Int × Int
iterateBuilder f n = (n, f n)
Sau đó iterate
là một biến thái cho iterateBuilder
.
Phần kết luận
Vì vậy, trong ngắn hạn, đại số F cho phép xác định các nếp gấp, nghĩa là các hoạt động làm giảm cấu trúc đệ quy xuống một giá trị duy nhất và F-thangebras cho phép thực hiện ngược lại: xây dựng cấu trúc vô hạn [có khả năng] từ một giá trị duy nhất.
Trong thực tế ở Haskell F-algebras và F-thangebras trùng khớp. Đây là một tài sản rất đẹp, là kết quả của sự hiện diện của giá trị 'dưới cùng' trong mỗi loại. Vì vậy, trong Haskell cả nếp gấp và mở ra có thể được tạo cho mọi loại đệ quy. Tuy nhiên, mô hình lý thuyết đằng sau điều này phức tạp hơn mô hình tôi đã trình bày ở trên, vì vậy tôi đã cố tình tránh nó.
Hi vọng điêu nay co ich.