每种类型都有独特的变形吗?

时间:2017-10-04 09:11:59

标签: haskell

最近我终于开始觉得我理解了catamorphisms。我在a recent answer中写了一些关于它们的内容,但是我简单地说,对于递归遍历该类型的值的过程的类型抽象的一个变形,使用该类型的模式匹配为每个构造函数的一个函数。类型有。虽然我欢迎任何有关这一点或我上面链接的答案中的较长版本的更正,但我认为我或多或少都有这个,这不是这个问题的主题,只是一些背景。

一旦我意识到你传递给一个catamorphism的函数完全对应于类型的构造函数,并且这些函数的参数同样对应于那些构造函数字段的类型,它们都突然感觉很机械而我看不到其中有替代实施的任何摆动空间。

例如,我只是编造了这种愚蠢的类型,没有关于它的结构“意味着什么”的真实概念,并为它导出了一个变形。我没有看到任何其他方式我可以定义这种类型的通用折叠:

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

我的问题是,每种类型都有独特的变形(直到参数重新排序)吗?或者是否存在反例:可以定义没有变形的类型,或存在两种截然不同但同样合理的同态的类型?如果没有反例(即,一个类型的catamorphism是唯一且可以简单推导出来的),是否有可能让GHC为我自动导出某种类型的类型,这会自动进行这种苦差事?

2 个答案:

答案 0 :(得分:5)

与递归类型相关的变形可以通过机械方式导出。

假设您有一个递归定义的类型,有多个构造函数,每个构造函数都有自己的arity。我将借用OP的例子。

data X a b f = A Int b
             | B
             | C (f a) (X a b f)
             | D a

然后,我们可以通过强制每个arity成为一个来重写相同的类型,从而解决所有问题。如果我们添加单位类型B,则Arity zero(())将变为1。

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)

这是可能的,因为在原始类型中,每个递归引用仅发生在位置。如果这不成立,使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,将()作为1a->b编写,则很容易记住这些同构现象1}}作为权力b^a。确实他们变成1) c^(a*b) = (c^a)^b , 2) c^(a+b) = c^a*c^b, 3) 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

答案 1 :(得分:0)

通过definition,同构同构关系(如果存在)是唯一的。在范畴论中,同态表示从初始代数到其他代数的唯一同态。就我在Haskell中所知,所有同构存在,因为Haskell的类型形成一个Cartesian Closed Category,其中终端对象,所有乘积和所有指数均存在。另请参见Bartosz Milewski的blog post about F-algebras,它对本主题进行了很好的介绍。