其中 1 是一个单位集(用一个元素设置), A × B 操作是两个集合 A 和 B 的交叉积(即成对的集合 (a, b) ,其中 a 遍历 A 的所有元素, b 遍历 B 的所有元素) .
两组 A 和 B 的不相交联合是一组 A | B ,它是集合 {(a, 1) : a in A} 和 {(b, 2) : b in B} 的并集 . 本质上它是 A 和 B 的所有元素的集合,但是每个元素'marked'属于 A 或 B ,所以当我们从 A | B 中选择任何元素时,我们将立即知道该元素是来自 A 还是来自 B .
现在我们可以定义F代数 . F代数只是一对 (T, f) ,其中 T 是某种类型, f 是 f :: F T -> T 类型的函数 . 在我们的例子中,F-代数是 (IntList, Nil|Cons) 和 (IntTree, Leaf|Branch) . 但请注意,尽管每个F的 f 函数类型相同,但 T 和 f 本身可以是任意的 . 例如,某些 g 和 h 的 (String, g :: 1 | (Int x String) -> String) 或 (Double, h :: Int | (Double, Double) -> Double) 也是相应F的F-代数 .
现在假设我们不是在Haskell中编程,而是在某种语言中允许直接在类型签名中使用代数类型(从技术上讲,Haskell允许通过元组和 Either a b 数据类型使用代数类型,但这会导致不必要的冗长) . 考虑一个功能:
reductor :: () | (Int × Int) -> Int
reductor () = 0
reductor (x, s) = x + s
可以看出 reductor 是 F1 Int -> Int 类型的函数,就像F-代数的定义一样!事实上,这对 (Int, reductor) 是一个F1代数 .
因为 IntList 是一个初始的F1代数,对于每个类型 T 并且对于每个函数 r :: F1 T -> T ,存在一个函数,称为 r 的catamorphism,它将 IntList 转换为 T ,并且这样的函数是唯一的 . 实际上,在我们的例子中, reductor 的一个变形是 sumFold . 注意 reductor 和 sumFold 是如何相似的:它们具有几乎相同的结构!在 reductor 中定义 s 参数用法(其类型对应于 T )对应于 sumFold 定义中 sumFold xs 的计算结果的使用 .
现在,F-coalgebra是一对 (T, g) ,其中 T 是一个类型, g 是 g :: T -> F T 类型的函数 . 例如, (IntStream, head&tail) 是F1-余代数 . 同样,就像在F-代数中一样, g 和 T 可以是任意的,例如, (String, h :: String -> Int x String) 也是某些h的F1-代数 .
在所有的F-coalgebras中都有所谓的终端F-余代数,它们是初始F-代数的双重代数 . 例如, IntStream 是终端F-余代数 . 这意味着对于每个类型 T 和每个函数 p :: T -> F1 T 都存在一个名为anamorphism的函数,它将 T 转换为 IntStream ,并且这样的函数是唯一的 .
考虑以下函数,它从给定的一个开始生成连续整数的流:
nats :: Int -> IntStream
nats n = Cons (n, nats (n+1))
现在让我们检查函数 natsBuilder :: Int -> F1 Int ,即 natsBuilder :: Int -> Int × Int :
natsBuilder :: Int -> Int × Int
natsBuilder n = (n, n+1)
在阅读了一下之后,我想我很清楚如何使用代数来表示类和对象 . 我们有一个 C 类型,它包含类中对象的所有可能内部状态;类本身是 C 上的余代数,它指定了对象的方法和属性 .
如代数示例所示,如果我们有 a → τ 和 b → τ 等一系列函数用于任何 a, b,… ,我们可以使用 Either (和类型)将它们全部组合成单个函数 . 双"notion"将组合一堆 τ → a , τ → b 等类型的函数 . 我们可以使用sum类型的对偶 - 产品类型来实现这一点 . 所以考虑到上面的两个函数(称为 f 和 g ),我们可以像这样创建一个函数:
both ∷ τ → (a, b)
both x = (f x, g x)
类型 (a, a) 是一种直接方式的仿函数,所以它肯定符合我们的F-coalgebra概念 . 这个特殊的技巧让我们将一堆不同的函数 - 或者对于OOP,方法 - 打包成一个类型为 τ → f τ 的函数 .
我们的类型 C 的元素表示对象的内部状态 . 如果对象具有一些可读属性,则它们必须能够依赖于状态 . 最明显的方法是使它们成为 C 的函数 . 因此,如果我们想要一个长度属性(例如 object.length ),我们将有一个函数 C → Int .
我们想要可以采用参数和修改状态的方法 . 为此,我们需要获取所有参数并生成一个新的 C . 让我们假设 setPosition 方法采用 x 和 y 坐标: object.setPosition(1, 2) . 它看起来像这样: C → ((Int, Int) → C) .
这里重要的模式是对象的"methods"和"properties"将对象本身作为它们的第一个参数 . 这就像Python中的 self 参数一样,与许多其他语言的隐式 this 类似 . 一个代数基本上只是封装了一个 self 参数的行为:这就是 C → F C 中的第一个 C .
所以让's put it all together. Let'想象一个具有 position 属性, name 属性和 setPosition 函数的类:
class C
private
x, y : Int
_name : String
public
name : String
position : (Int, Int)
setPosition : (Int, Int) → C
我们需要两个部分来代表这个类 . 首先,我们需要表示对象的内部状态;在这种情况下,它只包含两个 Int 和一个 String . (这是我们的类型 C . )然后我们需要提出代表该类的代数 .
data C = Obj { x, y ∷ Int
, _name ∷ String }
我们有两个属性要写 . 它们非常简单:
position ∷ C → (Int, Int)
position self = (x self, y self)
name ∷ C → String
name self = _name self
现在我们只需要能够更新位置:
setPosition ∷ C → (Int, Int) → C
setPosition self (newX, newY) = self { x = newX, y = newY }
我们可以为某些类别 C 定义F代数 . 首先,我们需要一个算子 F : C → C - 即一个endofunctor . (所有Haskell Functor s实际上都是来自 Hask → Hask 的endofunctors . )然后,代数只是来自 C 的一个对象 A ,其中有一个态射 F A → A . 余代数是相同的,除了 A → F A .
现在,我们有两个操作: f ∘ f → f 和 Identity → f . 为了获得相应的代数,我们只需翻转箭头 . 这为我们提供了两个新操作: f → f ∘ f 和 f → Identity . 我们可以通过添加上面的类型变量将它们变成Haskell类型,给我们 ∀α. f α → f (f α) 和 ∀α. f α → α . 这看起来就像comonad的定义:
class Functor f ⇒ Comonad f where
coreturn ∷ f α → α
cojoin ∷ f α → f (f α)
4 回答
F-代数和F-余代数是数学结构,有助于推理归纳类型(或递归类型) .
F-algebras
我们先从F-algebras开始 . 我会尽量简单 .
我想你知道什么是递归类型 . 例如,这是整数列表的类型:
很明显它是递归的 - 实际上,它的定义指的是它自己 . 它的定义由两个数据构造函数组成,它们具有以下类型:
请注意,我已将
Nil
的类型写为() -> IntList
,而不仅仅是IntList
. 从理论的角度来看,这些实际上是等同的类型,因为()
类型只有一个居民 .如果我们以更集理论的方式编写这些函数的签名,我们就会得到
其中
1
是一个单位集(用一个元素设置),A × B
操作是两个集合A
和B
的交叉积(即成对的集合(a, b)
,其中a
遍历A
的所有元素,b
遍历B
的所有元素) .两组
A
和B
的不相交联合是一组A | B
,它是集合{(a, 1) : a in A}
和{(b, 2) : b in B}
的并集 . 本质上它是A
和B
的所有元素的集合,但是每个元素'marked'属于A
或B
,所以当我们从A | B
中选择任何元素时,我们将立即知道该元素是来自A
还是来自B
.我们可以'join'
Nil
和Cons
函数,因此它们将形成一个工作在集合1 | (Int × IntList)
上的单个函数:实际上,如果
Nil|Cons
函数应用于()
值(显然属于1 | (Int × IntList)
集),那么它的行为就好像它是Nil
;如果Nil|Cons
应用于(Int, IntList)
类型的任何值(此类值也在集1 | (Int × IntList)
中,则其行为为Cons
.现在考虑另一种数据类型:
它有以下构造函数:
也可以加入一个函数:
可以看出,这两个
joined
函数都有类似的类型:它们看起来都像其中
F
是一种转换,它采用我们的类型并提供更复杂的类型,包括x
和|
操作,T
的用法以及可能的其他类型 . 例如,对于IntList
和IntTree
F
,如下所示:我们可以立即注意到任何代数类型都可以用这种方式编写 . 实际上,这就是为什么它们被称为“代数”:它们由许多“总和”(工会)和其他类型的“产品”(交叉产品)组成 .
现在我们可以定义F代数 . F代数只是一对
(T, f)
,其中T
是某种类型,f
是f :: F T -> T
类型的函数 . 在我们的例子中,F-代数是(IntList, Nil|Cons)
和(IntTree, Leaf|Branch)
. 但请注意,尽管每个F的f
函数类型相同,但T
和f
本身可以是任意的 . 例如,某些g
和h
的(String, g :: 1 | (Int x String) -> String)
或(Double, h :: Int | (Double, Double) -> Double)
也是相应F的F-代数 .之后我们可以引入F-代数同态,然后引入具有非常有用性质的初始F-代数 . 事实上,
(IntList, Nil|Cons)
是一个初始的F1代数,(IntTree, Leaf|Branch)
是一个初始的F2代数 . 我不会提供这些术语和属性的确切定义,因为它们比需要的更复杂和抽象 .尽管如此,例如
(IntList, Nil|Cons)
是F-代数的事实允许我们在这种类型上定义类似于fold
的函数 . 如您所知,fold是一种将一些递归数据类型转换为一个有限值的操作 . 例如,我们可以将整数列表折叠为单个值,该值是列表中所有元素的总和:可以在任何递归数据类型上概括此类操作 .
以下是
foldr
功能的签名:请注意,我使用大括号将前两个参数与最后一个参数分开 . 这不是真正的
foldr
函数,但它与它同构(也就是说,你可以很容易地从另一个中得到一个,反之亦然) . 部分应用foldr
将具有以下签名:我们可以看到这是一个函数,它获取整数列表并返回一个整数 . 让我们根据
IntList
类型定义这样的函数 .我们看到这个函数由两部分组成:第一部分定义了这个函数行为
Nil
IntList
的一部分,第二部分定义函数在Cons
部分的行为 .现在假设我们不是在Haskell中编程,而是在某种语言中允许直接在类型签名中使用代数类型(从技术上讲,Haskell允许通过元组和
Either a b
数据类型使用代数类型,但这会导致不必要的冗长) . 考虑一个功能:可以看出
reductor
是F1 Int -> Int
类型的函数,就像F-代数的定义一样!事实上,这对(Int, reductor)
是一个F1代数 .因为
IntList
是一个初始的F1代数,对于每个类型T
并且对于每个函数r :: F1 T -> T
,存在一个函数,称为r
的catamorphism,它将IntList
转换为T
,并且这样的函数是唯一的 . 实际上,在我们的例子中,reductor
的一个变形是sumFold
. 注意reductor
和sumFold
是如何相似的:它们具有几乎相同的结构!在reductor
中定义s
参数用法(其类型对应于T
)对应于sumFold
定义中sumFold xs
的计算结果的使用 .只是为了让它更清晰并帮助你看到模式,这是另一个例子,我们再次从得到的折叠函数开始 . 考虑
append
函数,它将第一个参数追加到第二个参数:这看起来如何
IntList
:再次,让我们尝试写出缩减器:
appendFold
是appendReductor
的一个变形,它将IntList
转换为IntList
.因此,基本上,F代数允许我们在递归数据结构上定义“折叠”,即将我们的结构减少到某个值的操作 .
F-coalgebras
F-coalgebras是F-algebras的所谓'dual'术语 . 它们允许我们为递归数据类型定义
unfolds
,即从某个值构造递归结构的方法 .假设您有以下类型:
这是一个无限的整数流 . 它唯一的构造函数具有以下类型:
或者,就集合而言
Haskell允许您对数据构造函数进行模式匹配,因此您可以在
IntStream
上定义以下函数:您可以自然地将这些函数'join'转换为
IntStream -> Int × IntStream
类型的单个函数:注意函数的结果如何与
IntStream
类型的代数表示一致 . 对于其他递归数据类型也可以执行类似的操作 . 也许你已经注意到了这种模式 . 我指的是一系列类型的函数其中
T
是某种类型 . 从现在开始,我们将定义现在,F-coalgebra是一对
(T, g)
,其中T
是一个类型,g
是g :: T -> F T
类型的函数 . 例如,(IntStream, head&tail)
是F1-余代数 . 同样,就像在F-代数中一样,g
和T
可以是任意的,例如,(String, h :: String -> Int x String)
也是某些h的F1-代数 .在所有的F-coalgebras中都有所谓的终端F-余代数,它们是初始F-代数的双重代数 . 例如,
IntStream
是终端F-余代数 . 这意味着对于每个类型T
和每个函数p :: T -> F1 T
都存在一个名为anamorphism的函数,它将T
转换为IntStream
,并且这样的函数是唯一的 .考虑以下函数,它从给定的一个开始生成连续整数的流:
现在让我们检查函数
natsBuilder :: Int -> F1 Int
,即natsBuilder :: Int -> Int × Int
:再次,我们可以看到
nats
和natsBuilder
之间的某些相似之处 . 它与我们之前在减速器和折叠中观察到的连接非常相似 .nats
是natsBuilder
的变形 .另一个例子,一个函数,它接受一个值和一个函数,并将函数的连续应用程序流返回给值:
它的构建器功能如下:
然后
iterate
是iterateBuilder
的变形 .结论
因此,简而言之,F-代数允许定义折叠,即将递归结构减少为单个值的操作,而F-coalgebras允许相反:从单个值构造[潜在]无限结构 .
事实上在Haskell F-algebras和F-coalgebras重合 . 这是一个非常好的属性,这是每种类型中存在“底部”值的结果 . 所以在Haskell中,可以为每个递归类型创建折叠和展开 . 然而,这背后的理论模型比我上面提到的更复杂,所以我故意避免它 .
希望这可以帮助 .
通过教程论文A tutorial on (co)algebras and (co)induction应该会给你一些关于计算机科学中的共同代数的见解 .
下面是它的引用来说服你,
Prelude, about Category theory. 类别理论应该重命名仿函数理论 . 因为类别是定义仿函数必须定义的类别 . (此外,函子是人们必须定义的,以便定义自然变换 . )
What's a functor? 这是从一个集合到另一个集合的转换,它保留了它们的结构 . (有关详细信息,网上有很多很好的描述) .
What's is an F-algebra? 它只是研究仿函数的普遍适用性 .
How can it be link to computer science ? 程序可以作为一组结构化信息进行查看 . 程序的执行对应于此结构化信息集的修改 . 执行应保留程序结构听起来不错 . 然后可以将执行视为这组信息的仿函数应用程序 . (定义程序的那个) .
Why F-co-algebra ? 程序本质上是双重的,因为它们是通过信息来描述的,并且它们对它起作用 . 那么主要是组成程序并使其改变的信息可以以两种方式查看 .
可以定义为程序正在处理的信息的数据 .
可以定义为程序共享信息的状态 .
那么在这个阶段,我想说,
F-代数是对数据宇宙上的函数变换的研究(如此处所定义) .
F-co-algebras是研究状态宇宙上的函数变换(如本文所定义) .
在程序的生命周期中,数据和状态共存,并且它们彼此完成 . 他们是双重的 .
代数
我认为开始的地方是理解 algebra 的想法 . 这只是代数结构的推广,如群,环,幺半群等 . 大多数时候,这些东西是以集合的形式引入的,但是因为我们会讨论Haskell类型 . (我无法抗拒使用一些希腊字母 - 它们让一切看起来都更酷!)
那么,代数只是一种具有一些功能和身份的类型
τ
. 这些函数使用不同数量的τ
类型的参数并生成τ
:uncurried,它们看起来都像(τ, τ,…, τ) → τ
. 它们还可以具有"identities"-元素τ
,它们对某些函数有特殊行为 .最简单的例子就是幺半群 . monoid是任何类型
τ
,其函数为mappend ∷ (τ, τ) → τ
,标识为mzero ∷ τ
. 其他示例包括诸如组之类的东西(除了具有额外的invert ∷ τ → τ
函数之外,它们就像monoids一样),环,格子等等 .所有功能都在
τ
上运行,但可以有不同的arities . 我们可以将它们写成τⁿ → τ
,其中τⁿ
映射到n
τ
的元组 . 这样,将身份视为τ⁰ → τ
是有意义的,其中τ⁰
只是空元组()
. 所以我们现在可以实际上简化代数的概念:它只是一些带有一些函数的类型 .代数只是数学中常见的模式,已被“排除”,就像我们使用代码一样 . 人们注意到一大堆有趣的东西 - 前面提到的幺半群,群体,格子等都遵循类似的模式,因此他们将其抽象出来 . 这样做的好处与编程相同:它创建了可重复使用的证据,并使某些推理更容易 .
F-Algebras
但是,我们还没有完成因子分解 . 到目前为止,我们有一堆函数
τⁿ → τ
. 我们实际上可以做一个巧妙的技巧,将它们全部合并到一个函数中 . 特别是,让我们看看幺半群:我们有mappend ∷ (τ, τ) → τ
和mempty ∷ () → τ
. 我们可以使用和类型Either
将它们转换为单个函数 . 它看起来像这样:对于任何代数,我们实际上可以反复使用这个转换将所有
τⁿ → τ
函数组合成一个函数 . (事实上,对于任何a, b,…
,我们可以为任意数量的函数a → τ
,b → τ
等执行此操作 . )这让我们可以将代数作为
τ
类型进行讨论,其中包含从Either
的一些混乱到单个τ
的单个函数 . 对于幺半群,这个烂摊子是:Either (τ, τ) ()
;对于组(具有额外的τ → τ
操作),它是:Either (Either (τ, τ) τ) ()
. 对于每种不同的结构,它都是不同的类型 . 那么所有这些类型有什么共同之处呢?最明显的是它们都只是产品 - 代数数据类型的总和 . 例如,对于monoids,我们可以创建一个monoid参数类型,适用于任何monoidτ:我们可以对组,环和格子以及所有其他可能的结构做同样的事情 .
所有这些类型还有什么特别之处?好吧,他们都是
Functors
!例如 . :因此,我们可以更广泛地概括我们的代数概念 . 对于某些函子
f
,它只是一些τ
类型的函数f τ → τ
. 事实上,我们可以把它写成一个类型类:这通常被称为"F-algebra",因为它由仿函数
F
决定 . 如果我们可以部分应用类型类,我们可以定义像class Monoid = Algebra MonoidArgument
这样的东西 .Coalgebras
现在,希望你能很好地掌握代数是什么以及它如何只是普通代数结构的推广 . 那么F-coalgebra是什么?好吧,co意味着它是代数的“双重” - 也就是说,我们采用代数并翻转一些箭头 . 我只看到上面定义中的一个箭头,所以我只是翻转它:
这就是全部!现在,这个结论可能看起来有点轻率(嘿) . 它告诉你什么是代数是什么,但是一旦我找到或想出一个好的例子,它并没有真正给出任何有关它如何得到它的见解:P .
类和对象
在阅读了一下之后,我想我很清楚如何使用代数来表示类和对象 . 我们有一个
C
类型,它包含类中对象的所有可能内部状态;类本身是C
上的余代数,它指定了对象的方法和属性 .如代数示例所示,如果我们有
a → τ
和b → τ
等一系列函数用于任何a, b,…
,我们可以使用Either
(和类型)将它们全部组合成单个函数 . 双"notion"将组合一堆τ → a
,τ → b
等类型的函数 . 我们可以使用sum类型的对偶 - 产品类型来实现这一点 . 所以考虑到上面的两个函数(称为f
和g
),我们可以像这样创建一个函数:类型
(a, a)
是一种直接方式的仿函数,所以它肯定符合我们的F-coalgebra概念 . 这个特殊的技巧让我们将一堆不同的函数 - 或者对于OOP,方法 - 打包成一个类型为τ → f τ
的函数 .我们的类型
C
的元素表示对象的内部状态 . 如果对象具有一些可读属性,则它们必须能够依赖于状态 . 最明显的方法是使它们成为C
的函数 . 因此,如果我们想要一个长度属性(例如object.length
),我们将有一个函数C → Int
.我们想要可以采用参数和修改状态的方法 . 为此,我们需要获取所有参数并生成一个新的
C
. 让我们假设setPosition
方法采用x
和y
坐标:object.setPosition(1, 2)
. 它看起来像这样:C → ((Int, Int) → C)
.这里重要的模式是对象的"methods"和"properties"将对象本身作为它们的第一个参数 . 这就像Python中的
self
参数一样,与许多其他语言的隐式this
类似 . 一个代数基本上只是封装了一个self
参数的行为:这就是C → F C
中的第一个C
.所以让's put it all together. Let'想象一个具有
position
属性,name
属性和setPosition
函数的类:我们需要两个部分来代表这个类 . 首先,我们需要表示对象的内部状态;在这种情况下,它只包含两个
Int
和一个String
. (这是我们的类型C
. )然后我们需要提出代表该类的代数 .我们有两个属性要写 . 它们非常简单:
现在我们只需要能够更新位置:
这就像一个带有显式
self
变量的Python类 . 既然我们有一堆self →
函数,我们需要将它们组合成代数的单个函数 . 我们做得到用一个简单的元组:类型
((Int, Int), String, (Int, Int) → c)
-对于任何c
- 是一个仿函数,所以coop
确实具有我们想要的形式:Functor f ⇒ C → f C
.鉴于此,
C
与coop
组成了一个代数,它指定了我上面给出的类 . 您可以看到我们如何使用相同的技术为对象指定任意数量的方法和属性 .这让我们可以使用代数推理来处理类 . 例如,我们可以引入“F-coalgebra同态”的概念来表示类之间的转换 . 这是一个可怕的声音术语,只意味着保留结构的天使之间的转换 . 这使得考虑将类映射到其他类更容易 .
简而言之,F-coalgebra通过拥有一堆属性和方法来表示一个类,这些属性和方法都依赖于包含每个对象内部状态的
self
参数 .其他类别
到目前为止,我们已经将代数和余代数作为Haskell类型进行了讨论 . 代数只是
τ
类型,函数为f τ → τ
,余代数只是τ
类型,函数为τ → f τ
.但是,没有什么能真正将这些想法与Haskell本身联系起来 . 事实上,它们通常是根据集合和数学函数而不是类型和Haskell函数引入的 . 实际上,我们可以将这些概念概括为任何类别!
我们可以为某些类别
C
定义F代数 . 首先,我们需要一个算子F : C → C
- 即一个endofunctor . (所有HaskellFunctor
s实际上都是来自Hask → Hask
的endofunctors . )然后,代数只是来自C
的一个对象A
,其中有一个态射F A → A
. 余代数是相同的,除了A → F A
.考虑其他类别我们可以获得什么?好吧,我们可以在不同的环境中使用相同的想法 . 像单子一样 . 在Haskell中,monad是某种类型
M ∷ ★ → ★
,有三个操作:map
函数只是M
是Functor
这一事实的证明 . 所以我们可以说monad只是一个有两个操作的函子:return
和join
.函子自己形成一个类别,它们之间的态射是所谓的"natural transformations" . 自然变换只是将一个仿函数转换为另一个仿函数同时保留其结构的一种方法 . Here's一篇很好的文章,帮助解释这个想法 . 它谈到了
concat
,这对于列表来说只是join
.使用Haskell仿函数,两个仿函数的组合本身就是仿函数 . 在伪代码中,我们可以这样写:
这有助于我们将
join
视为f ∘ f → f
的映射 .join
的类型是∀α. f (f α) → f α
. 直觉上,我们可以看到一个对所有类型α
有效的函数如何被认为是f
的转换 .return
是一个类似的转变 . 它的类型是∀α. α → f α
. 这看起来与众不同 - 第一个α
不是"in"一个仿函数!幸运的是,我们可以通过在那里添加一个标识函数来解决这个问题:∀α. Identity α → f α
. 所以return
是一个转型Identity → f
.现在我们可以将monad想象成一个代数,基于一些函数
f
,其中包含操作f ∘ f → f
和Identity → f
. 不是't this look familiar? It'非常类似于monoid,它只是某种类型的τ
,其操作是τ × τ → τ
和() → τ
.所以monad就像一个monoid,除了没有类型我们有一个仿函数 . 它是同一种代数,只是在不同的类别中 . (据我所知,这就是“monad只是endofunctors类别中的monoid”的短语 . )
现在,我们有两个操作:
f ∘ f → f
和Identity → f
. 为了获得相应的代数,我们只需翻转箭头 . 这为我们提供了两个新操作:f → f ∘ f
和f → Identity
. 我们可以通过添加上面的类型变量将它们变成Haskell类型,给我们∀α. f α → f (f α)
和∀α. f α → α
. 这看起来就像comonad的定义:因此,comonad是一个endofunctors类别的代数 .
我将从与编程相关的内容开始,然后添加一些数学内容,以尽可能保持具体和实际 .
让我们引用一些关于coinduction的计算机科学家的话......
http://www.cs.umd.edu/~micinski/posts/2012-09-04-on-understanding-coinduction.html
http://adam.chlipala.net/cpdt/html/Coinductive.html
http://www.alexandrasilva.org/#/talks.html
将环境数学上下文与通常的编程任务相关联
什么是“代数”?
代数结构通常如下:
东西
这些东西可以做什么
这应该听起来像1.属性和2.方法的对象 . 或者甚至更好,它应该听起来像类型签名 .
标准数学例子包括monoid⊃group⊃矢量空间⊃"an algebra" . 幺半群就像自动机一样:动词序列(例如,
f.g.h.h.nothing.f.g.f
) . 始终添加历史记录并且永远不会删除它的git
日志将是一个幺半群而不是一个组 . 如果你添加了反转(例如负数,分数,根,删除累积的历史记录,不破坏破碎的镜像),你会得到一个组 .组包含可以一起添加或减去的内容 . 例如,
Duration
可以一起添加 . (但Date
不能 . )持续时间存在于向量空间(不仅仅是一个组),因为它们也可以通过外部数字进行缩放 . (scaling :: (Number,Duration) → Duration
的类型签名 . )代数⊂向量空间可以做另一件事:有一些
m :: (T,T) → T
. 称之为"multiplication"或不要,因为一旦你离开Integers
,"multiplication"(或"exponentiation")应该是不太明显的 .(这就是人们关注(类别理论)通用属性的原因:告诉他们乘法应该做什么或者是什么样的:
)
代数→代数
与乘法相比,乘法更容易以感觉非任意的方式定义,因为从
T → (T,T)
开始,你可以重复相同的元素 . ("diagonal map" - 类似于光谱理论中的对角矩阵/算子)对比通常是跟踪(对角线条目的总和),尽管重要的是你的国家所做的事情;
trace
对于矩阵来说只是一个很好的答案 .一般来说,查看dual space的原因是因为它有时更容易考虑法向量而不是关于它所说的熟悉的几何向量的平面,就像在光线跟踪器中一样 .
驯服(非)结构化数据
数学家可能会像TQFT's那样建模有趣的东西,而程序员则需要与之搏斗
个日期/时间(
+ :: (Date,Duration) → Date
),个地方(
Paris
≠(+48.8567,+2.3508)
!这是一个形状,而不是一个点 . ),非结构化JSON,在某种意义上应该是一致的,
错误但关闭的XML,
极其复杂的GIS数据应满足合理的关系,
正则表达式对你来说意味着什么,但对perl意味着更少 .
CRM应该包含所有执行's phone numbers and villa locations, his (now ex-) wife and kids'名称,生日和所有以前的礼物,每个礼物都应该满足"obvious"关系(对客户来说很明显),这些关系难以编码,
.....
计算机科学家在谈论代数时,通常会考虑定制操作,就像笛卡尔积 . 我相信这就是人们说"Algebras are coalgebras in Haskell"时的意思 . 但是程序员必须对复杂的数据类型进行建模,如
Place
,Date/Time
和Customer
,并使这些模型看起来像现实世界(或至少是最终用户对现实世界的看法)尽可能 - 我相信对偶,可能超越集合世界 .