我在函数式编程和PLT圈子中曾多次听到过“ coalgebras”一词,尤其是当讨论对象,共面体,透镜等时。对该术语进行谷歌搜索会给出一些页面,其中给出了这些结构的数学描述,这对我来说是非常难以理解的。谁能解释编程中上下文中的代数意味着什么,它们的意义是什么,以及它们与对象和子代之间的关系?
我在函数式编程和PLT圈子中曾多次听到过“ coalgebras”一词,尤其是当讨论对象,共面体,透镜等时。对该术语进行谷歌搜索会给出一些页面,其中给出了这些结构的数学描述,这对我来说是非常难以理解的。谁能解释编程中上下文中的代数意味着什么,它们的意义是什么,以及它们与对象和子代之间的关系?
Answers:
我认为应该从理解代数的概念入手。这只是群,环,类群等代数结构的概括。大多数时候,这些东西都是以集合的形式介绍的,但是由于我们是朋友,所以我将改为谈论Haskell类型。(不过,我无法抗拒使用一些希腊字母,它们使所有内容看起来都更酷!)
因此,代数只是一种τ
具有某些功能和标识的类型。这些函数采用不同数量的类型的参数,τ
并产生一个τ
:未经咖喱的,它们看起来都像(τ, τ,…, τ) → τ
。它们也可以具有“身份” τ
,其中的某些元素具有某些功能的特殊行为。
最简单的例子就是类人动物。Monoid是τ
具有函数mappend ∷ (τ, τ) → τ
和标识的任何类型mzero ∷ τ
。其他示例包括诸如组(除了具有附加invert ∷ τ → τ
功能之外,与monoid相似),环,晶格等。
所有功能都可以运行,τ
但是可以具有不同的功能。我们可以将它们写为τⁿ → τ
,其中τⁿ
映射到的元组n
τ
。通过这种方式,它是有道理的思考身份的τ⁰ → τ
地方τ⁰
正好是空的元组()
。因此,我们现在实际上可以简化代数的概念:它只是某种类型,上面有许多函数。
就像我们对代码所做的那样,代数只是数学中的一种常见模式,已被“分解”。人们注意到,一大堆有趣的事物(上述的类半体,组,格子等)都遵循相似的模式,因此将其抽象出来。这样做的好处与编程相同:它可以创建可重用的证明,并使某些类型的推理更容易。
但是,我们还没有完全完成分解。到目前为止,我们有很多功能τⁿ → τ
。实际上,我们可以做一个巧妙的技巧将它们全部组合成一个函数。特别地,让我们看一下monoid:我们有mappend ∷ (τ, τ) → τ
and mempty ∷ () → τ
。我们可以使用sum类型将它们变成单个函数Either
。它看起来像这样:
op ∷ Monoid τ ⇒ Either (τ, τ) () → τ
op (Left (a, b)) = mappend (a, b)
op (Right ()) = mempty
实际上,我们可以反复使用此转换来合并 所有的τⁿ → τ
功能集成到一个单一的一个,对于任何代数。(事实上,我们可以为任何数量的功能做到这一点a → τ
,b → τ
等了所有 a, b,…
。)
这让我们将代数作为一种τ
具有单个函数的类型来讨论Either
s的到单一τ
。对于幺,这个烂摊子是:Either (τ, τ) ()
; 对于组(具有额外的τ → τ
操作),它是:Either (Either (τ, τ) τ) ()
。每个不同的结构都有不同的类型。那么所有这些类型有什么共同点?最明显的是,它们都是乘积之和-代数数据类型。例如,对于monoid,我们可以创建一个适用于任何 monoidτ 的monoid参数类型:
data MonoidArgument τ = Mappend τ τ -- here τ τ is the same as (τ, τ)
| Mempty -- here we can just leave the () out
我们可以对组,环和晶格以及所有其他可能的结构执行相同的操作。
所有这些类型还有什么特别之处?好吧,他们都是Functors
!例如:
instance Functor MonoidArgument where
fmap f (Mappend τ τ) = Mappend (f τ) (f τ)
fmap f Mempty = Mempty
因此,我们可以进一步推广代数的概念。只是某种类型τ
具有f τ → τ
某种函子功能的f
。实际上,我们可以将其写为类型类:
class Functor f ⇒ Algebra f τ where
op ∷ f τ → τ
由于它是由函子确定的,因此通常称为“ F代数” F
。如果可以部分应用类型类,则可以定义类似的内容class Monoid = Algebra MonoidArgument
。
现在,希望您能很好地了解什么是代数,以及它如何只是普通代数结构的推广。那么什么是F代数?好吧,co表示它是代数的“对偶”-也就是说,我们取一个代数并翻转一些箭头。我在上述定义中只看到一个箭头,所以我将其翻转:
class Functor f ⇒ CoAlgebra f τ where
coop ∷ τ → f τ
仅此而已!现在,这个结论似乎有些浮躁(heh)。它告诉你什么是合并代数,但实际上并没有提供任何有用的见解或我们为何关心的见解。找到或提出一个或两个很好的示例后,我将稍作介绍。
在阅读了一些内容之后,我认为我对如何使用合并代数表示类和对象有一个好主意。我们有一个类型C
,它包含该类中对象的所有可能的内部状态;该类本身是一个结对子,C
它指定对象的方法和属性。
如代数示例所示,如果我们拥有一堆函数,例如a → τ
and b → τ
for any a, b,…
,则可以使用Either
sum类型将它们全部组合为一个函数。双重“概念”将结合类型为type的一堆函数τ → a
,τ → b
依此类推。我们可以使用总和类型(产品类型)的对偶来完成此操作。因此,根据上述两个函数(称为f
和g
),我们可以创建一个像这样的单个函数:
both ∷ τ → (a, b)
both x = (f x, g x)
该类型(a, a)
是直接的函子,因此它确实符合我们的F-coalgebra概念。这个特殊的技巧使我们可以将一堆不同的函数(对于OOP来说,方法)打包成一个type单个函数τ → f τ
。
我们类型的元素C
代表对象的内部状态。如果对象具有某些可读属性,则它们必须能够依赖状态。最明显的方法是使它们成为的函数C
。因此,如果我们想要一个length属性(例如object.length
),我们将有一个function C → Int
。
我们想要可以接受参数并修改状态的方法。为此,我们需要接受所有参数并产生一个新的C
。让我们想象一个setPosition
采用x
和y
坐标的方法:object.setPosition(1, 2)
。它看起来像这样:C → ((Int, Int) → C)
。
这里的重要模式是对象的“方法”和“属性”将对象本身作为其第一个参数。就像self
Python中的参数和this
许多其他语言的隐式一样。一个余代数基本上只是封装的采取的行为self
参数:这是第什么C
的C → F C
是。
所以让我们放在一起。让我们想象一个具有position
属性,name
属性和setPosition
函数的类:
class C
private
x, y : Int
_name : String
public
name : String
position : (Int, Int)
setPosition : (Int, Int) → C
我们需要两部分来代表这个课程。首先,我们需要表示对象的内部状态;在这种情况下,它仅包含两个Int
s和一个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 }
这就像带有显式self
变量的Python类。现在我们有了很多self →
函数,我们需要将它们组合成合并函数的单个函数。我们可以用一个简单的元组来做到这一点:
coop ∷ C → ((Int, Int), String, (Int, Int) → C)
coop self = (position self, name self, setPosition 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函数来介绍它们。确实,我们可以将这些概念概括为任何类别!
我们可以为某些类别定义F代数C
。首先,我们需要一个函子F : C → C
-即endofunctor。(所有Haskell Functor
实际上都是的终结符Hask → Hask
。)然后,代数只是A
来自C
态射的一个对象F A → A
。除了A → F A
。
通过考虑其他类别,我们可以获得什么?好吧,我们可以在不同的上下文中使用相同的想法。像单子。在Haskell中,monad是M ∷ ★ → ★
具有三种操作的某种类型:
map ∷ (α → β) → (M α → M β)
return ∷ α → M α
join ∷ M (M α) → M α
该map
功能只是一个事实,证明M
是一个Functor
。所以我们可以说单子只是一个函子两个操作:return
和join
。
函子本身构成一个类别,它们之间的射态称为所谓的“自然变换”。自然变换只是将一个函子转换为另一个函子,同时保留其结构的一种方法。这是一篇很好的文章,有助于解释这个想法。它谈论concat
,仅join
用于列表。
对于Haskell函子,两个函子的组成就是函子本身。用伪代码,我们可以这样写:
instance (Functor f, Functor g) ⇒ Functor (f ∘ g) where
fmap fun x = fmap (fmap fun) x
这有助于我们将其join
视为的映射f ∘ f → f
。的类型join
是∀α. f (f α) → f α
。直观地,我们可以看到一个函数对所有类型α
被视为对f
。
return
是类似的转变。其类型为∀α. α → f α
。这看起来有所不同-第一个α
不是“在”函子中!幸运的是,我们可以在此处添加一个身份函子来解决此问题:∀α. Identity α → f α
。所以return
是一个转型Identity → f
。
现在我们可以将Monad看作只是一个代数,围绕着一个f
带有运算f ∘ f → f
和的仿函数的代数Identity → f
。这看起来不熟悉吗?与类人动物非常相似,只是一种类型τ
的操作τ × τ → τ
和() → τ
。
因此,一个monad就像一个monoid,除了没有类型,我们有一个仿函数。这是同一种代数,只是类别不同。(据我所知,“ monad在endofunctors类别中只是一个monoid”这句话来自这里。)
现在,我们有两个操作:f ∘ f → f
和Identity → f
。要获得相应的gegebra,我们只需翻转箭头。这给了我们两个新的操作:f → f ∘ f
和f → Identity
。通过如上所述添加类型变量,我们可以将它们转换为Haskell类型,并赋予∀α. f α → f (f α)
和∀α. f α → α
。这看起来像comonad的定义:
class Functor f ⇒ Comonad f where
coreturn ∷ f α → α
cojoin ∷ f α → f (f α)
所以comonad那么一个余代数在endofunctors的类别。
(,)
身份函子与对应()
。id面体类别中的mono面体对象是带有箭头的对象,该箭头与您的mono面体代数相对应,该对象描述了Hask中具有产品类型作为object面体结构的mono面体对象。C上endofunctors类别中的一个monoid对象是C上的monad,因此,您的理解是正确的。:]
F代数和F代数是数学结构,有助于推理归纳类型(或递归类型)。
我们首先从F代数开始。我将尝试变得尽可能简单。
我猜你知道什么是递归类型。例如,这是整数列表的类型:
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
操作的两组矢量积A
和B
(即,对一组(a, b)
,其中a
通过的所有元素进入A
和b
经过的所有元素B
)。
两套脱节工会A
和B
是一组A | B
是集工会{(a, 1) : a in A}
和{(b, 2) : b in B}
。本质上,它是一组来自所有元素A
,并B
属于两种,但每个“标记”这个元素A
或者B
,所以当我们选择的任何元素来自A | B
我们会立刻知道此元素是否来自何处A
或B
。
我们可以“连接” Nil
和Cons
函数,因此它们将形成一个在集合上工作的单个函数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
where F
是一种转换,它采用我们的类型并给出更复杂的类型,它由x
和|
,T
类型以及可能的其他类型组成。例如,for IntList
和IntTree
F
如下所示:
F1 T = 1 | (Int × T)
F2 T = Int | (T × T)
我们可以立即注意到,任何代数类型都可以用这种方式编写。实际上,这就是为什么将它们称为“代数”的原因:它们由许多其他类型的“和”(联合)和“乘积”(叉乘积)组成。
现在我们可以定义F代数。F-代数只是一对(T, f)
,其中T
是某种类型,f
是type 的函数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代数这一事实使我们能够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
我们看到,这个功能由两个部分组成:第一部分定义这个函数的行为Nil
的一部分IntList
,而第二部分定义的函数的行为Cons
的一部分。
现在假设我们不是用Haskell编程,而是使用某种允许直接在类型签名中使用代数类型的语言(嗯,从技术上讲,Haskell允许通过元组和Either a b
数据类型使用代数类型,但这会导致不必要的冗长)。考虑一个函数:
reductor :: () | (Int × Int) -> Int
reductor () = 0
reductor (x, s) = x + s
可以看出,它reductor
是type的函数F1 Int -> Int
,就像F代数的定义一样!实际上,这对(Int, reductor)
是F1代数。
因为IntList
是初始F1-代数,对于每种类型T
和每个功能r :: F1 T -> T
存在的功能,称为catamorphism为r
,它转换IntList
到T
,并且这样的功能是唯一的。事实上,在我们的例子为catamorphism 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]
这在我们的外观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
是appendReductor
转化IntList
为的变形IntList
。
因此,从本质上讲,F代数允许我们在递归数据结构上定义“折叠”,即,将我们的结构缩小至一定值的运算。
F-代数是F-代数的所谓“双重”术语。它们使我们能够定义unfolds
递归数据类型,即从某种值构造递归结构的方法。
假设您具有以下类型:
data IntStream = Cons (Int, IntStream)
这是一个无限的整数流。其唯一的构造函数具有以下类型:
Cons :: (Int, IntStream) -> IntStream
或者,就集合而言
Cons :: Int × IntStream -> IntStream
Haskell允许您对数据构造函数进行模式匹配,因此您可以定义在IntStream
s上运行的以下函数:
head :: IntStream -> Int
head (Cons (x, xs)) = x
tail :: IntStream -> IntStream
tail (Cons (x, xs)) = xs
您自然可以将这些函数“连接”为type的单个函数IntStream -> Int × IntStream
:
head&tail :: IntStream -> Int × IntStream
head&tail (Cons (x, xs)) = (x, xs)
注意函数的结果如何与我们的IntStream
类型的代数表示相一致。对于其他递归数据类型,也可以执行类似的操作。也许您已经注意到了这种模式。我指的是类型的功能族
g :: T -> F T
哪里T
是某种类型。从现在开始,我们将定义
F1 T = Int × T
现在,F-coalgebra是一对(T, g)
,其中T
是类型,g
是type 的函数g :: T -> F T
。例如,(IntStream, head&tail)
是F1代数。再次,就像在F代数中一样,g
并且T
可以是任意的,例如,(String, h :: String -> Int x String)
F1代数也是h。
在所有F代数中,有所谓的末端F代数,它是初始F代数的对偶。例如,IntStream
是终端F-coalgebra。这意味着,对于每种类型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)
同样,我们可以看到nats
和之间有一些相似之处natsBuilder
。这与我们之前观察到的减速器和折痕的连接非常相似。nats
是的变形natsBuilder
。
另一个示例是一个函数,该函数接受一个值和一个函数,并将该函数的连续应用程序流返回到该值:
iterate :: (Int -> Int) -> Int -> IntStream
iterate f n = Cons (n, iterate f (f n))
其构建器功能如下:
iterateBuilder :: (Int -> Int) -> Int -> Int × Int
iterateBuilder f n = (n, f n)
然后iterate
是的变形iterateBuilder
。
因此,简而言之,F代数允许定义折叠,也就是将递归结构缩减为单个值的运算,而F代数允许相反的事情:从单个值构造一个[潜在]无限的结构。
实际上,在Haskell中,F代数和F代数是重合的。这是一个非常好的属性,这是每种类型中都存在“底部”值的结果。因此,在Haskell中,可以为每种递归类型创建折叠和展开。但是,其背后的理论模型比我上面介绍的模型更复杂,因此我特意避免了它。
希望这可以帮助。
appendReductor
看起来有些奇怪,并没有真正帮助我看到那里的模式... :)您可以仔细检查它的正确性吗?..减速器类型总体上应该是什么样?在r
那里的定义是F1
由IntList确定的,还是任意F?
阅读本教程文件有关(共)代数和(共)归纳的教程应该使您对计算机科学中的共代数有一些了解。
下面是它的说服说服您,
一般而言,某种编程语言中的程序会处理数据。在过去几十年的计算机科学发展过程中,很明显,需要对这些数据进行抽象描述,例如,以确保一个人的程序不依赖于其所操作的数据的特定表示形式。而且,这种抽象性有助于正确性证明。
这种愿望导致了在代数规范或抽象数据类型理论这一分支中在计算机科学中使用代数方法。研究的对象本身就是数据类型,使用的是代数熟悉的技术概念。计算机科学家使用的数据类型通常是从给定的(构造函数)运算集合中生成的,正是由于这个原因,代数的“初始化”扮演了如此重要的角色。
实践证明,标准代数技术可用于捕获计算机科学中使用的数据结构的各个基本方面。但是事实证明,很难用代数方式描述计算中出现的某些固有动力学结构。这种结构通常涉及状态的概念,可以以多种方式对其进行转换。此类基于状态的动力系统的形式化方法通常使用自动机或过渡系统,作为经典的早期参考文献。
在过去的十年中,越来越多的人逐渐意识到,这种基于状态的系统不应被描述为代数,而应被称为共同代数。这些是代数的形式对偶,在本教程中将对此进行精确说明。代数的“初始性”的双重属性,即最终性,对于这类共同代数至关重要。这种最终的共代数所需要的逻辑推理原理不是归纳而是共归纳。
关于类别理论的前奏。 范畴论应该重命名函子论。类别是定义函子时必须定义的类别。(此外,函子是定义自然转换所必须定义的。)
什么是函子? 这是从一组到另一组的转换,保留了它们的结构。(有关详细信息,网上有很多不错的描述)。
什么是F代数? 这是函子的代数。这只是对函子普遍适用性的研究。
它如何与计算机科学联系起来? 程序可以视为结构化的信息集。程序的执行对应于此结构化信息集的修改。执行应该保留程序结构,这听起来不错。然后,可以将执行视为函子在这组信息上的应用。(定义程序的那个)。
为什么选择F-co代数? 程序本质上是双重的,因为它们通过信息来描述并对其起作用。那么主要可以通过两种方式查看组成程序并进行更改的信息。
然后在这个阶段,我想说,
在程序的生命周期中,数据和状态共存,并且它们彼此互补。他们是双重的。
我将从明显与编程相关的东西开始,然后添加一些数学的东西,以使其尽可能具体和扎实。
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
π. *)
计算机不能容纳所有π,因为它只有有限的内存!但是它可以做的是持有一个有限的程序,该程序将产生所需的任意任意长的π扩展。只要仅使用列表的有限部分,就可以根据需要使用该无限列表进行计算。
但是,请考虑以下程序:
let print_third_element (k : int list) = match k with
| _ :: _ :: thd :: tl -> print thd
print_third_element pi
该程序应打印pi的第三位数字。但是在某些语言中,对函数的任何参数在传递给函数之前都要先进行求值(严格而不是惰性的求值)。如果我们使用这个缩减顺序,那么我们上面的程序将永远运行pi的数字,然后将其传递给我们的打印机功能(这永远不会发生)。由于计算机没有无限的内存,因此该程序最终将耗尽内存并崩溃。这可能不是最佳评估顺序。
http://adam.chlipala.net/cpdt/html/Coinductive.html
在像Haskell这样的惰性函数式编程语言中,无处不在的数据结构无处不在。无限列表和更多奇特的数据类型为程序各部分之间的通信提供了方便的抽象。在许多情况下,要实现类似的便利而没有无限的惰性结构,就需要对控制流进行杂技反转。
http://www.alexandrasilva.org/#/talks.html
代数结构通常如下所示:
这听起来像带有1.属性和2.方法的对象。甚至更好,它听起来应该像类型签名。
标准的数学示例包括mono半群⊃组⊃向量空间⊃“代数”。Monoid就像自动机:动词序列(例如f.g.h.h.nothing.f.g.f
)。一个git
总是增加的历史,从来没有日志删除这将是一个独异而不是一组。如果添加反函数(例如,负数,分数,根,删除累积的历史记录,破碎破碎的镜像),则会得到一个分组。
组包含可以相加或相减的事物。例如,Duration
可以将s加在一起。(但是Date
s不能。)持续时间存在于向量空间(不仅仅是一组)中,因为它们也可以由外部数字缩放。(的类型签名scaling :: (Number,Duration) → Duration
。)
代数⊂向量空间还可以做另一件事:有些m :: (T,T) → T
。Integers
称其为“乘法”或不称其为“乘法”,因为一旦离开,“乘”(或“幂”)的含义就不那么明显了。
(这就是为什么人们着眼于(类别理论)通用属性:告诉他们乘法应该做什么或像这样:
)
与乘法相比,以乘法为准的方法更容易定义共乘法,因为从乘法开始T → (T,T)
就可以重复相同的元素。(“对角图” –就像光谱理论中的对角矩阵/算子一样)
Counit通常是迹线(对角线项的总和),尽管同样重要的是Counit的功能;trace
只是矩阵的一个好答案。
通常,查看双重空间的原因是因为在该空间中思考更容易。例如,有时考虑法线矢量要比考虑法线平面更容易,但是您可以使用矢量控制平面(包括超平面)(现在我要说的是熟悉的几何矢量,例如在光线跟踪器中) 。
数学家可能正在建模一些有趣的东西,例如TQFT,而程序员则必须努力
+ :: (Date,Duration) → Date
),Paris
≠ (+48.8567,+2.3508)
!这是一个形状,而不是一个点。),计算机科学家在谈论代数时,通常会想到设置操作,例如笛卡尔积。我相信这就是人们说“代数就是Haskell中的代数”时的意思。但要程序员必须模拟复杂的数据类型,如程度Place
,Date/Time
以及Customer
-和使这些车型看起来就像真实世界(或至少是最终用户的真实世界的看法)尽可能-我相信偶,可能不仅限于集合世界。