每种类型都有独特的变形吗?
Does each type have a unique catamorphism?
最近我终于开始觉得我理解变质了。我在 中写了一些关于它们的内容,但简而言之,我会说一个类型的变形抽象了递归遍历该类型值的过程,该类型的模式匹配具体化为每个构造函数的一个函数类型有。虽然我欢迎对这一点或上面链接的我的答案中的较长版本进行任何更正,但我认为我或多或少对此有所了解,这不是这个问题的主题,只是一些背景知识。
一旦我意识到传递给变形的函数与类型的构造函数完全对应,并且这些函数的参数同样对应于那些构造函数字段的类型,这一切突然感觉很机械,我不看不到替代实施的回旋余地。
例如,我只是编造了这个愚蠢的类型,对它的结构是什么没有真正的概念"means",并为它导出了一个变形。我没有看到任何其他方法可以定义这种类型的通用折叠:
data X a b f = A Int b
| B
| C (f a) (X a b f)
| D a
xCata :: (Int -> b -> r)
-> r
-> (f a -> r -> r)
-> (a -> r)
-> X a b f
-> r
xCata a b c d v = case v of
A i x -> a i x
B -> b
C f x -> c f (xCata a b c d x)
D x -> d x
我的问题是,是否每种类型都有独特的变形(直到参数重新排序)?或者是否存在反例:无法定义变质的类型,或者存在两种不同但同样合理的变质的类型?如果没有反例(即,类型的变形是唯一的并且可以简单地推导),是否有可能让 GHC 为我推导某种类型类来自动完成这项苦差事?
与递归类型关联的变质可以机械地推导。
假设你有一个递归定义的类型,有多个构造函数,每个构造函数都有自己的参数。我会借用OP的例子。
data X a b f = A Int b
| B
| C (f a) (X a b f)
| D a
然后,我们可以通过强制每个 arity 为一个来重写相同的类型,uncurrying everything。如果我们添加单位类型 ()
.
,则零元数 (B
) 变为一元数
data X a b f = A (Int, b)
| B ()
| C (f a, X a b f)
| D a
然后,我们可以将构造函数的数量减少到一个,利用 Either
而不是多个构造函数。下面,为了简洁起见,我们只写中缀 +
而不是 Either
。
data X a b f = X ((Int, b) + () + (f a, X a b f) + a)
在术语级别,我们知道我们可以重写任何递归定义
作为 x = f x where f w = ...
的形式,写出一个显式不动点方程 x = f x
。在类型级别,我们可以使用相同的方法
重构递归类型。
data X a b f = X (F (X a b f)) -- fixed point equation
data F a b f w = F ((Int, b) + () + (f a, w) + a)
现在,我们注意到我们可以自动派生一个仿函数实例。
deriving instance Functor (F a b f)
这是可能的,因为在原始类型中每个递归引用只发生在positive位置。如果这不成立,使得 F a b f
不是函子,那么我们就不能有变形。
最后我们可以把cata
的类型写成这样:
cata :: (F a b f w -> w) -> X a b f -> w
这是OP的xCata
类型吗?这是。我们只需要应用一些类型同构。我们使用以下代数定律:
1) (a,b) -> c ~= a -> b -> c (currying)
2) (a+b) -> c ~= (a -> c, b -> c)
3) () -> c ~= c
顺便说一句,如果我们把(a,b)
写成乘积a*b
,单位()
写成1
,a->b
就很容易记住这些同构]作为幂b^a
。他们确实成为
c^(a*b) = (c^a)^b
c^(a+b) = c^a*c^b
c^1 = c
不管怎样,我们开始重写F a b f w -> w
部分,只
F a b f w -> w
=~ (def F)
((Int, b) + () + (f a, w) + a) -> w
=~ (2)
((Int, b) -> w, () -> w, (f a, w) -> w, a -> w)
=~ (3)
((Int, b) -> w, w, (f a, w) -> w, a -> w)
=~ (1)
(Int -> b -> w, w, f a -> w -> w, a -> w)
现在让我们考虑完整类型:
cata :: (F a b f w -> w) -> X a b f -> w
~= (above)
(Int -> b -> w, w, f a -> w -> w, a -> w) -> X a b f -> w
~= (1)
(Int -> b -> w)
-> w
-> (f a -> w -> w)
-> (a -> w)
-> X a b f
-> w
这确实是(重命名 w=r
)想要的类型
xCata :: (Int -> b -> r)
-> r
-> (f a -> r -> r)
-> (a -> r)
-> X a b f
-> r
cata
的“标准”实现是
cata g = wrap . fmap (cata g) . unwrap
where unwrap (X y) = y
wrap y = X y
由于其笼统性,需要一些努力才能理解,但这确实是预期的。
关于自动化:是的,这可以自动化,至少部分自动化。
hackage 上有包 recursion-schemes
允许
一个写类似
的东西
type X a b f = Fix (F a f b)
data F a b f w = ... -- you can use the actual constructors here
deriving Functor
-- use cata here
示例:
import Data.Functor.Foldable hiding (Nil, Cons)
data ListF a k = NilF | ConsF a k deriving Functor
type List a = Fix (ListF a)
-- helper patterns, so that we can avoid to match the Fix
-- newtype constructor explicitly
pattern Nil = Fix NilF
pattern Cons a as = Fix (ConsF a as)
-- normal recursion
sumList1 :: Num a => List a -> a
sumList1 Nil = 0
sumList1 (Cons a as) = a + sumList1 as
-- with cata
sumList2 :: forall a. Num a => List a -> a
sumList2 = cata h
where
h :: ListF a a -> a
h NilF = 0
h (ConsF a s) = a + s
-- with LambdaCase
sumList3 :: Num a => List a -> a
sumList3 = cata $ \case
NilF -> 0
ConsF a s -> a + s
变形(如果存在)是 definition. In category theory a catamorphism denotes the unique homomorphism from an initial algebra into some other algebra. To the best of my knowledge in Haskell all catamorphisms exists because Haskell's types form a Cartesian Closed Category where terminal objects, all products, sums and exponentials exist. See also Bartosz Milewski's blog post about F-algebras 唯一的,这很好地介绍了主题。
最近我终于开始觉得我理解变质了。我在
一旦我意识到传递给变形的函数与类型的构造函数完全对应,并且这些函数的参数同样对应于那些构造函数字段的类型,这一切突然感觉很机械,我不看不到替代实施的回旋余地。
例如,我只是编造了这个愚蠢的类型,对它的结构是什么没有真正的概念"means",并为它导出了一个变形。我没有看到任何其他方法可以定义这种类型的通用折叠:
data X a b f = A Int b
| B
| C (f a) (X a b f)
| D a
xCata :: (Int -> b -> r)
-> r
-> (f a -> r -> r)
-> (a -> r)
-> X a b f
-> r
xCata a b c d v = case v of
A i x -> a i x
B -> b
C f x -> c f (xCata a b c d x)
D x -> d x
我的问题是,是否每种类型都有独特的变形(直到参数重新排序)?或者是否存在反例:无法定义变质的类型,或者存在两种不同但同样合理的变质的类型?如果没有反例(即,类型的变形是唯一的并且可以简单地推导),是否有可能让 GHC 为我推导某种类型类来自动完成这项苦差事?
与递归类型关联的变质可以机械地推导。
假设你有一个递归定义的类型,有多个构造函数,每个构造函数都有自己的参数。我会借用OP的例子。
data X a b f = A Int b
| B
| C (f a) (X a b f)
| D a
然后,我们可以通过强制每个 arity 为一个来重写相同的类型,uncurrying everything。如果我们添加单位类型 ()
.
B
) 变为一元数
data X a b f = A (Int, b)
| B ()
| C (f a, X a b f)
| D a
然后,我们可以将构造函数的数量减少到一个,利用 Either
而不是多个构造函数。下面,为了简洁起见,我们只写中缀 +
而不是 Either
。
data X a b f = X ((Int, b) + () + (f a, X a b f) + a)
在术语级别,我们知道我们可以重写任何递归定义
作为 x = f x where f w = ...
的形式,写出一个显式不动点方程 x = f x
。在类型级别,我们可以使用相同的方法
重构递归类型。
data X a b f = X (F (X a b f)) -- fixed point equation
data F a b f w = F ((Int, b) + () + (f a, w) + a)
现在,我们注意到我们可以自动派生一个仿函数实例。
deriving instance Functor (F a b f)
这是可能的,因为在原始类型中每个递归引用只发生在positive位置。如果这不成立,使得 F a b f
不是函子,那么我们就不能有变形。
最后我们可以把cata
的类型写成这样:
cata :: (F a b f w -> w) -> X a b f -> w
这是OP的xCata
类型吗?这是。我们只需要应用一些类型同构。我们使用以下代数定律:
1) (a,b) -> c ~= a -> b -> c (currying)
2) (a+b) -> c ~= (a -> c, b -> c)
3) () -> c ~= c
顺便说一句,如果我们把(a,b)
写成乘积a*b
,单位()
写成1
,a->b
就很容易记住这些同构]作为幂b^a
。他们确实成为
c^(a*b) = (c^a)^b
c^(a+b) = c^a*c^b
c^1 = c
不管怎样,我们开始重写F a b f w -> w
部分,只
F a b f w -> w
=~ (def F)
((Int, b) + () + (f a, w) + a) -> w
=~ (2)
((Int, b) -> w, () -> w, (f a, w) -> w, a -> w)
=~ (3)
((Int, b) -> w, w, (f a, w) -> w, a -> w)
=~ (1)
(Int -> b -> w, w, f a -> w -> w, a -> w)
现在让我们考虑完整类型:
cata :: (F a b f w -> w) -> X a b f -> w
~= (above)
(Int -> b -> w, w, f a -> w -> w, a -> w) -> X a b f -> w
~= (1)
(Int -> b -> w)
-> w
-> (f a -> w -> w)
-> (a -> w)
-> X a b f
-> w
这确实是(重命名 w=r
)想要的类型
xCata :: (Int -> b -> r)
-> r
-> (f a -> r -> r)
-> (a -> r)
-> X a b f
-> r
cata
的“标准”实现是
cata g = wrap . fmap (cata g) . unwrap
where unwrap (X y) = y
wrap y = X y
由于其笼统性,需要一些努力才能理解,但这确实是预期的。
关于自动化:是的,这可以自动化,至少部分自动化。
hackage 上有包 recursion-schemes
允许
一个写类似
type X a b f = Fix (F a f b)
data F a b f w = ... -- you can use the actual constructors here
deriving Functor
-- use cata here
示例:
import Data.Functor.Foldable hiding (Nil, Cons)
data ListF a k = NilF | ConsF a k deriving Functor
type List a = Fix (ListF a)
-- helper patterns, so that we can avoid to match the Fix
-- newtype constructor explicitly
pattern Nil = Fix NilF
pattern Cons a as = Fix (ConsF a as)
-- normal recursion
sumList1 :: Num a => List a -> a
sumList1 Nil = 0
sumList1 (Cons a as) = a + sumList1 as
-- with cata
sumList2 :: forall a. Num a => List a -> a
sumList2 = cata h
where
h :: ListF a a -> a
h NilF = 0
h (ConsF a s) = a + s
-- with LambdaCase
sumList3 :: Num a => List a -> a
sumList3 = cata $ \case
NilF -> 0
ConsF a s -> a + s
变形(如果存在)是 definition. In category theory a catamorphism denotes the unique homomorphism from an initial algebra into some other algebra. To the best of my knowledge in Haskell all catamorphisms exists because Haskell's types form a Cartesian Closed Category where terminal objects, all products, sums and exponentials exist. See also Bartosz Milewski's blog post about F-algebras 唯一的,这很好地介绍了主题。