编程中的“代数”是什么意思?

在函数式编程和PLT圈子里,我多次听到“余数”这个术语,特别是当讨论的是物体,连接器,镜头等时。 谷歌search这个术语给页面给这些结构的math描述,这是我很难理解的。 任何人都可以解释在编程语境下什么是合数,它们的意义是什么,它们是如何与对象和连接器相关的?

代数

我认为开始的地方是理解代数的思想。 这只是一个代数结构的概括,如组,环,单撇子等等。 大多数情况下,这些东西都是以集合的forms来介绍的,但是因为我们是朋友,所以我会讨论一下Haskelltypes。 (尽pipe我使用了一些希腊字母,但我无法抗拒 – 它们让所有事情看起来都更酷!)

那么代数就是一个具有一定function和特性的τtypes。 这些函数采用不同数量的typesτ的参数,并产生一个τ :不感冒,它们都看起来像(τ, τ,…, τ) → τ 。 他们也可以具有“身份” – 具有特殊行为的一些元素的一些function。

这个最简单的例子就是monoid。 幺半群是具有函数mappend ∷ (τ, τ) → τ和同一性mzero ∷ τ任意typesmzero ∷ τ 。 其他的例子包括类似组(除了具有一个额外的invert ∷ τ → τ函数),环,格子等等。

所有的function在τ运行,但可以有不同的属性。 我们可以把它们写成τⁿ → τ ,其中τⁿ映射到τⁿ一个元组。 这样,把身份认为τ⁰ → τ是有意义的,其中τ⁰只是空元组() 。 所以我们实际上可以简化一个代数的思想:它只是一些有许多function的types。

代数只是math中的一个常见模式,就像我们对代码的处理一样。 人们注意到一大堆有趣的东西 – 前面提到的monoids,groups,lattice等 – 都遵循类似的模式,所以他们把它抽象出来。 这样做的好处与编程相同:创build可重复使用的certificate并使某些推理更容易。

F-代数

但是,我们还没有完全考虑因素。 到目前为止,我们有一堆函数τⁿ → τ 。 我们实际上可以做一个巧妙的把戏把它们合并成一个函数。 尤其是,我们来看mappend ∷ (τ, τ) → τ :我们有mappend ∷ (τ, τ) → τmempty ∷ () → τ 。 我们可以使用一个和types来把它们变成一个单一的函数。 它看起来像这样:

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

实际上,我们可以反复使用这个变换来将所有τⁿ → τ函数组合成一个单一的代数。 (事实上​​,对于任何 a, b,… ,我们都可以对任意数量的函数a → τb → τ等等做这个)

这让我们把代数作为一个typesτ用一个单一的函数从任意一个τ到一个τ 。 对于monoids,这个混乱是: Either (τ, τ) () ; 对于组(有一个额外的τ → τ操作),它是:( Either (Either (τ, τ) τ) () 。 对于不同的结构来说,这是一种不同的types。 那么所有这些types有什么共同点呢? 最明显的是它们都是产品的代数数据types。 例如,对于monoids,我们可以创build一个monoid参数types,适用于任何单子τ:

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

我们可以为团体,环和格子以及所有其他可能的结构做同样的事情。

所有这些types还有什么特别之处? 那么,他们都是Functors ! 例如:

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

所以我们可以更加概括一个代数的概念。 对于某个函数f τ → τ它只是一个typesτ ,函数f τ → τ 。 实际上,我们可以把它写成一个types类:

 class Functor f ⇒ Algebra f τ where op ∷ f τ → τ 

这通常称为“F-代数”,因为它是由函数F决定的。 如果我们可以部分应用types类,我们可以定义class Monoid = Algebra MonoidArgument

余代数

现在,希望你能很好地把握代数是什么,以及它是如何代数结构的普遍化。 那么什么是F代数? 那么,它意味着它是代数的“双重” – 也就是说,我们取一个代数并翻转一些箭头。 我只在上面的定义中看到一个箭头,所以我只是翻转一下:

 class Functor f ⇒ CoAlgebra f τ where coop ∷ τ → f τ 

就是这样! 现在,这个结论可能看起来有些轻浮(heh)。 它告诉你什么是代数,但是没有真正了解它是如何有用或为什么我们关心。 一旦我find或者提出一个好的例子,我会做一点。

类和对象

在阅读了一下之后,我想我对如何用代数来表示类和对象有了一个很好的想法。 我们有一个typesC ,它包含了类中所有对象的内部状态。 该类本身是C上的一个余代数,它指定了对象的方法和属性。

如代数示例所示,如果对于任意a, b,…有一组函数,如a → τb → τ ,则可以使用一个和types将它们合并成一个函数。 双“概念”将结合τ → aτ → b等一系列函数。 我们可以使用总和types(产品types)的对偶来实现这一点。 因此,考虑到上述两个函数(称为fg ),我们可以创build一个如下的函数:

 both ∷ τ → (a, b) both x = (fx, gx) 

(a, a)types是一个简单的函数,所以它肯定符合我们的F代数的概念。 这个特殊的技巧让我们把一堆不同的函数打包成一个单一的函数τ → f τ

我们typesC的元素表示对象的内部状态。 如果对象具有一些可读属性,则它们必须能够依赖于状态。 最明显的做法是使它们成为C的函数。 所以,如果我们想要一个长度属性(例如object.length ),我们将有一个函数C → Int

我们需要可以采取参数和修改状态的方法。 要做到这一点,我们需要采取所有的论据,并产生一个新的C 让我们设想一个带有xy坐标的setPosition方法: object.setPosition(1, 2) 。 它看起来像这样: C → ((Int, Int) → C)

这里的重要模式是对象的“方法”和“属性”以对象本身作为第一个参数。 这就像Python中的self参数一样,也像许多其他语言的隐含的一样。 一个余代数本质上是封装了一个self参数的行为:这就是C → FC的第一个C

所以我们把它放在一起。 让我们设想一个具有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 } 

这就像一个具有显式selfvariables的Python类。 现在我们有了一堆self →函数,我们需要把它们合并成一个单一的代数函数。 我们可以用一个简单的元组来做到这一点:

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

types((Int, Int), String, (Int, Int) → c) – 对于任何 c是一个函子,所以coop的forms是我们想要的: Functor f ⇒ C → f C

考虑到这一点, C连同coop形成了一个代数,它指定了上面给出的类。 你可以看到我们如何使用这种相同的技术来为我们的对象指定任意数量的方法和属性。

这让我们用英代数推理来处理类。 例如,我们可以引入一个“F-余代数同态”的概念来表示类之间的转换。 这是一个吓人的声音,只是意味着保留结构的余数之间的转换。 这使得考虑将类映射到其他类更容易。

简而言之,F代数代表一个类,它拥有一系列的属性和方法,这些属性和方法都依赖于包含每个对象内部状态的self参数。

其他类别

到目前为止,我们已经把代数和余代数描述为Haskelltypes。 一个代数只是一个typesτ ,函数f τ → τ而一个余代数只是一个typesτ ,函数τ → f τ

然而,这些想法本身并没有与Haskell真正联系在一起 。 实际上,它们通常是以集合和math函数的forms来引入的,而不是types和Haskell函数。 的确,我们可以将这些概念概括为任何类别!

我们可以为C类定义一个F-代数。 首先,我们需要一个仿函数F : C → C也就是一个内核函数 。 (所有的Haskell Functor实际上都是Hask → Hask )然后,一个代数只是一个来自C的对象A ,态射FA → A 。 除了A → FA之外,余代数是相同的。

我们通过考虑其他类别获得什么? 那么,我们可以在不同的情况下使用相同的想法。 像单子一样。 在Haskell中,monad是一些M ∷ ★ → ★types的M ∷ ★ → ★有三个操作:

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

mapfunction只是MFunctor这个事实的certificate。 所以我们可以说monad只是一个有两个操作的函数: returnjoin

仿函数本身就是一个类,它们之间的态射是所谓的“自然变换”。 自然转换只是将一个函数转换成另一个的一种方式,同时保留其结构。 这里有一篇很好的文章来帮助解释这个想法。 它谈到concat ,这只是join列表。

使用Haskell函子,两个函子的组成本身就是一个函子。 在伪代码中,我们可以这样写:

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

这有助于我们将join看作是从f ∘ f → f的映射。 join的types是∀α. f (f α) → f α ∀α. f (f α) → f α 。 直觉上,我们可以看到,对于所有typesα有效的函数如何被认为是f一个变换。

return是一个类似的转换。 它的types是∀α. α → f α ∀α. α → f α 。 这看起来不同 – 第一个α不是“在”一个函子! 令人高兴的是,我们可以通过在这里添加一个身份函子来解决这个问题: ∀α. Identity α → f α ∀α. Identity α → f α 。 所以return是一个转换Identity → f

现在我们可以把monad看作是一个基于某个函子f的代数,运算f ∘ f → fIdentity → f 。 这看起来不熟悉吗? 它与幺半群非常相似,它只是一个typesτ具有运算τ × τ → τ() → τ

所以monad就像一个monoid,除了有一个types,我们有一个functor。 这是同一种代数,只是在一个不同的类别。 (就我所知,这就是“单子只是一个单一的工人单元”这个词。

现在,我们有这两个操作: f ∘ f → fIdentity → f 。 为了得到相应的代数,我们只需要翻转箭头。 这给了我们两个新的操作: f → f ∘ ff → Identity 。 我们可以通过添加typesvariables将它们变成Haskelltypes,给我们∀α. f α → f (f α) ∀α. f α → α ∀α. f α → f (f α) ∀α. f α → α ∀α. f α → f (f α)∀α. f α → α ∀α. f α → α 。 这看起来就像一个comonad的定义:

 class Functor f ⇒ Comonad f where coreturn ∷ f α → α cojoin ∷ f α → f (f α) 

那么一个共同体就是一个内部pipe理者的代数

F-代数和F-代数是推理归纳types (或recursiontypes )的math结构。

F-代数

我们首先用F-代数开始。 我会尽可能地简单。

我猜你知道什么是recursiontypes。 例如,这是一个整数列表的types:

 data IntList = Nil | Cons (Int, IntList) 

显然这是recursion的 – 实际上,它的定义是指它自己。 其定义由两个数据构造函数组成,它们具有以下types:

 Nil :: () -> IntList Cons :: (Int, IntList) -> IntList 

请注意,我写了Nil as () -> IntList ,而不是简单的IntList 。 事实上,从理论的angular度来看,这些实际上是相同的types,因为()types只有一个居民。

如果我们以更理论的方式写这些函数的签名,我们就会得到

 Nil :: 1 -> IntList Cons :: Int × IntList -> IntList 

其中1是一个单位集合(由一个元素设置), A × B操作是两个集合AB的交叉乘积(也就是,一组对(a, b) ,其中a经过Ab所有元素通过B )的所有元素。

两个集合AB不相交联合是集合A | B A | B{(a, 1) : a in A} {(b, 2) : b in B} {(a, 1) : a in A} {(b, 2) : b in B}的集合。 本质上它是一组来自AB的所有元素,但是这些元素中的每一个都被标记为属于AB ,所以当我们从A | B A | B我们会立刻知道这个元素是来自A还是来自B

我们可以“join” NilCons函数,所以它们将在一个集合1 | (Int × IntList)上形成一个单一的函数 1 | (Int × IntList)

 Nil|Cons :: 1 | (Int × IntList) -> IntList 

实际上,如果Nil|Cons函数应用于()值(显然,它属于1 | (Int × IntList)集合),那么它的行为就好像是Nil ; 如果Nil|Cons应用于任何types的值(Int, IntList) (这些值也在集合1 | (Int × IntList) ,则它的行为如同Cons

现在考虑另一种数据types:

 data IntTree = Leaf Int | Branch (IntTree, IntTree) 

它有以下构造函数:

 Leaf :: Int -> IntTree Branch :: (IntTree, IntTree) -> IntTree 

也可以join一个function:

 Leaf|Branch :: Int | (IntTree × IntTree) -> IntTree 

可以看出,这两个joined函数都具有相似的types:它们都看起来像

 f :: FT -> T 

其中F是一种types转换,其types更为复杂,由x| 操作, T和其他可能的types的用法。 例如,对于IntListIntTree F外观如下所示:

 F1 T = 1 | (Int × T) F2 T = Int | (T × T) 

我们可以立即注意到,任何代数types都可以这样写。 实际上,这就是为什么他们被称为“代数”的原因:它们是由一些其他types的“和”(“联合”)和“产品”(交叉产品)构成的。

现在我们可以定义F-代数。 F-代数只是一对(T, f) ,其中T是某种types, ff :: FT -> T的函数。 在我们的例子中,F-algebras是(IntList, Nil|Cons)(IntTree, Leaf|Branch) 。 然而请注意,尽pipef函数的types对于每个F都是相同的,但是Tf本身可以是任意的。 例如,对于一些gh(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-代数。 我不会提出这些术语和属性的确切定义,因为它们比需要的更加复杂和抽象。

尽pipe如此, (IntList, Nil|Cons)是F代数的事实允许我们在这种types上定义fold like函数。 如你所知,fold是一种将某个recursion数据types转化为一个有限值的操作。 例如,我们可以将整数列表折叠成单个值,该列表是列表中所有元素的总和:

 foldr (+) 0 [1, 2, 3, 4] -> 1 + 2 + 3 + 4 = 10 

在任何recursion数据types上推广这样的操作是可能的。

以下是foldr函数的签名:

 foldr :: ((a -> b -> b), b) -> [a] -> b 

请注意,我用大括号来分隔最后一个参数的前两个参数。 这不是真正的foldr函数,但是它是同构的(也就是说,你可以很容易地从另一个中获得一个,反之亦然)。 部分应用的foldr将具有以下签名:

 foldr ((+), 0) :: [Int] -> Int 

我们可以看到这是一个函数,它接受一个整数列表并返回一个整数。 我们用我们的IntListtypes来定义这样的函数。

 sumFold :: IntList -> Int sumFold Nil = 0 sumFold (Cons x xs) = x + sumFold xs 

我们看到这个函数由两部分组成:第一部分在IntList Nil部分上定义了这个函数的行为,第二部分在Cons部分定义函数的行为。

现在假设我们不是在Haskell编程,而是使用某种允许直接在types签名中使用代数types的语言(从技术上讲,Haskell允许通过元组使用代数types, Either ab数据types,但是这会导致不必要的冗长)。 考虑一个函数:

 reductor :: () | (Int × Int) -> Int reductor () = 0 reductor (x, s) = x + s 

可以看出, reductor是typesF1 Int -> Int的函数,就像F代数的定义一样! 事实上,这个对(Int, reductor)是一个F1代数。

因为IntList是一个初始的F1-代数,对于每一个typesT和对于每个函数r :: F1 T -> T ,都存在一个称为catamorphism for r的函数, 它将IntList转换为T ,并且这个函数是唯一的。 事实上,在我们的例子中, reductorsumFoldsumFold 。 注意reductorsumFold是如何相似的:他们有几乎相同的结构! 在reductor定义中,参数用法(其types对应于T )对应sumFold xssumFold定义中计算结果的使用。

只是为了使它更清楚,并帮助您看到模式,这里是另一个例子,我们再次从最终的折叠函数开始。 考虑append函数,它将第一个参数附加到第二个参数:

 (append [4, 5, 6]) [1, 2, 3] = (foldr (:) [4, 5, 6]) [1, 2, 3] -> [1, 2, 3, 4, 5, 6] 

这是怎么看我们的IntList

 appendFold :: IntList -> IntList -> IntList appendFold ys () = ys appendFold ys (Cons x xs) = x : appendFold ys xs 

再次,让我们试着写出减速器:

 appendReductor :: IntList -> () | (Int × IntList) -> IntList appendReductor ys () = ys appendReductor ys (x, rs) = x : rs 

appendFold是将IntList转换成IntList appendReductor变形。

因此,本质上,F-代数允许我们在recursion数据结构上定义“折叠”,即将结构减less到一定价值的操作。

F-余代数

F-代数是所谓F-代数的“双重”项。 它们允许我们定义recursion数据types的unfolds ,也就是从某种价值构造recursion结构的方式。

假设你有以下types:

 data IntStream = Cons (Int, IntStream) 

这是一个无限的整数stream。 它唯一的构造函数具有以下types:

 Cons :: (Int, IntStream) -> IntStream 

或者,就集合而言

 Cons :: Int × IntStream -> IntStream 

Haskell允许您在数据构造函数上进行模式匹配,因此您可以定义在IntStream工作的以下函数:

 head :: IntStream -> Int head (Cons (x, xs)) = x tail :: IntStream -> IntStream tail (Cons (x, xs)) = xs 

你可以将这些函数自然地“join” IntStream -> Int × IntStreamtypes的单个函数IntStream -> Int × IntStream

 head&tail :: IntStream -> Int × IntStream head&tail (Cons (x, xs)) = (x, xs) 

注意函数的结果如何与我们的IntStreamtypes的代数表示相一致。 对于其他recursion数据types也可以做类似的事情。 也许你已经注意到了这个模式。 我指的是一个types的函数族

 g :: T -> FT 

其中T是某种types。 从现在起我们将定义

 F1 T = Int × T 

现在, F-代数是一对(T, g) ,其中T是一个types, gg :: T -> FT的函数。 例如(IntStream, head&tail)是一个F1代数。 同样,就像在F代数中一样, gT也可以是任意的,例如(String, h :: String -> Int x String)也是一个h的F1代数。

在所有的F-余代数中,有所谓的末端F-余代数 ,它与初始F-代数是双重的。 例如, IntStream是一个terminalF代数。 这意味着对于每个typesT和每个函数p :: T -> F1 T ,都存在一个函数,称为anamorphism ,它将T转换为IntStream ,并且这个函数是唯一的。

考虑下面的函数,它从给定的一个开始产生连续的整数stream:

 nats :: Int -> IntStream nats n = Cons (n, nats (n+1)) 

现在我们natsBuilder :: Int -> F1 Int一个函数natsBuilder :: Int -> F1 Int ,也就是natsBuilder :: Int -> Int × Int

 natsBuilder :: Int -> Int × Int natsBuilder n = (n, n+1) 

再次,我们可以看到natsnatsBuilder之间的一些相似之处。 这与我们之前观察到的还原剂和褶皱的联系非常相似。 natsnatsBuilder的变形。

另一个例子是一个函数,它接受一个值和一个函数,并将该函数的连续应用程序stream返回给值:

 iterate :: (Int -> Int) -> Int -> IntStream iterate fn = Cons (n, iterate f (fn)) 

其构build器function如下:

 iterateBuilder :: (Int -> Int) -> Int -> Int × Int iterateBuilder fn = (n, fn) 

然后iterateiterateBuilder的变形。

结论

所以,简而言之,F-代数可以定义折叠,也就是说,将recursion结构简化为一个单一值的运算,而F-代数则可以做相反的事情:用单一的值构造一个[潜在的]无限的结构。

其实在Haskell F-代数和F-代数是重合的。 这是一个非常好的属性,这是每种types都存在“底层”价值的结果。 所以在Haskell中,可以为每个recursiontypes创build折叠和展开。 然而,这背后的理论模型比我上面提到的更复杂,所以我故意避开它。

希望这可以帮助。

阅读教程论文关于(共)代数和(共)归纳的教程应该给你一些关于计算机科学中的联合代数的见解。

下面是引用它来说服你,

一般而言,某些编程语言中的程序操纵数据。 在过去几十年计算机科学的发展过程中,很明显,对这些数据的抽象描述是可取的,例如,确保一个人的计划不依赖于它所运行的数据的特定表示。 而且,这种抽象性有利于certificate正确性。
这种欲望导致在计算机科学中使用代数方法,在称为代数规范或抽象数据types理论的分支中。 研究对象本身就是数据types,使用代数熟悉的技术概念。 计算机科学家使用的数据types通常是从​​给定的(构造函数)操作集合中产生的,正因为如此,代数的“初始性”起着非常重要的作用。
标准的代数技术已经certificate在捕捉计算机科学中使用的数据结构的各个重要方面是有用的。 但是,结果却很难用代数方法来描述计算中出现的一些固有的dynamic结构。 这样的结构通常涉及一个国家的概念,可以通过各种方式进行转变。 这种基于状态的dynamic系统的forms化方法通常使用自动机或过渡系统作为经典的早期参考。
在过去的十年里,人们逐渐认识到,这种基于状态的系统不应该被描述成代数,而是所谓的共同代数。 这些是代数的forms对偶,在本教程中将以精确的方式。 代数的“初始性”的双重属性,即最终结果对于这样的共同代数是至关重要的。 而最终的共同代数所需要的逻辑推理原则并不是归纳而是共诱导。


前奏,关于分类理论。 分类理论应该是函子的重命名理论。 因为类是定义函数的必须定义的。 (此外,函数是为了定义自然变换必须定义的。)

什么是函子? 这是一个从一套到另一套保持其结构的转变。 (更多的细节在网上有很多很好的描述)。

什么是F代数? 这是函子的代数。 这只是研究函数的普遍适用性。

它如何能够链接到计算机科学? 程序可以被视为一组结构化的信息。 程序的执行对应于对这一组结构化信息的修改。 执行应该保持程序结构,这听起来不错。 然后,可以将执行视为应用函子对这组信息。 (定义程序的那个)。

为什么F-co-algebra? 程序本质上是双重的,因为它们是通过信息来描述的,而且它们都是以此为依据的 那么主要是组成程序并使它们改变的信息可以以两种方式查看。

  • 数据可以被定义为程序处理的信息。
  • 可以定义为程序共享的信息的状态。

那么在这个阶段,我想说,

  • F代数是研究数据宇宙中的函数变换(如此处所定义的)。
  • F-co-algebras是研究作用于状态宇宙的函子变换(如此处所定义的)。

在程序的生命周期中,数据和状态是并存的,并且彼此完成。 他们是双重的。

我将从与编程相关的东西开始,然后添加一些math内容,尽可能保持具体和实际。


我们引用一些计算机科学家关于coinduction …

http://www.cs.umd.edu/~micinski/posts/2012-09-04-on-understanding-coinduction.html

感应是关于有限的数据,共感是关于无限的数据。

无限数据的典型例子是一个懒惰列表(一个stream)的types。 例如,可以说我们在内存中有以下对象:

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

电脑不能容纳所有的π,因为它只有有限的内存! 但是它可以做的是持有一个有限的程序,这个程序会产生任何你想要的任意长的π扩展。 只要你只使用有限的列表,你可以根据你的需要来计算这个无限列表。

但是,请考虑以下程序:

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

这个程序应该打印pi的第三个数字。 但是在某些语言中,函数的任何参数在被传递给函数(严格的,不是懒惰的评估)之前被评估。 如果我们使用这个缩减顺序,那么我们上面的程序将永远运行pi的数字,然后它可以传递给我们的打印机function(永远不会发生)。 由于机器没有无限的内存,程序最终会耗尽内存并崩溃。 这可能不是最好的评估顺序。

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

在Haskell等懒惰的函数式编程语言中,无处不在的数据结构无处不在。 无限的列表和更奇特的数据types为程序各部分之间的通信提供了方便的抽象。 在没有无限的懒惰结构的情况下获得类似的便利,在许多情况下,需要控制stream的杂技倒置。

http://www.alexandrasilva.org/#/talks.html 亚历山德拉·席尔瓦(Alexandra Silva)的代数


将环境math上下文与通常的编程任务联系起来

什么是“代数”?

代数结构一般看起来像:

  1. 东东
  2. 什么东西可以做

这应该听起来像具有属性和方法的对象。 甚至更好,它应该听起来像types签名。

标准的math例子包括幺半群⊃群⊃vector空间⊃“代数”。 fghhnothing.fgf类似于自动机:动词序列(例如, fghhnothing.fgf )。 总是添加历史logging,永远不会删除它的git日志将是一个monoid,而不是一个组。 如果你添加倒数(如负数,分数,根,删除累积的历史,不破碎的镜子),你会得到一个组。

组包含可以一起添加或减去的东西。 例如Duration s可以加在一起。 (但Date不能)。持续时间在向量空间中(而不仅仅是一个组),因为它们也可以被外部数字缩放。 (定scaling :: (Number,Duration) → Durationtypes签名scaling :: (Number,Duration) → Duration

代数⊂vector空间可以做另一件事:有一些m :: (T,T) → T 。 称之为“乘法”或不要,因为一旦离开Integers ,“乘法”(或者“求幂” )应该是什么就不那么明显了。

(这就是为什么人们希望(类别理论)的通用属性:告诉他们什么乘法应该做什么类似

产品的普遍性


代数→余代数

乘法更容易定义,而不是乘法,因为从T → (T,T)可以重复相同的元素。 (“对angular映射” – 如谱理论中的对angularmatrix/运算符)

计数通常是痕迹(对angular线条目的总和),尽pipe重要的是你的计算是什么; trace只是matrix的一个很好的答案。

一般来说,考虑双重空间的原因是因为在这个空间中思考更容易。 例如,考虑一个法向量有时比想象它正常的平面更容易,但是你可以用向量来控制平面(包括超平面)(现在我正在谈论熟悉的几何向量,就像射线追踪器一样) 。


驯服(未)结构化数据

math家可能会像TQFT一样build模一些有趣的东西,而程序员必须与之搏斗

  • date/时间( + :: (Date,Duration) → Date ),
  • Paris(+48.8567,+2.3508) !这是一个形状,而不是一个点。),
  • 非结构化的JSON在某种意义上应该是一致的,
  • 错误但是closures的XML,
  • 令人难以置信的复杂的GIS数据应该满足明智的关系,
  • 正则expression式对你来说意味着什么,但是对Perl来说意味着更less。
  • 客户关系pipe理(CRM)应该包含所有pipe理人员的电话号码和别墅位置,他(现在的前妻)和孩子的姓名,生日和所有以前的礼物,每一个都应该满足难以置信的“显而易见”的关系很难编码,
  • …..

计算机科学家在讨论余代数时,通常都会考虑到笛卡尔的产品。 我相信这就是人们所说的“代数是哈斯克尔中的余数”的意思。 但是程序员必须对PlaceDate/TimeCustomer等复杂数据types进行build模,并使这些模型尽可能地像现实世界(或者至less是terminal用户对现实世界的看法) – 我相信双重的东西,除了集合世界之外,可能是有用的。