Có một số cách tốt để xem xét điều này. Điều dễ nhất đối với tôi là suy nghĩ về mối quan hệ giữa "Định nghĩa quy nạp" và "Định nghĩa cưỡng chế"
Một định nghĩa quy nạp của một bộ đi như thế này.
Tập hợp "Nat" được định nghĩa là tập nhỏ nhất sao cho "Không" nằm trong Nat và nếu n ở Nat thì "Succ n" nằm trong Nat.
Tương ứng với Ocaml sau
type nat = Zero | Succ of nat
Một điều cần lưu ý về định nghĩa này là một con số
omega = Succ(omega)
KHÔNG phải là thành viên của bộ này. Tại sao? Giả sử rằng, bây giờ hãy xem xét tập N có tất cả các yếu tố giống như Nat ngoại trừ nó không có omega. Rõ ràng Zero ở trong N và nếu y ở N, Succ (y) ở N, nhưng N nhỏ hơn Nat là một mâu thuẫn. Vì vậy, omega không có trong Nat.
Hoặc, có lẽ hữu ích hơn cho một nhà khoa học máy tính:
Với một số tập hợp "a", tập hợp "Danh sách của a" được định nghĩa là tập nhỏ nhất sao cho "Nil" nằm trong Danh sách của a và nếu xs nằm trong Danh sách của a và x nằm trong "Nhược điểm x xs" nằm trong danh sách của a.
Mà tương ứng với một cái gì đó như
type 'a list = Nil | Cons of 'a * 'a list
Từ phẫu thuật ở đây là "nhỏ nhất". Nếu chúng ta không nói "nhỏ nhất", chúng ta sẽ không có cách nào để nói nếu bộ Nat có chứa một quả chuối!
Lần nữa,
zeros = Cons(Zero,zeros)
không phải là một định nghĩa hợp lệ cho một danh sách các nats, giống như omega không phải là một Nat hợp lệ.
Xác định dữ liệu theo cách tự nhiên như thế này cho phép chúng ta xác định các hàm hoạt động trên nó bằng cách sử dụng đệ quy
let rec plus a b = match a with
| Zero -> b
| Succ(c) -> let r = plus c b in Succ(r)
sau đó chúng ta có thể chứng minh sự thật về điều này, như "cộng với số không = a" bằng cách sử dụng cảm ứng (cụ thể là cảm ứng cấu trúc)
Bằng chứng của chúng tôi tiến hành bằng cách cảm ứng cấu trúc trên a.
Đối với trường hợp cơ sở, hãy để một số không. plus Zero Zero = match Zero with |Zero -> Zero | Succ(c) -> let r = plus c b in Succ(r)
vì vậy chúng tôi biết plus Zero Zero = Zero
. Hãy a
là một nat. Giả sử giả thuyết quy nạp rằng plus a Zero = a
. Bây giờ chúng tôi cho thấy rằng plus (Succ(a)) Zero = Succ(a)
điều này là hiển nhiên vì 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)
Như vậy, bằng cách cảm ứng plus a Zero = a
cho tất cả mọi người a
trong nat
Tất nhiên chúng ta có thể chứng minh những điều thú vị hơn, nhưng đây là ý tưởng chung.
Cho đến nay chúng tôi đã xử lý dữ liệu được xác định theo quy nạp mà chúng tôi có được bằng cách để nó là tập hợp "nhỏ nhất". Vì vậy, bây giờ chúng tôi muốn làm việc với codata được xác định rõ ràng mà chúng tôi nhận được bằng cách để nó là tập hợp lớn nhất.
Vì thế
Hãy để một bộ. Tập hợp "Luồng của a" được định nghĩa là tập lớn nhất sao cho mỗi x trong luồng của a, x bao gồm cặp theo thứ tự (đầu, đuôi) sao cho đầu nằm trong a và đuôi nằm trong Luồng của a
Trong Haskell, chúng tôi sẽ thể hiện điều này như
data Stream a = Stream a (Stream a) --"data" not "newtype"
Trên thực tế, trong Haskell, chúng tôi sử dụng các danh sách tích hợp thông thường, có thể là một cặp theo thứ tự hoặc một danh sách trống.
data [a] = [] | a:[a]
Banana cũng không phải là thành viên của loại này, vì nó không phải là một cặp theo thứ tự hoặc danh sách trống. Nhưng, bây giờ chúng ta có thể nói
ones = 1:ones
và đây là một định nghĩa hoàn toàn hợp lệ. Hơn nữa, chúng ta có thể thực hiện đệ quy trên dữ liệu đồng này. Trên thực tế, một hàm có thể vừa là đệ quy vừa là đệ quy. Trong khi đệ quy được định nghĩa bởi hàm có một miền bao gồm dữ liệu, đồng quy đệ quy chỉ có nghĩa là nó có một miền đồng (còn gọi là phạm vi) là đồng dữ liệu. Đệ quy nguyên thủy có nghĩa là luôn luôn "tự gọi mình" trên dữ liệu nhỏ hơn cho đến khi đạt được một số dữ liệu nhỏ nhất. Đồng quy nguyên thủy luôn "tự gọi" dữ liệu lớn hơn hoặc bằng với những gì bạn có trước đây.
ones = 1:ones
là chủ yếu đồng đệ quy. Trong khi chức năng map
(giống như "foreach" trong các ngôn ngữ mệnh lệnh) vừa là đệ quy nguyên thủy (sắp xếp) và đồng quy nguyên thủy.
map :: (a -> b) -> [a] -> [b]
map f [] = []
map f (x:xs) = (f x):map f xs
tương tự với hàm zipWith
có một hàm và một cặp danh sách và kết hợp chúng lại với nhau bằng hàm đó.
zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
zipWith f (a:as) (b:bs) = (f a b):zipWith f as bs
zipWith _ _ _ = [] --base case
ví dụ kinh điển của các ngôn ngữ chức năng là chuỗi Fibonacci
fib 0 = 0
fib 1 = 1
fib n = (fib (n-1)) + (fib (n-2))
vốn là đệ quy nguyên thủy, nhưng có thể được thể hiện thanh lịch hơn như một danh sách vô hạn
fibs = 0:1:zipWith (+) fibs (tail fibs)
fib' n = fibs !! n --the !! is haskell syntax for index at
một ví dụ thú vị về cảm ứng / cưỡng chế đang chứng minh rằng hai định nghĩa này tính toán cùng một thứ. Điều này được để lại như một bài tập cho người đọc.