正如Phillip JF提到的,对于半结构逻辑来说,共同体是一个有趣的讨论话题。让我们谈谈线性λ演算。这很像您平常使用的类型化lambda演算,只不过每个变量必须正好使用一次。
为了感受一下,让我们计算给定类型的线性函数,也就是说,
a -> a
具有唯一的一个元素 id
。 而
(a,a) -> (a,a)
有两个变量,id
和 flip
。请注意,在常规的 lambda 演算中,(a,a) -> (a,a)
有四个取值。
(a, b) ↦ (a, a)
(a, b) ↦ (b, b)
(a, b) ↦ (a, b)
(a, b) ↦ (b, a)
但前两种方法要求我们使用其中一个参数两次,而丢弃另一个参数。这正是线性λ演算的本质——不允许这些类型的函数。
顺便提一句,线性LC的目的是什么?嗯,我们可以用它来模拟线性效应或资源使用。例如,如果我们有一个文件类型和几个转换器,它可能看起来像
data File
open :: String -> File
close :: File -> ()
t1 :: File -> File
t2 :: File -> File
然后以下是有效的管道:
close . t1 . t2 . open
close . t2 . t1 . open
close . t1 . open
close . t2 . open
但是这种“分支”计算并不是。
let f1 = open "foo"
f2 = t1 f1
f3 = t2 f1
in close f3
由于我们两次使用了f1
,你可能会有所疑惑。
现在,在这一点上,您可能会对哪些事情必须遵循线性规则产生疑问。例如,我决定某些管道不必包括t1
和t2
(与之前的枚举练习相比)。此外,我引入了open
和close
函数,它们可以愉快地创建和销毁File
类型,尽管这违反了线性规则。
事实上,我们可能会假设存在一些违反线性规则的函数——但并非所有客户端都可能会这样假设。这就像IO
单子—所有的秘密都存在于IO
的实现中,以便用户在“纯粹”的世界中工作。
这就是Comonoid
发挥作用的地方。
class Comonoid m where
destroy :: m -> ()
split :: m -> (m, m)
在线性λ演算中实例化Comonoid
的类型是具有带着销毁和复制规则的携带型。换句话说,这种类型并不是非常受限于线性λ演算。
由于Haskell根本不实现线性λ演算规则,因此我们总是可以实例化Comonoid
。
instance Comonoid a where
destroy a = ()
split a = (a, a)
或者,另一种思考的方式是Haskell是一个线性LC系统,恰好为每种类型实例化 Comonoid
,并自动应用destroy
和split
。
Z_n
代表a
,[]
代表m
,我的运算符是:delete _ = []; split x = [(0, x), (1, x+1), ... (n-1, x+n-1)]
(所有加法都是模n的)。我如何检查是否满足这些定律?如果我想要检查idL $ first delete $ split x = x
,我该如何将其提升到[]
单子中? - n. m.