"编程代码"在编程环境中意味着什么?

mis*_*tor 334 haskell functional-programming scala category-theory recursion-schemes

我在函数式编程和PLT圈子中多次听到过"enggebras"这个术语,特别是在讨论对象,comonads,镜头等时.谷歌搜索这个术语给出了对这些结构进行数学描述的页面,这对我来说几乎是不可理解的.任何人都可以解释一下代数在编程环境中的意义,它们的意义是什么,以及它们与对象和共同体的关系?

Tik*_*vis 470

代数

我认为开始的地方是理解代数的概念.这只是代数结构的推广,如群,环,幺半群等.大多数时候,这些东西是以集合的形式引入的,但由于我们是朋友之间,我将谈论Haskell类型.(我无法抗拒使用一些希腊字母 - 它们让一切看起来都更酷!)

因此,代数只是?具有一些功能和身份的类型.这些函数使用不同数量的类型参数?并生成?:uncurried,它们看起来都像(?, ?,…, ?) ? ?.它们也可以具有"身份" - ?具有某些功能的特殊行为的元素.

最简单的例子就是幺半群.幺半群是?具有功能mappend ? (?, ?) ? ?和身份的任何类型mzero ? ?.其他例子包括诸如组之类的东西(除了具有额外invert ? ? ? ?功能之外,它们就像幺半群一样),环,格子等等.

所有功能都可以运行,?但可以有不同的功能.我们可以把它们写成?? ? ?,??映射到元组n ?.这样,将身份视为空元组的?? ? ?位置??是有道理的().所以我们现在实际上可以简化代数的概念:它只是一些带有一些函数的类型.

代数只是数学中常见的模式,已被"排除",就像我们使用代码一样.人们注意到一大堆有趣的东西 - 前面提到的幺半群,群体,格子等都遵循类似的模式,因此他们将其抽象出来.这样做的好处与编程相同:它可以创建可重复使用的证据,并使某些推理更容易.

F-代数

但是,我们还没有完成因子分解.到目前为止,我们有很多功能?? ? ?.我们实际上可以做一个巧妙的技巧,将它们全部合并到一个函数中.特别是,让我们来看看幺半群:我们有mappend ? (?, ?) ? ?mempty ? () ? ?.我们可以使用sum类型将它们转换为单个函数Either.它看起来像这样:

op ? Monoid ? ? Either (?, ?) () ? ?
op (Left (a, b)) = mappend (a, b)
op (Right ())    = mempty
Run Code Online (Sandbox Code Playgroud)

我们可以使用这种转变反复结合所有?? ? ?功能集成到一个单一的一个,对于任何代数.(事实上,我们可以为任何数量的功能做到这一点a ? ?,b ? ?对等的任何 a, b,….)

这让我们可以将代数称为?具有单个函数的类型,从一些混乱Either单个函数?.对于幺半群,这个烂摊子是:Either (?, ?) (); 对于团体(有额外的? ? ?操作),它是:Either (Either (?, ?) ?) ().对于每种不同的结构,它都是不同的类型.那么所有这些类型有什么共同之处呢?最明显的是它们都只是产品 - 代数数据类型的总和.例如,对于monoids,我们可以创建一个适用于任何 monoidτ 的monoid参数类型:

data MonoidArgument ? = Mappend ? ? -- here ? ? is the same as (?, ?)
                      | Mempty      -- here we can just leave the () out
Run Code Online (Sandbox Code Playgroud)

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

所有这些类型还有什么特别之处?好吧,他们都是Functors!例如:

instance Functor MonoidArgument where
  fmap f (Mappend ? ?) = Mappend (f ?) (f ?)
  fmap f Mempty        = Mempty
Run Code Online (Sandbox Code Playgroud)

因此,我们可以更广泛地概括我们的代数概念.这只是某种类型?与功能f ? ? ?对一些仿函数f.实际上,我们可以将其写为类型类:

class Functor f ? Algebra f ? where
  op ? f ? ? ?
Run Code Online (Sandbox Code Playgroud)

这通常被称为"F代数",因为它是由仿函数决定的F.如果我们可以部分应用类型类,我们可以定义类似的东西class Monoid = Algebra MonoidArgument.

余代数

现在,希望你能很好地掌握代数是什么,以及它如何只是普通代数结构的推广.什么是F-coalgebra?好吧,co意味着它是代数的"双重" - 也就是说,我们采用代数并翻转一些箭头.我只在上面的定义中看到一个箭头,所以我只是翻转它:

class Functor f ? CoAlgebra f ? where
  coop ? ? ? f ?
Run Code Online (Sandbox Code Playgroud)

这就是全部!现在,这个结论可能看起来有点轻率(嘿).它告诉你什么是代数是什么,但并没有真正给出任何关于它如何有用或我们关心的原因的见解.一旦我找到或想出一个好的例子,我会稍微谈谈它:P.

类和对象

在阅读了一下之后,我想我很清楚如何使用代数来表示类和对象.我们有一个类型C,包含类中对象的所有可能的内部状态; 类本身是一个代数,C它指定了对象的方法和属性.

如代数示例所示,如果我们有一堆函数a ? ?,b ? ?对于任何函数a, b,…,我们可以使用Eithersum类型将它们全部组合成单个函数.双重"概念"将组合一堆类型的函数? ? a,? ? b依此类推.我们可以使用sum类型的对偶 - 产品类型来实现这一点.所以考虑到上面的两个函数(调用fg),我们可以像这样创建一个函数:

both ? ? ? (a, b)
both x = (f x, g x)
Run Code Online (Sandbox Code Playgroud)

这种类型(a, a)是一种直接方式的仿函数,所以它肯定符合我们的F-coalgebra概念.这个特殊的技巧让我们将一堆不同的函数 - 或者对于OOP,方法 - 打包成一个类型的函数? ? f ?.

我们类型的元素C表示对象的内部状态.如果对象具有一些可读属性,则它们必须能够依赖于状态.最明显的方法是使它们成为一种功能C.因此,如果我们想要一个长度属性(例如object.length),我们就会有一个函数C ? Int.

我们想要可以采用参数和修改状态的方法.为此,我们需要获取所有参数并生成新的参数C.让我们想象一个setPosition采用a xy坐标的方法:object.setPosition(1, 2).它看起来像这样:C ? ((Int, Int) ? C).

这里重要的模式是对象的"方法"和"属性"将对象本身作为它们的第一个参数.这就像selfPython中的参数一样,this与许多其他语言的隐含一样.一个余代数基本上只是封装的采取的行为self参数:这是第什么CC ? F C是.

所以我们把它们放在一起吧.让我们设想一个具有position属性,name属性和setPosition函数的类:

class C
  private
    x, y  : Int
    _name : String
  public
    name        : String
    position    : (Int, Int)
    setPosition : (Int, Int) ? C
Run Code Online (Sandbox Code Playgroud)

我们需要两个部分来代表这个类.首先,我们需要表示对象的内部状态; 在这种情况下,它只持有两个Int和一个String.(这是我们的类型C.)然后我们需要提出代表该类的代数.

data C = Obj { x, y  ? Int
             , _name ? String }
Run Code Online (Sandbox Code Playgroud)

我们有两个属性要写.它们非常简单:

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

name ? C ? String
name self = _name self
Run Code Online (Sandbox Code Playgroud)

现在我们只需要能够更新位置:

setPosition ? C ? (Int, Int) ? C
setPosition self (newX, newY) = self { x = newX, y = newY }
Run Code Online (Sandbox Code Playgroud)

这就像一个带有显式self变量的Python类.现在我们有了很多self ?函数,我们需要将它们组合成一个用于代数的单个函数.我们可以用一个简单的元组来做到这一点:

coop ? C ? ((Int, Int), String, (Int, Int) ? C)
coop self = (position self, name self, setPosition self)
Run Code Online (Sandbox Code Playgroud)

类型((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函数引入的.实际上,我们可以将这些概念概括为任何类别!

我们可以为某个类别定义F代数C.首先,我们需要一个仿函数F : C ? C- 即一个endofunctor.(所有的Haskell Functors的实际上是从endofunctors Hask ? Hask).然后,一个代数只是一个对象AC与态射F A ? A.余代数是相同的,除了A ? F A.

考虑其他类别我们可以获得什么?好吧,我们可以在不同的环境中使用相同的想法.像单子一样.在Haskell中,monad是一种M ? ? ? ?有三种操作的类型:

map      ? (? ? ?) ? (M ? ? M ?)
return   ? ? ? M ?
join     ? M (M ?) ? M ?
Run Code Online (Sandbox Code Playgroud)

map功能只是一个事实,证明M是一个Functor.所以我们可以说monad只是一个有两个操作的函子:returnjoin.

函子本身形成一个类别,它们之间的态射是所谓的"自然变换".自然变换只是将一个仿函数转换为另一个仿函数同时保留其结构的一种方法.这是一篇很好的文章,有助于解释这个想法.它谈到concat,这只是join为了列表.

使用Haskell仿函数,两个仿函数的组合本身就是仿函数.在伪代码中,我们可以这样写:

instance (Functor f, Functor g) ? Functor (f ? g) where
  fmap fun x = fmap (fmap fun) x
Run Code Online (Sandbox Code Playgroud)

这有助于我们将其join视为一种映射f ? f ? f.类型join??. f (f ?) ? f ?.直观地,我们可以看到对所有类型有效的函数如何?被视为转换f.

return是一个类似的转变.它的类型是??. ? ? f ?.这看起来与众不同 - 第一个?不是"in"的仿函数!令人高兴的是,我们可以通过在那里添加一个标识函数来解决这个问题:??. Identity ? ? f ?.return转型也是如此Identity ? f.

现在我们可以把monad想象成一个代数,它基于一些f带有操作f ? f ? f和算子的算子Identity ? f.这看起来不熟悉吗?它与monoid非常相似,它只是某种类型?的操作? × ? ? ?() ? ?.

所以monad就像一个monoid,除了没有类型我们有一个functor.它是同一种代数,只是在不同的类别中.(据我所知,这就是"monad只是endofunctors类别中的monoid"这句话.)

现在,我们有这两个操作:f ? f ? fIdentity ? f.为了获得相应的代数,我们只需翻转箭头.这给了我们两个新的操作:f ? f ? ff ? Identity.我们可以通过添加上面的类型变量将它们变成Haskell类型,给我们??. f ? ? f (f ?)??. f ? ? ?.这看起来就像comonad的定义:

class Functor f ? Comonad f where
  coreturn ? f ? ? ?
  cojoin   ? f ? ? f (f ?)
Run Code Online (Sandbox Code Playgroud)

所以comonad那么一个余代数在endofunctors的类别.

  • 这非常有价值.我已经设法通过阅读和例子(例如,看到它们与catamoprhisms一起使用)来模糊关于整个F代数业务的一些直觉,但这对我来说都是非常清楚的.谢谢! (45认同)
  • 这是一个很好的解释. (28认同)
  • 那是一个史诗般的结局! (8认同)
  • 值得一提的是:这里的"endofunctors类别"更确切地说是一个类别,其对象是某些类别的endofunctors,其箭头是自然变换.这是一个monoidal类,其functor组成对应于`(,)`和`()`的标识函子.幺半群类别中的幺半群对象是具有与幺半群代数对应的箭头的对象,其描述了Hask中的幺半群对象,其中产品类型为幺半群结构.C语言中endofunctor类别中的monoid对象是C上的monad,所以是的,你的理解是正确的.:] (7认同)
  • @EdwardKmett:谢谢.我添加的关于类和对象的东西可以吗?我今天才读到它,但似乎有道理. (5认同)
  • 这是一个很好的阐述,Tikhon,非常感谢你!我已经看到过这部分内容以很多不同的方式说出来,但直到现在它才变得如此有意义.你已经完全清楚了解代数的概念.我对于代数仍然模糊(OOP类比并没有真正帮助我),但事实上它只是翻转箭头意味着它不会太难.我还一直期待你将变异对象状态的概念与爱德华的镜头库联系起来,因为它代表了这样一个代数. (3认同)
  • 类似于类的解释似乎是非标准的(虽然我知道有关于该主题的论文!).我认为更常见的(对我来说,更容易理解)类比是考虑无限结构,其中余代数观察密码. (2认同)
  • "comonad只是endofunctors类别中的代数." 感谢这个极好的巨魔标语. (2认同)

Vla*_*eev 83

F-代数和F-余代数是数学结构,有助于推理归纳类型(或递归类型).

F-代数

我们先从F-algebras开始.我会尽量简单.

我想你知道什么是递归类型.例如,这是整数列表的类型:

data IntList = Nil | Cons (Int, IntList)
Run Code Online (Sandbox Code Playgroud)

很明显它是递归的 - 实际上,它的定义指的是它自己.它的定义由两个数据构造函数组成,它们具有以下类型:

Nil  :: () -> IntList
Cons :: (Int, IntList) -> IntList
Run Code Online (Sandbox Code Playgroud)

请注意,我已经写类型Nil() -> IntList,不是简单的IntList.从理论的角度来看,这些实际上是等同的类型,因为()类型只有一个居民.

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

Nil  :: 1 -> IntList
Cons :: Int × IntList -> IntList
Run Code Online (Sandbox Code Playgroud)

其中1是单位设定(设定一个元件),并A × B操作的两组矢量积AB(即,设置对(a, b)其中a通过的所有元素进入Ab通过的所有元素去B).

两套脱节工会AB是一组A | B是集工会{(a, 1) : a in A}{(b, 2) : b in B}.本质上,它是一组来自所有元素的AB,但每次这个元素"标记"为属于任一AB,所以当我们选择的任何元素来自A | B我们会立刻知道此元素是否来自何方AB.

我们可以"加入" NilCons函数,因此它们将构成一个集合的函数1 | (Int × IntList):

Nil|Cons :: 1 | (Int × IntList) -> IntList
Run Code Online (Sandbox Code Playgroud)

实际上,如果Nil|Cons函数应用于()值(显然属于1 | (Int × IntList)集合),那么它的行为就好像它一样Nil; if Nil|Cons应用于任何类型的(Int, IntList)值(这些值也在集合中1 | (Int × IntList),它表现为Cons.

现在考虑另一种数据类型:

data IntTree = Leaf Int | Branch (IntTree, IntTree)
Run Code Online (Sandbox Code Playgroud)

它有以下构造函数:

Leaf   :: Int -> IntTree
Branch :: (IntTree, IntTree) -> IntTree
Run Code Online (Sandbox Code Playgroud)

也可以加入一个函数:

Leaf|Branch :: Int | (IntTree × IntTree) -> IntTree
Run Code Online (Sandbox Code Playgroud)

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

f :: F T -> T
Run Code Online (Sandbox Code Playgroud)

这里F是一种转型这需要我们的类型,并给出更复杂的类型,其中包括x|作业,用途T以及可能的其他类型.例如,for IntListIntTree F看起来如下:

F1 T = 1 | (Int × T)
F2 T = Int | (T × T)
Run Code Online (Sandbox Code Playgroud)

我们可以立即注意到任何代数类型都可以用这种方式编写.实际上,这就是为什么它们被称为"代数":它们由许多"总和"(联合)和其他类型的"产品"(交叉产品)组成.

现在我们可以定义F代数.F代数只是一对(T, f),它T是某种类型,f是类型的函数f :: F T -> T.在我们的例子中,F-代数是(IntList, Nil|Cons)(IntTree, Leaf|Branch).但是请注意,尽管这种类型的f功能是按照每个F相同的,T并且f自己可以随心所欲.例如,(String, g :: 1 | (Int x String) -> String)或者(Double, h :: Int | (Double, Double) -> Double)对于某些g并且h也是对应F的F-代数.

之后我们可以引入F-代数同态,然后引入具有非常有用性质的初始F-代数.事实上,它(IntList, Nil|Cons)是一个初始的F1代数,并且(IntTree, Leaf|Branch)是一个初始的F2代数.我不会提供这些术语和属性的确切定义,因为它们比需要的更复杂和抽象.

尽管如此,事实上,比如(IntList, Nil|Cons)F-algebra允许我们fold在这种类型上定义类似函数.如您所知,fold是一种将一些递归数据类型转换为一个有限值的操作.例如,我们可以将整数列表折叠为单个值,该值是列表中所有元素的总和:

foldr (+) 0 [1, 2, 3, 4] -> 1 + 2 + 3 + 4 = 10
Run Code Online (Sandbox Code Playgroud)

可以在任何递归数据类型上概括此类操作.

以下是foldr功能的签名:

foldr :: ((a -> b -> b), b) -> [a] -> b
Run Code Online (Sandbox Code Playgroud)

请注意,我使用大括号将前两个参数与最后一个参数分开.这不是真正的foldr功能,但它是同构的(也就是说,你可以轻松地从另一个中获得一个,反之亦然).部分应用foldr将具有以下签名:

foldr ((+), 0) :: [Int] -> Int
Run Code Online (Sandbox Code Playgroud)

我们可以看到这是一个函数,它获取整数列表并返回一个整数.让我们根据我们的IntList类型定义这样的函数.

sumFold :: IntList -> Int
sumFold Nil         = 0
sumFold (Cons x xs) = x + sumFold xs
Run Code Online (Sandbox Code Playgroud)

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

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

reductor :: () | (Int × Int) -> Int
reductor ()     = 0
reductor (x, s) = x + s
Run Code Online (Sandbox Code Playgroud)

可以看出,它reductor是类型的函数F1 Int -> Int,就像F代数的定义一样!实际上,这对(Int, reductor)是F1代数.

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

只是为了让它更清晰并帮助你看到模式,这是另一个例子,我们再次从得到的折叠函数开始.考虑append将第一个参数附加到第二个参数的函数:

(append [4, 5, 6]) [1, 2, 3] = (foldr (:) [4, 5, 6]) [1, 2, 3] -> [1, 2, 3, 4, 5, 6]
Run Code Online (Sandbox Code Playgroud)

这看起来如何IntList:

appendFold :: IntList -> IntList -> IntList
appendFold ys ()          = ys
appendFold ys (Cons x xs) = x : appendFold ys xs
Run Code Online (Sandbox Code Playgroud)

再次,让我们尝试写出缩减器:

appendReductor :: IntList -> () | (Int × IntList) -> IntList
appendReductor ys ()      = ys
appendReductor ys (x, rs) = x : rs
Run Code Online (Sandbox Code Playgroud)

appendFold为catamorphism appendReductor其将IntList进入IntList.

因此,基本上,F代数允许我们在递归数据结构上定义"折叠",即将结构减少到某个值的操作.

F-余代数

F-coalgebras是F-algebras的所谓"双重"术语.它们允许我们定义unfolds递归数据类型,即从某个值构造递归结构的方法.

假设您有以下类型:

data IntStream = Cons (Int, IntStream)
Run Code Online (Sandbox Code Playgroud)

这是一个无限的整数流.它唯一的构造函数具有以下类型:

Cons :: (Int, IntStream) -> IntStream
Run Code Online (Sandbox Code Playgroud)

或者,就集合而言

Cons :: Int × IntStream -> IntStream
Run Code Online (Sandbox Code Playgroud)

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

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

tail :: IntStream -> IntStream
tail (Cons (x, xs)) = xs
Run Code Online (Sandbox Code Playgroud)

您可以自然地将这些函数"加入"到单个函数类型中IntStream -> Int × IntStream:

head&tail :: IntStream -> Int × IntStream
head&tail (Cons (x, xs)) = (x, xs)
Run Code Online (Sandbox Code Playgroud)

注意函数的结果如何与我们IntStream类型的代数表示一致.对于其他递归数据类型也可以执行类似的操作.也许你已经注意到了这种模式.我指的是一系列类型的函数

g :: T -> F T
Run Code Online (Sandbox Code Playgroud)

哪种T类型.从现在开始,我们将定义

F1 T = Int × T
Run Code Online (Sandbox Code Playgroud)

现在,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存在一个称为变形的函数,它转换TIntStream,并且这种函数是唯一的.

考虑以下函数,它从给定的一个开始生成连续整数的流:

nats :: Int -> IntStream
nats n = Cons (n, nats (n+1))
Run Code Online (Sandbox Code Playgroud)

现在让我们检查一个函数natsBuilder :: Int -> F1 Int,即natsBuilder :: Int -> Int × Int:

natsBuilder :: Int -> Int × Int
natsBuilder n = (n, n+1)
Run Code Online (Sandbox Code Playgroud)

再次,我们可以看到nats和之间的一些相似之处natsBuilder.它与我们之前在减速器和折叠中观察到的连接非常相似.nats是一个变形的natsBuilder.

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

iterate :: (Int -> Int) -> Int -> IntStream
iterate f n = Cons (n, iterate f (f n))
Run Code Online (Sandbox Code Playgroud)

它的构建器功能如下:

iterateBuilder :: (Int -> Int) -> Int -> Int × Int
iterateBuilder f n = (n, f n)
Run Code Online (Sandbox Code Playgroud)

然后iterate是一个变形的iterateBuilder.

结论

因此,简而言之,F-代数允许定义折叠,即将递归结构减少为单个值的操作,而F-coalgebras允许相反:从单个值构造[潜在]无限结构.

事实上在Haskell F-algebras和F-coalgebras重合.这是一个非常好的属性,这是每种类型中存在"底部"值的结果.所以在Haskell中,可以为每个递归类型创建折叠和展开.然而,这背后的理论模型比我上面提到的更复杂,所以我故意避免它.

希望这可以帮助.


zur*_*rgl 37

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

以下是引用你的引用,

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


前奏,关于范畴理论. 类别理论应该重命名仿函数理论.因为类别是定义仿函数必须定义的类别.(此外,仿函数是人们必须定义的,以便定义自然变换.)

什么是算子? 它是从一组到另一组的转换,保留了它们的结构.(有关详细信息,网上有很多很好的描述).

什么是F代数? 它是仿函数的代数.这只是对仿函数普遍适用性的研究.

它如何与计算机科学联系起来? 程序可以作为一组结构化信息进行查看.程序的执行对应于该结构化信息集的修改.执行应保留程序结构听起来不错.然后可以将执行视为这组信息的仿函数应用程序.(定义程序的那个).

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

  • 可以定义为程序正在处理的信息的数据.
  • 可以定义为程序共享信息的状态.

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

  • F代数是对数据宇宙上的函数变换的研究(如这里定义的).
  • F-co-algebras是研究状态宇宙上的函数变换(如这里定义的).

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


iso*_*mes 5

我将从明显与编程相关的内容开始,然后添加一些数学内容,以使其尽可能具体和实际。


让我们引用一些关于联合归纳的计算机科学家的话......

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

归纳法是关于有限数据,协同归纳法是关于无限数据。

无限数据的典型例子是惰性列表(流)的类型。例如,假设我们在内存中有以下对象:

 let (pi : int list) = (* some function which computes the digits of
 ?. *)
Run Code Online (Sandbox Code Playgroud)

计算机不能容纳所有 ?,因为它只有有限的内存量!但是它可以做的是持有一个有限程序,它会产生任意长的 ? 你想要的。只要您只使用列表的有限部分,您就可以根据需要使用该无限列表进行计算。

但是,请考虑以下程序:

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


 print_third_element pi
Run Code Online (Sandbox Code Playgroud)

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

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

在像 Haskell 这样的惰性函数式编程语言中,无限数据结构无处不在。无限列表和更奇特的数据类型为程序各部分之间的通信提供了方便的抽象。在许多情况下,在没有无限惰性结构的情况下实现类似的便利需要控制流的特技反转。

http://www.alexandrasilva.org/#/talks.html 亚历山德拉席尔瓦的代数例子


将环境数学上下文与通常的编程任务相关联

什么是“代数”?

代数结构通常如下所示:

  1. 东西
  2. 东西能做什么

这听起来应该像具有 1. 属性和 2. 方法的对象。或者甚至更好,它应该听起来像类型签名。

标准数学示例包括幺半群?团体 ?向量空间 ? “一个代数”。Monoids 就像自动机:动词序列(例如,f.g.h.h.nothing.f.g.f)。一个git总是添加历史并且从不删除它的日志将是一个幺半群,而不是一个组。如果您添加逆数(例如负数、分数、根、删除累积的历史、取消破碎的镜像),您将得到一个组。

组包含可以一起添加或减去的内容。例如Durations 可以加在一起。(但Dates 不能。)持续时间存在于向量空间(不仅仅是一个组)中,因为它们也可以通过外部数字进行缩放。( . 的类型签名scaling :: (Number,Duration) ? Duration

代数?向量空间还可以做另一件事:有一些m :: (T,T) ? T. Integers称其为“乘法”或不称为“乘法”,因为一旦离开,“乘法”(或“求幂”)应该是什么就不那么明显了。

(这就是为什么人们期待(范畴论)通用属性:告诉他们乘法应该做什么像什么

产品的普遍属性 )


代数?代数

协乘比乘法更容易以一种感觉非任意的方式定义,因为从T ? (T,T)你开始,你可以重复相同的元素。(“对角线图”——就像谱理论中的对角线矩阵/运算符)

Counit 通常是跟踪(对角线条目的总和),尽管同样重要的是您的 counit做了什么;trace只是矩阵的一个很好的答案。

一般而言,查看双空间的原因是因为在该空间中更容易思考。例如,有时考虑法向量比考虑它法线的平面更容易,但是您可以使用向量控制平面(包括超平面)(现在我说的是熟悉的几何向量,就像在光线跟踪器中一样) .


驯服(非)结构化数据

数学家可能正在建模一些有趣的东西,比如TQFT,而程序员必须与

  • 日期/时间 ( + :: (Date,Duration) ? Date),
  • 地方(Paris(+48.8567,+2.3508)!这是一个形状,而不是一个点。),
  • 在某种意义上应该是一致的非结构化 JSON,
  • 错误但关闭的 XML,
  • 非常复杂的 GIS 数据,应该满足大量合理的关系,
  • 正则表达式这意味着东西给你,但意味着相当少到perl。
  • CRM 应该保存所有高管的电话号码和别墅位置、他(现在的前任)妻子和孩子的名字、生日和所有以前的礼物,每一个都应该满足“明显”的关系(对客户来说是显而易见的),这是令人难以置信的很难编码,
  • .....

计算机科学家在谈论余代数时,通常会想到固定运算,例如笛卡尔积。我相信这就是人们说“代数是 Haskell 中的代数”时的意思。但在某种程度上,程序员必须对像PlaceDate/Time和等复杂数据类型Customer进行建模——并使这些模型看起来尽可能像现实世界(或者至少是最终用户对现实世界的看法)——我相信对偶,可能不仅适用于场景世界。