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

时间:2013-04-15 12:20:43

标签: scala haskell functional-programming programming-languages category-theory

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

2 个答案:

答案 0 :(得分:82)

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

F-代数

我们首先使用F-algebras开始。我会尽量简单。

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

data IntList = Nil | Cons (Int, IntList)
很明显它是递归的 - 实际上,它的定义指的是它自己。它的定义由两个数据构造函数组成,它们具有以下类型:

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

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

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

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

其中1是一个单位集(使用一个元素设置),A × B操作是两组AB的交叉积(即,成对(a, b)其中a遍历A的所有元素,b遍历B的所有元素。

两组AB的不相交联合是一组A | B,它是集合{(a, 1) : a in A}{(b, 2) : b in B}的联合。基本上它是来自AB的所有元素的集合,但是每个元素都标记为'属于AB,因此当我们从A | B中选择任何元素时,我们会立即知道此元素是来自A还是来自B

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

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

确实,如果Nil|Cons函数应用于()值(显然属于1 | (Int × IntList)集),则其行为就像Nil;如果将Nil|Cons应用于(Int, IntList)类型的任何值(此类值也位于集1 | (Int × IntList)中,则其行为为Cons

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

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

它有以下构造函数:

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

也可以加入一个函数:

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

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

f :: F T -> T

其中F是一种转换,它采用我们的类型并提供更复杂的类型,包括x|操作,T和其他可能的用法类型。例如,对于IntListIntTree F,如下所示:

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

我们可以立即注意到任何代数类型都可以用这种方式编写。实际上,这就是为什么它们被称为“代数”的原因:它们由许多“和”组成。' (工会)和'产品' (其他类型的交叉产品)。

现在我们可以定义F代数。 F-algebra 只是一对(T, f),其中T是某种类型,而ff :: F T -> T类型的函数。在我们的示例中,F代数是(IntList, Nil|Cons)(IntTree, Leaf|Branch)。但请注意,尽管每种F的f函数类型相同,但Tf本身可以是任意的。例如,某些(String, g :: 1 | (Int x String) -> String)(Double, h :: Int | (Double, Double) -> Double)的{​​{1}}或g也是相应F的F代数。

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

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

fold

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

以下是foldr (+) 0 [1, 2, 3, 4] -> 1 + 2 + 3 + 4 = 10 功能的签名:

foldr

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

foldr

我们可以看到这是一个函数,它获取整数列表并返回一个整数。让我们根据foldr ((+), 0) :: [Int] -> Int 类型定义此类函数。

IntList

我们看到这个函数由两部分组成:第一部分在sumFold :: IntList -> Int sumFold Nil = 0 sumFold (Cons x xs) = x + sumFold xs 的{​​{1}}部分定义了这个函数的行为,第二部分定义了函数在{{{}}上的行为。 1}}部分。

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

IntList

可以看出ConsEither a b类型的函数,就像F代数的定义一样!实际上,reductor :: () | (Int × Int) -> Int reductor () = 0 reductor (x, s) = x + s 对是一个F1代数。

因为reductor是初始的F1代数,对于每种类型F1 Int -> Int和每个函数(Int, reductor),都存在一个函数,称为 catamorphism for {{ 1}},将IntList转换为T,此功能是唯一的。实际上,在我们的例子中,r :: F1 T -> T的一个解释是r。请注意IntListT的相似之处:它们具有几乎相同的结构!在reductor定义sumFold参数用法(其类型对应于reductor)对应于sumFold定义中reductor的计算结果的使用。

为了让它更清晰并帮助您看到模式,这是另一个例子,我们再次从生成的折叠函数开始。考虑将s函数附加到第二个参数的T函数:

sumFold xs

它在sumFold上的显示方式:

append

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

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

IntListappendFold :: 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

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

F-余代数

F-coalgebras是所谓的“双重”。 F代数项。它们允许我们为递归数据类型定义appendReductor,即从某个值构造递归结构的方法。

假设您有以下类型:

IntList

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

IntList

或者,就集合而言

unfolds

Haskell允许您对数据构造函数进行模式匹配,因此您可以定义以下data IntStream = Cons (Int, IntStream) 的函数:

Cons :: (Int, IntStream) -> IntStream

你可以自然地加入'这些函数转换为Cons :: Int × IntStream -> IntStream 类型的单个函数:

IntStream

注意函数的结果如何与head :: IntStream -> Int head (Cons (x, xs)) = x tail :: IntStream -> IntStream tail (Cons (x, xs)) = xs 类型的代数表示一致。对于其他递归数据类型也可以执行类似的操作。也许你已经注意到了这种模式。我指的是

类型的函数族
IntStream -> Int × IntStream

其中head&tail :: IntStream -> Int × IntStream head&tail (Cons (x, xs)) = (x, xs) 是某种类型。从现在开始,我们将定义

IntStream

现在, F-coalgebra 是一对g :: T -> F T ,其中T是一种类型,F1 T = Int × T (T, g)类型的函数。例如,T是F1-coalgebra。同样,就像在F代数中一样,gg :: T -> F T可以是任意的,例如,(IntStream, head&tail)也是某些h的F1代数。

在所有F-coalgebras中,都有所谓的终端F-coalgebras ,它们与初始F-代数是双重的。例如,g是终端F-余代数。这意味着对于每个类型T和每个函数(String, h :: String -> Int x String),都存在一个名为 anamorphism 的函数,它将IntStream转换为T,并且这种功能是独一无二的。

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

p :: T -> F1 T

现在让我们检查一个函数T,即IntStream

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

同样,我们可以看到natsBuilder :: Int -> F1 IntnatsBuilder :: Int -> Int × Int之间的某些相似之处。它与我们之前在减速器和折叠中观察到的连接非常相似。 natsBuilder :: Int -> Int × Int natsBuilder n = (n, n+1) nats的变形。

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

natsBuilder

它的构建器功能如下:

nats

然后natsBuilderiterate :: (Int -> Int) -> Int -> IntStream iterate f n = Cons (n, iterate f (f n)) 的变形。

结论

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

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

希望这有帮助。

答案 1 :(得分:37)

通过教程论文 A tutorial on (co)algebras and (co)induction 可以让您对计算机科学中的共同代数有所了解。

以下是引用你的引用,

  

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


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

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

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

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

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

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

然后在这个阶段,我想说,

  • F-algebra是对数据宇宙上的函数变换的研究(如此处所定义)。
  • F-co-algebras是研究状态宇宙上的函数变换(如此处所定义)。

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