Không có ý nghĩa gì về vấn đề lập trình của người nổi tiếng thế giới.


339

Tôi đã nghe thuật ngữ "than đá" nhiều lần trong các chương trình chức năng và vòng tròn PLT, đặc biệt khi cuộc thảo luận là về các vật thể, comonad, ống kính, v.v. Googling thuật ngữ này cung cấp cho các trang mô tả toán học của các cấu trúc này là khá khó hiểu đối với tôi. Bất cứ ai cũng có thể giải thích ý nghĩa của than đá trong bối cảnh lập trình, tầm quan trọng của chúng và cách chúng liên quan đến các đối tượng và comonad?


21
Tôi có thể giới thiệu các mẫu sách xuất sắc của Jeremy Gibbons trong FP: samplesinfp.wordpress.com và bài viết khá dễ hiểu của anh ấy "Tính toán các chương trình chức năng" không? Cả hai đều bao gồm các chú chó con theo một cách khá nghiêm ngặt (so với, ví dụ, một bài đăng trên blog), nhưng chúng cũng khá khép kín đối với một người biết một chút về Haskell.
Kristopher Micinski

2
@KristopherMicinski, rất thú vị. Cảm ơn!
missingfaktor

Câu trả lời:


474

Đại số

Tôi nghĩ rằng nơi bắt đầu sẽ là để hiểu ý tưởng của đại số . Đây chỉ là một khái quát của các cấu trúc đại số như các nhóm, vòng, đơn sắc và như vậy. Hầu hết thời gian, những điều này được giới thiệu dưới dạng tập hợp, nhưng vì chúng tôi là bạn bè, nên tôi sẽ nói về các loại Haskell thay thế. (Tôi không thể cưỡng lại việc sử dụng một số chữ cái Hy Lạp mặc dù chúng làm mọi thứ trông mát mẻ hơn!)

Một đại số, sau đó, chỉ là một loại τvới một số chức năng và danh tính. Các hàm này có số lượng đối số loại khác nhau τvà tạo ra một τ: chưa được xử lý, tất cả chúng trông giống như (τ, τ,…, τ) → τ. Họ cũng có thể có "danh tính" các bản τsao có hành vi đặc biệt với một số chức năng.

Ví dụ đơn giản nhất về điều này là monoid. Một monoid là bất kỳ loại τcó chức năng mappend ∷ (τ, τ) → τvà danh tính mzero ∷ τ. Các ví dụ khác bao gồm những thứ như các nhóm (giống như các đơn sắc ngoại trừ có invert ∷ τ → τchức năng bổ sung ), nhẫn, lưới, v.v.

Tất cả các chức năng hoạt động trên τnhưng có thể có hương liệu khác nhau. Chúng ta có thể viết chúng ra như là τⁿ → τ, nơi τⁿbản đồ đến một tuple n τ. Bằng cách này, sẽ có ý nghĩa khi nghĩ về danh tính như τ⁰ → τnơi τ⁰chỉ là bộ dữ liệu trống rỗng (). Vì vậy, bây giờ chúng ta thực sự có thể đơn giản hóa ý tưởng về đại số: đó chỉ là một số loại với một số hàm trên đó.

Đại số chỉ là một mô hình phổ biến trong toán học được "bao thanh toán", giống như chúng ta làm với mã. Mọi người nhận thấy rằng cả một loạt những điều thú vị, các đơn vị, nhóm, mạng đã nói ở trên, tất cả đều theo một mô hình tương tự, vì vậy họ đã trừu tượng hóa nó. Ưu điểm của việc này cũng giống như trong lập trình: nó tạo ra các bằng chứng có thể tái sử dụng và làm cho một số loại lý luận dễ dàng hơn.

F-Algebras

Tuy nhiên, chúng tôi không hoàn thành với bao thanh toán. Cho đến nay, chúng ta có một loạt các chức năng τⁿ → τ. Chúng ta thực sự có thể làm một thủ thuật gọn gàng để kết hợp tất cả chúng thành một chức năng. Cụ thể, chúng ta hãy nhìn vào các đơn sắc: chúng ta có mappend ∷ (τ, τ) → τmempty ∷ () → τ. Chúng ta có thể biến những thứ này thành một hàm duy nhất bằng cách sử dụng một kiểu tổng cộng Either. Nó sẽ trông như thế này:

op  Monoid τ  Either (τ, τ) ()  τ
op (Left (a, b)) = mappend (a, b)
op (Right ())    = mempty

Chúng ta thực sự có thể sử dụng phép biến đổi này nhiều lần để kết hợp tất cả các τⁿ → τhàm thành một hàm duy nhất, cho bất kỳ đại số nào . (Trong thực tế, chúng ta có thể làm điều này cho bất kỳ số lượng các chức năng a → τ, b → τvà như vậy cho bất kỳ a, b,… .)

Điều này cho phép chúng ta nói về đại số dưới dạng một loại τvới một hàm duy nhất từ một số mớ hỗn độn Eitherthành một τ. Đối với đơn sắc, mớ hỗn độn này là : Either (τ, τ) (); đối với các nhóm (có τ → τhoạt động bổ sung ), đó là : Either (Either (τ, τ) τ) (). Đó là một loại khác nhau cho mọi cấu trúc khác nhau. Vì vậy, những gì tất cả các loại có điểm chung? Điều rõ ràng nhất là tất cả chúng chỉ là tổng của các sản phẩm Kiểu dữ liệu đại số. Ví dụ: đối với các đơn sắc, chúng ta có thể tạo một loại đối số đơn trị hoạt động cho mọi đơn vị:

data MonoidArgument τ = Mappend τ τ -- here τ τ is the same as (τ, τ)
                      | Mempty      -- here we can just leave the () out

Chúng ta có thể làm điều tương tự cho các nhóm và vòng và lưới và tất cả các cấu trúc có thể khác.

Những gì khác là đặc biệt về tất cả các loại? Chà, tất cả đều như Functorsvậy! Ví dụ:

instance Functor MonoidArgument where
  fmap f (Mappend τ τ) = Mappend (f τ) (f τ)
  fmap f Mempty        = Mempty

Vì vậy, chúng ta có thể khái quát hóa ý tưởng của chúng ta về đại số hơn nữa. Nó chỉ là một số loạiτ với một chức năng f τ → τcho một số functor f. Trên thực tế, chúng ta có thể viết nó ra như một kiểu chữ:

class Functor f  Algebra f τ where
  op  f τ  τ

Đây thường được gọi là "Đại số F" bởi vì nó được xác định bởi functor F. Nếu chúng ta có thể áp dụng một phần kiểu chữ, chúng ta có thể định nghĩa một cái gì đó như thế nào class Monoid = Algebra MonoidArgument.

Coachebras

Bây giờ, hy vọng bạn đã hiểu rõ về đại số là gì và làm thế nào nó chỉ là một khái quát của các cấu trúc đại số bình thường. Vậy F-thangicon là gì? Chà, đồng ngụ ý rằng đó là "kép" của một đại số, đó là, chúng ta lấy một đại số và lật một số mũi tên. Tôi chỉ thấy một mũi tên trong định nghĩa trên, vì vậy tôi sẽ chỉ lật nó:

class Functor f  CoAlgebra f τ where
  coop  τ  f τ

Và đó là tất cả! Bây giờ, kết luận này có vẻ hơi thiếu sót (heh). Nó nói với bạn biết thế nào là một con than, nhưng không thực sự cung cấp bất kỳ cái nhìn sâu sắc nào về việc nó hữu ích như thế nào hoặc tại sao chúng ta quan tâm. Tôi sẽ hiểu điều đó một chút, một khi tôi tìm thấy hoặc đưa ra một ví dụ hay: hai.

Lớp học và đối tượng

Sau khi đọc xung quanh một chút, tôi nghĩ rằng tôi có một ý tưởng tốt về cách sử dụng than đá để đại diện cho các lớp và các đối tượng. Chúng ta có một loại Cchứa tất cả các trạng thái bên trong có thể có của các đối tượng trong lớp; lớp học là một lớp thanC đó chỉ định các phương thức và thuộc tính của các đối tượng.

Như trong ví dụ đại số, nếu chúng ta có một loạt các hàm như a → τb → τđối với bất kỳ a, b,…, chúng ta có thể kết hợp tất cả chúng thành một hàm duy nhất bằng cách sử dụng Eithermột kiểu tổng. "Khái niệm" kép sẽ kết hợp một loạt các chức năng của loại τ → a, τ → bv.v. Chúng ta có thể làm điều này bằng cách sử dụng kép của một loại tổng hợp một loại sản phẩm. Vì vậy, với hai hàm trên (được gọi fg), chúng ta có thể tạo một hàm duy nhất như vậy:

both  τ  (a, b)
both x = (f x, g x)

Loại (a, a)này là một functor theo cách đơn giản, vì vậy nó chắc chắn phù hợp với quan niệm của chúng ta về một F-thangebra. Thủ thuật đặc biệt này cho phép chúng tôi gói một loạt các chức năng khác nhau, hoặc đối với OOP, các phương thức thành một hàm kiểu duy nhất τ → f τ.

Các yếu tố của loại của chúng tôi Cđại diện cho trạng thái nội bộ của đối tượng. Nếu đối tượng có một số thuộc tính có thể đọc được, chúng phải có khả năng phụ thuộc vào trạng thái. Cách rõ ràng nhất để làm điều này là làm cho chúng trở thành một chức năng của C. Vì vậy, nếu chúng ta muốn một thuộc tính chiều dài (ví dụ object.length), chúng ta sẽ có một hàm C → Int.

Chúng tôi muốn các phương thức có thể lấy một đối số và sửa đổi trạng thái. Để làm điều này, chúng ta cần phải lấy tất cả các đối số và tạo ra một đối số mới C. Chúng ta hãy tưởng tượng một setPositionphương thức có một xvà một ytọa độ : object.setPosition(1, 2). Nó sẽ trông như thế này : C → ((Int, Int) → C).

Mẫu quan trọng ở đây là "phương thức" và "thuộc tính" của đối tượng lấy chính đối tượng làm đối số đầu tiên của chúng. Đây giống như selftham số trong Python và giống như ẩn thiscủa nhiều ngôn ngữ khác. Một ký hiệu than về cơ bản chỉ gói gọn hành vi lấy selftham số: đó là cái đầu tiên Ctrong đó C → F C.

Vì vậy, hãy đặt tất cả lại với nhau. Chúng ta hãy tưởng tượng một lớp với một positiontài sản, một nametài sản và setPositionchức năng:

class C
  private
    x, y  : Int
    _name : String
  public
    name        : String
    position    : (Int, Int)
    setPosition : (Int, Int)  C

Chúng ta cần hai phần để đại diện cho lớp này. Đầu tiên, chúng ta cần thể hiện trạng thái bên trong của đối tượng; trong trường hợp này nó chỉ giữ hai Ints và a String. (Đây là kiểu của chúng tôi C.) Sau đó, chúng tôi cần phải đưa ra biểu đồ đại diện cho lớp.

data C = Obj { x, y   Int
             , _name  String }

Chúng tôi có hai thuộc tính để viết. Chúng khá tầm thường:

position  C  (Int, Int)
position self = (x self, y self)

name  C  String
name self = _name self

Bây giờ chúng tôi chỉ cần có thể cập nhật vị trí:

setPosition  C  (Int, Int)  C
setPosition self (newX, newY) = self { x = newX, y = newY }

Điều này giống như một lớp Python với các selfbiến rõ ràng của nó . Bây giờ chúng ta có một loạt các self →chức năng, chúng ta cần kết hợp chúng thành một chức năng duy nhất cho đại số. Chúng ta có thể làm điều này với một tuple đơn giản:

coop  C  ((Int, Int), String, (Int, Int)  C)
coop self = (position self, name self, setPosition self)

Kiểu đối ((Int, Int), String, (Int, Int) → c)với bất kỳ người nào c là một functor, vì vậy coopcó dạng chúng ta muốn : Functor f ⇒ C → f C.

Với điều này, Ccùng với coophình thức một ký hiệu than xác định lớp tôi đã đưa ra ở trên. Bạn có thể thấy làm thế nào chúng ta có thể sử dụng kỹ thuật tương tự này để chỉ định bất kỳ số phương thức và thuộc tính nào cho các đối tượng của chúng ta có.

Điều này cho phép chúng tôi sử dụng lý luận than đá để đối phó với các lớp. Ví dụ, chúng ta có thể đưa ra khái niệm "đồng cấu F-thangular" để biểu diễn các phép biến đổi giữa các lớp. Đây là một thuật ngữ âm thanh đáng sợ chỉ có nghĩa là một sự chuyển đổi giữa các than đá bảo tồn cấu trúc. Điều này làm cho việc suy nghĩ về ánh xạ các lớp lên các lớp khác dễ dàng hơn nhiều.

Nói tóm lại, một ký tự F đại diện cho một lớp bằng cách có một loạt các thuộc tính và phương thức mà tất cả phụ thuộc vào một self tham số có chứa trạng thái bên trong của mỗi đối tượng.

Danh mục khác

Cho đến nay, chúng ta đã nói về đại số và than đá như các loại Haskell. Đại số chỉ là một loại τcó chức năng f τ → τvà đại số chỉ là một loại τcó chức năngτ → f τ .

Tuy nhiên, không có gì thực sự ràng buộc những ý tưởng này với Haskell per se . Trên thực tế, chúng thường được giới thiệu dưới dạng tập hợp và hàm toán học hơn là kiểu và hàm Haskell. Thật vậy, chúng ta có thể khái quát các khái niệm này cho bất kỳ loại !

Chúng ta có thể định nghĩa một đại số F cho một số loại C. Đầu tiên, chúng ta cần một functor GianthatF : C → C là, một endofunctor . (Tất cả các Haskell Functorthực sự là endofunctor từ Hask → Hask.) Sau đó, đại số chỉ là một đối tượng Atừ Cvới một hình thái F A → A. Một con ngựa giống như nhau ngoại trừ vớiA → F A .

Chúng ta đạt được gì khi xem xét các danh mục khác? Vâng, chúng ta có thể sử dụng cùng một ý tưởng trong các bối cảnh khác nhau. Giống như các đơn nguyên. Trong Haskell, một đơn nguyên là một số loại M ∷ ★ → ★có ba thao tác:

map         β)  (M α  M β)
return    α  M α
join      M (M α)  M α

Các mapchức năng chỉ là một bằng chứng về sự thật rằng Mlà một Functor. Vì vậy, chúng ta có thể nói rằng một đơn nguyên chỉ là một functor với hai hoạt động: returnjoin.

Functor tự tạo thành một thể loại, với hình thái giữa chúng được gọi là "biến đổi tự nhiên". Một phép biến đổi tự nhiên chỉ là một cách để biến một functor thành một functor khác trong khi bảo tồn cấu trúc của nó. Đây là một bài viết tốt giúp giải thích ý tưởng. Nó nói về concat, mà chỉ joindành cho danh sách.

Với functor Haskell, thành phần của hai functor là một functor. Trong mã giả, chúng ta có thể viết điều này:

instance (Functor f, Functor g)  Functor (f  g) where
  fmap fun x = fmap (fmap fun) x

Điều này giúp chúng tôi suy nghĩ về việc joinlập bản đồ từ f ∘ f → f. Các loại join∀α. f (f α) → f α. Theo trực giác, chúng ta có thể thấy một hàm hợp lệ cho tất cả các loại αcó thể được coi là một biến đổi của f.

returnlà một sự chuyển đổi tương tự. Loại của nó là ∀α. α → f α. Điều này có vẻ khác biệt, người đầu tiên αkhông "ở" một functor! Hạnh phúc, chúng ta có thể khắc phục điều này bằng cách thêm một functor danh tính ở đó : ∀α. Identity α → f α. Vậy returnlà một sự biến đổi Identity → f.

Bây giờ chúng ta có thể nghĩ về một đơn nguyên chỉ là một đại số dựa trên một số functor fvới các hoạt động f ∘ f → fIdentity → f. Điều này có vẻ quen thuộc phải không? Nó rất giống với một monoid, chỉ là một số loại τcó hoạt động τ × τ → τ() → τ.

Vì vậy, một đơn nguyên giống như một monoid, ngoại trừ thay vì có một loại chúng ta có một functor. Đó là cùng một loại đại số, chỉ trong một loại khác. (Đây là nơi cụm từ "Một đơn nguyên chỉ là một đơn chất trong danh mục endofunctor" xuất phát từ theo như tôi biết.)

Bây giờ, chúng ta có hai hoạt động: f ∘ f → fIdentity → f. Để có được con số tương ứng, chúng ta chỉ cần lật các mũi tên. Điều này cho chúng ta hai hoạt động mới: f → f ∘ ff → Identity. Chúng ta có thể biến chúng thành các loại Haskell bằng cách thêm các biến loại như trên, cho chúng ta ∀α. f α → f (f α)∀α. f α → α. Điều này trông giống như định nghĩa của một comonad:

class Functor f  Comonad f where
  coreturn  f α  α
  cojoin    f α  f (f α)

Vì vậy, một comonad sau đó là một đại số trong một thể loại endofunctor.


45
Điều này là vô cùng có giá trị. Tôi đã xoay sở để mơ hồ một số trực giác về toàn bộ hoạt động kinh doanh đại số F này từ việc đọc và các ví dụ (ví dụ, từ việc nhìn thấy việc sử dụng chúng với catamoprhism), nhưng điều này hoàn toàn rõ ràng đối với tôi. Cảm ơn!
Luis Casillas

28
Đây là một lời giải thích tuyệt vời.
Edward KMett

5
@EdwardKmett: Cảm ơn. Những thứ tôi thêm vào về các lớp và các đối tượng có ổn không? Tôi chỉ đọc về nó ngày hôm nay, nhưng nó có vẻ có ý nghĩa.
Tikhon Jelvis

7
Đối với những gì nó có giá trị: "Danh mục của endofunctor" ở đây chính xác hơn là một thể loại có đối tượng là endofunctor trên một số thể loại và mũi tên của chúng là biến đổi tự nhiên. Đây là một thể loại đơn hình, với thành phần functor tương ứng (,)và functor nhận dạng (). Một đối tượng đơn hình trong danh mục đơn hình là một đối tượng có mũi tên tương ứng với đại số đơn hình của bạn, mô tả một đối tượng đơn hình trong Hask với các loại sản phẩm là cấu trúc đơn hình. Một đối tượng monoid trong danh mục endofunctor trên C là một đơn vị trên C, vì vậy, sự hiểu biết của bạn là chính xác. :]
CA McCann

8
Đó là một kết thúc tuyệt vời!
jdinunzio

85

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 Nilnhư () -> 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 × Bhoạt động là một sản phẩm chéo của hai bộ AB(có nghĩa là, bộ đôi (a, b)nơi ađi qua tất cả các yếu tố của Abđi qua tất cả các yếu tố củaB ).

Liên hiệp hai tập hợp ABlà một tập hợp A | Blà tập hợp của tập hợp {(a, 1) : a in A}{(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 AB, 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 Ahoặc B, vì vậy khi chúng ta chọn bất kỳ phần tử từ A | Bchúng tôi sẽ ngay lập tức biết yếu tố này xuất phát từ Ahoặc từB .

Chúng ta có thể 'tham gia' Nilvà các Conschứ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|Conshà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 joinedchứ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 đó Fmộ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 xvà các |hoạt động, tập quán Tvà có thể các loại khác. Ví dụ, cho IntListIntTree Ftrô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 đó Tcó một số loại và flà 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)(IntTree, Leaf|Branch). Tuy nhiên, lưu ý rằng mặc dù loại fchức năng đó là giống nhau cho mỗi F Tfbả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ố ghcũ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 foldhà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 foldrchứ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à foldrchứ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 foldrsẽ 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 IntListloạ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 Nilmột phần IntListvà phần thứ hai xác định hành vi của hàm trên Consmộ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 bdatatype, 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 đó reductorlà 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ì IntListlà một đại số F1 ban đầu, cho mỗi loại Tvà cho mỗi hàm r :: F1 T -> Ttồn tại một hàm, được gọi là catamorphism cho r, chuyển đổi IntListthành Tvà hàm đó là duy nhất. Thật vậy, trong ví dụ của chúng tôi một catamorphism cho reductorsumFold. Lưu ý cách reductorsumFoldtương tự nhau: chúng có cấu trúc gần như giống nhau! Trong reductorđịnh nghĩa ssử 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 xstrong 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 appendhà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

appendFoldlà một dị hình appendReductormà biến IntListthà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 unfoldscho 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 IntStreams:

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 IntStreamloạ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 Tlà 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 đó Tlà một loại và glà 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, gTcó 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ụ, IntStreamlà 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 Tvà cho mọi chức năng đều p :: T -> F1 Ttồn tại một hàm, được gọi là biến hình , chuyển đổi Tthành IntStreamvà 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 natsnatsBuilder. 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 đó. natslà 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 đó iteratelà 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.


Kiểu và định nghĩa về appendReductorngoại hình hơi lạ và không thực sự giúp tôi nhìn thấy mẫu ở đó ... :) Bạn có thể kiểm tra lại xem nó có đúng không? .. Nói chung các loại reductor trông như thế nào? Trong định nghĩa của rnó, được F1xác định bởi IntList, hay nó là một F tùy ý?
Max Galkin

37

Xem qua tài liệu hướng dẫn Một hướng dẫn về (đồng) đại số và (đồng) cảm ứng sẽ cung cấp cho bạn một cái nhìn sâu sắc về đại số trong khoa học máy tính.

Dưới đây là một trích dẫn của nó để thuyết phục bạn,

Nói chung, một chương trình trong một số ngôn ngữ lập trình thao túng dữ liệu. Trong sự phát triển của khoa học máy tính trong vài thập kỷ qua, rõ ràng là một mô tả trừu tượng về những dữ liệu này là mong muốn, ví dụ để đảm bảo rằng chương trình của một người không phụ thuộc vào sự biểu diễn cụ thể của dữ liệu mà nó vận hành. Ngoài ra, tính trừu tượng như vậy tạo điều kiện cho bằng chứng chính xác.
Mong muốn này đã dẫn đến việc sử dụng các phương pháp đại số trong khoa học máy tính, trong một nhánh gọi là đặc tả đại số hoặc lý thuyết kiểu dữ liệu trừu tượng. Đối tượng nghiên cứu là các kiểu dữ liệu trong chính chúng, sử dụng các khái niệm về các kỹ thuật quen thuộc từ đại số. Các kiểu dữ liệu được sử dụng bởi các nhà khoa học máy tính thường được tạo ra từ một tập hợp các thao tác (hàm tạo) nhất định và vì lý do này, "tính ban đầu" của đại số đóng vai trò quan trọng như vậy.
Các kỹ thuật đại số tiêu chuẩn đã tỏ ra hữu ích trong việc nắm bắt các khía cạnh thiết yếu khác nhau của cấu trúc dữ liệu được sử dụng trong khoa học máy tính. Nhưng hóa ra rất khó để mô tả đại số một số cấu trúc động vốn có xảy ra trong điện toán. Các cấu trúc như vậy thường liên quan đến một khái niệm về trạng thái, có thể được chuyển đổi theo nhiều cách khác nhau. Các cách tiếp cận chính thức đối với các hệ thống động lực dựa trên trạng thái như vậy thường sử dụng các hệ thống tự động hoặc chuyển tiếp, như các tài liệu tham khảo cổ điển ban đầu.
Trong thập kỷ qua, cái nhìn sâu sắc dần dần phát triển rằng các hệ thống dựa trên trạng thái như vậy không nên được mô tả như đại số, mà được gọi là đồng đại số. Đây là kép chính thức của đại số, theo cách sẽ được thực hiện chính xác trong hướng dẫn này. Thuộc tính kép của "tính ban đầu" đối với đại số, cụ thể là tính hữu hạn hóa ra rất quan trọng đối với các đồng đại số như vậy. Và nguyên tắc suy luận logic cần thiết cho các đồng đại số cuối cùng như vậy không phải là cảm ứng mà là đồng cảm.


Mở đầu, về lý thuyết Thể loại. Lý thuyết danh mục nên được đổi tên thành lý thuyết của functor. Vì các thể loại là những gì người ta phải xác định để xác định functor. (Hơn nữa, functor là những gì người ta phải xác định để xác định các phép biến đổi tự nhiên.)

Functor là gì? Đó là một sự chuyển đổi từ bộ này sang bộ khác bảo tồn cấu trúc của chúng. (Để biết thêm chi tiết có rất nhiều mô tả hay trên mạng).

Đại số F là gì? Đó là đại số của functor. Đó chỉ là nghiên cứu về sự sở hữu phổ quát của functor.

Làm thế nào nó có thể được liên kết với khoa học máy tính? Chương trình có thể được xem như một bộ thông tin có cấu trúc. Việc thực hiện chương trình tương ứng với việc sửa đổi bộ thông tin có cấu trúc này. Nghe có vẻ tốt khi thực hiện nên bảo tồn cấu trúc chương trình. Sau đó, việc thực thi có thể được xem như là ứng dụng của functor đối với tập thông tin này. (Người xác định chương trình).

Tại sao F-co-đại số? Chương trình là kép về bản chất vì chúng được mô tả bằng thông tin và họ hành động theo nó. Sau đó, chủ yếu là thông tin soạn chương trình và làm cho chúng thay đổi có thể được xem theo hai cách.

  • Dữ liệu có thể được định nghĩa là thông tin đang được chương trình xử lý.
  • Trạng thái có thể được định nghĩa là thông tin đang được chia sẻ bởi chương trình.

Sau đó, ở giai đoạn này, tôi muốn nói rằng,

  • Đại số F là nghiên cứu về biến đổi functorial hoạt động trên Vũ trụ của Dữ liệu (như được định nghĩa ở đây).
  • F-co-algebras là nghiên cứu về biến đổi functorial tác động lên Vũ trụ của Nhà nước (như được định nghĩa ở đây).

Trong vòng đời của một chương trình, dữ liệu và trạng thái cùng tồn tại và chúng hoàn thiện lẫn nhau. Chúng là kép.


5

Tôi sẽ bắt đầu với những thứ rõ ràng liên quan đến lập trình và sau đó thêm vào một số thứ toán học, để giữ cho nó cụ thể và thực tế nhất có thể.


Chúng ta hãy trích dẫn một số nhà khoa học máy tính về cưỡng chế giáo dục

http://www.cs.umd.edu/~micinski/posts/2012-09-04-on-under Hiểu-coindtions.html

Cảm ứng là về dữ liệu hữu hạn, đồng cảm ứng là về dữ liệu vô hạn.

Ví dụ điển hình của dữ liệu vô hạn là loại danh sách lười biếng (luồng). Ví dụ: giả sử chúng ta có đối tượng sau trong bộ nhớ:

 let (pi : int list) = (* some function which computes the digits of
 π. *)

Máy tính không thể chứa tất cả số π, vì nó chỉ có một lượng bộ nhớ hữu hạn! Nhưng những gì nó có thể làm là tổ chức một chương trình hữu hạn, nó sẽ tạo ra bất kỳ sự mở rộng dài tùy ý nào của số π mà bạn mong muốn. Miễn là bạn chỉ sử dụng các phần hữu hạn của danh sách, bạn có thể tính toán với danh sách vô hạn đó bao nhiêu tùy ý.

Tuy nhiên, hãy xem xét chương trình sau:

let print_third_element (k : int list) =   match k with
     | _ :: _ :: thd :: tl -> print thd


 print_third_element pi

Chương trình này nên in chữ số thứ ba của pi. Nhưng trong một số ngôn ngữ, bất kỳ đối số nào đối với một chức năng đều được đánh giá trước khi được chuyển vào một chức năng (nghiêm ngặt, không lười biếng, đánh giá). Nếu chúng ta sử dụng thứ tự giảm này, thì chương trình trên của chúng tôi sẽ chạy mãi mãi tính toán các chữ số pi trước khi nó có thể được chuyển đến chức năng máy in của chúng tôi (điều này không bao giờ xảy ra). Vì máy không có bộ nhớ vô hạn, cuối cùng chương trình sẽ hết bộ nhớ và gặp sự cố. Đây có thể không phải là thứ tự đánh giá tốt nhất.

http://adam.chlipala.net/cpdt/html/Coindulation.html

Trong các ngôn ngữ lập trình chức năng lười biếng như Haskell, cấu trúc dữ liệu vô hạn có ở khắp mọi nơi. Danh sách vô hạn và các kiểu dữ liệu kỳ lạ hơn cung cấp các tóm tắt thuận tiện cho việc giao tiếp giữa các phần của chương trình. Để đạt được sự thuận tiện tương tự mà không có cấu trúc lười biếng vô hạn, trong nhiều trường hợp, sẽ đòi hỏi sự đảo ngược của dòng chảy điều khiển.

http://www.alexandrasilva.org/#/talks.html ví dụ về than đá của Alexandra Silva


Liên quan bối cảnh toán học xung quanh với các nhiệm vụ lập trình thông thường

"Đại số" là gì?

Các cấu trúc đại số thường trông giống như:

  1. Đồ đạc
  2. Những thứ có thể làm

Điều này sẽ nghe giống như các đối tượng với 1. thuộc tính và 2. phương thức. Hoặc thậm chí tốt hơn, nó nên âm thanh như chữ ký loại.

Các ví dụ toán học tiêu chuẩn bao gồm monoid ⊃ nhóm vector-space ⊃ "một đại số". Monoids giống như automata: chuỗi các động từ (ví dụ f.g.h.h.nothing.f.g.f:). Mộtgit nhật ký luôn luôn thêm lịch sử và không bao giờ xóa nó sẽ là một monoid nhưng không phải là một nhóm. Nếu bạn thêm nghịch đảo (ví dụ số âm, phân số, gốc, xóa lịch sử tích lũy, không làm vỡ gương vỡ), bạn sẽ có được một nhóm.

Các nhóm chứa những thứ có thể được thêm hoặc trừ với nhau. Ví dụ Durations có thể được thêm vào với nhau. (Nhưng Datekhông thể.) Độ bền sống trong không gian vectơ (không chỉ là một nhóm) bởi vì chúng cũng có thể được thu nhỏ bởi các số bên ngoài. (Một chữ ký loại scaling :: (Number,Duration) → Duration.)

Đại số spaces không gian vectơ có thể làm một điều khác: có một số m :: (T,T) → T. Gọi đây là "phép nhân" hoặc không, bởi vì một khi bạn để lại Integersthì không rõ ràng "phép nhân" (hay "lũy thừa" ) là gì.

(Đây là lý do tại sao mọi người tìm đến các thuộc tính phổ quát (theo lý thuyết): để cho họ biết phép nhân nào nên làm hoặc giống như :

tài sản chung của sản phẩm )


Đại số → Coachebras

Comultiplication dễ xác định theo cách cảm thấy không độc đoán, hơn là nhân, vì để đi từ T → (T,T)bạn có thể chỉ cần lặp lại cùng một yếu tố. ("Bản đồ đường chéo" - giống như ma trận / toán tử đường chéo trong lý thuyết quang phổ)

Counit thường là dấu vết (tổng của các mục chéo), mặc dù một lần nữa điều quan trọng là những gì counit của bạn làm ; tracechỉ là một câu trả lời tốt cho ma trận.

Lý do để nhìn vào một không gian kép , nói chung, là bởi vì nó dễ dàng hơn để suy nghĩ trong không gian đó. Ví dụ, đôi khi dễ dàng nghĩ về một vectơ bình thường hơn so với mặt phẳng bình thường, nhưng bạn có thể điều khiển các mặt phẳng (bao gồm cả siêu phẳng) bằng các vectơ (và bây giờ tôi đang nói về vectơ hình học quen thuộc, như trong một máy dò tia) .


Dữ liệu có cấu trúc (un)

Các nhà toán học có thể đang mô hình hóa một cái gì đó thú vị như của TQFT , trong khi các lập trình viên phải vật lộn với

  • ngày / lần (+ :: (Date,Duration) → Date ),
  • địa điểm ( Paris(+48.8567,+2.3508)! Đó là một hình dạng, không phải là một điểm.),
  • JSON không cấu trúc được cho là nhất quán theo một nghĩa nào đó,
  • XML sai nhưng gần gũi,
  • dữ liệu GIS cực kỳ phức tạp sẽ đáp ứng vô số mối quan hệ hợp lý,
  • biểu thức chính quy có nghĩa là một cái gì đó cho bạn, nhưng có nghĩa là ít hơn đáng kể cho perl.
  • CRM nên giữ tất cả các số điện thoại và vị trí biệt thự của giám đốc điều hành, tên của vợ và con của anh ấy (bây giờ là cũ) và tất cả các quà tặng trước đó, mỗi món quà sẽ đáp ứng các mối quan hệ "rõ ràng" (rõ ràng với khách hàng) khó mã hóa
  • .....

Các nhà khoa học máy tính, khi nói về than đá, thường có các thao tác thiết lập trong tâm trí, giống như sản phẩm của Cartesian. Tôi tin rằng đây là ý nghĩa của mọi người khi họ nói như "Algebras là than trong Haskell". Nhưng ở mức độ mà các lập trình viên phải mô hình hóa các kiểu dữ liệu phức tạp như Place, Date/Timevà, và Customerlàm cho các mô hình đó trông giống như thế giới thực (hoặc ít nhất là quan điểm của người dùng cuối về thế giới thực) như tôi có thể tin tưởng. có thể hữu ích ngoài chỉ thế giới thiết lập.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.