具有`bool`,`both`等功能的模式

时间:2017-07-20 21:03:34

标签: haskell pattern-matching gadt

我最近了解了GADTs及其符号:

E.g。

data Maybe a where
  Nothing :: Maybe a
  Just    :: a -> Maybe a

data Either a b where
  Left  :: a -> Either a b
  Right :: b -> Either a b

data Bool where
  False :: Bool
  True  :: Bool

现在我注意到booleither等功能的相似性,基本上就像GADT定义一样:

  1. 以每一行为参数
  2. 将实际类型替换为字母表的下一个字母
  3. 最后返回一个函数Type -> (the letter of step 2)
  4. E.g。

    maybe  :: b -> (a -> b) -> Maybe a -> b
    either :: (a -> c) -> (b -> c) -> Either a b -> c
    bool   :: a -> a -> Bool -> a
    

    这还包括foldr,但我注意到,例如虽然你可以很容易地定义它,但是元组没有这样的功能:

    tuple :: (a -> b -> c) -> (a,b) -> c
    tuple f (x,y) = f x y
    

    这种模式是什么?在我看来,这些函数减轻了模式匹配的需要(因为它们为每种情况提供了一般方法),因此对该类型进行操作的每个函数都可以根据此函数进行定义。

1 个答案:

答案 0 :(得分:4)

首先,你提到的类型不是真正的GADT,它们是普通的ADT,因为每个构造函数的返回类型总是T a。一个合适的GADT就像是

data T a where
   K1 :: T Bool  -- not T a

无论如何,您提到的技术是将代数数据类型编码为(多态)函数的众所周知的方法。它有许多名称,如教会编码,Boehm-Berarducci编码,终结编码作为一种变形等等。有时Yoneda引理被用来证明这种方法,但是没有必要理解类别理论机制来理解这种方法。 / p>

基本上,这个想法如下。所有ADT都可以通过

生成
  • 产品类型(,) a b
  • 总和类型Either a b
  • 箭头类型a -> b
  • 单位类型()
  • void type Void(在Haskell中很少使用,但理论上很好)
  • 变量(如果定义的bing类型具有参数)
  • 可能是基本类型(Integer,...)
  • type level-recursion

当某个值构造函数采用被定义为参数的类型时,将使用类型级递归。典型的例子是Peano风格的自然品:

data Nat where
   O :: Nat
   S :: Nat -> Nat
     -- ^^^ recursive!

列表也很常见:

data List a where
   Nil :: List a
   Cons :: a -> List a -> List a
             -- ^^^^^^ recursive!

Maybe a,对等类型不是递归的。

请注意,每个ADT,无论递归与否,都可以通过对所有构造函数求和(Either)并将所有参数相乘来简化为带有sigle参数的单个构造函数。例如,Nat

同构
data Nat1 where
  O1 :: () -> Nat
  S1 :: Nat -> Nat

同构
data Nat2 where K2 :: (Either () Nat) -> Nat

列表成为

data List1 a where K1 :: (Either () (a, List a)) -> List a

上述步骤使用了类型的代数,这使得类型的和和产品遵循与高中代数相同的规则,而a -> b的行为类似于指数b^a

因此,我们可以用

形式编写任何ADT
-- pseudo code
data T where
   K :: F T -> T
type F k = .....

例如

type F_Nat k = Either () k      -- for T = Nat
type F_List_a k = Either () (a, k) -- for T = List a

(请注意,后一种类型函数F取决于a,但它现在并不重要。)

非递归类型不会使用k

type F_Maybe_a k = Either () a     -- for T = Maybe a

请注意,上面的构造函数K使类型T同构为F T(让我们忽略它引入的提升/额外底部)。基本上,我们有

Nat ~= F Nat = Either () Nat
List a ~= F (List a) = Either () (a, List a)
Maybe a ~= F (Maybe a) = Either () a

我们甚至可以通过从F

中抽象来进一步形式化
newtype Fix f = Fix { unFix :: f (Fix f) }

根据定义,Fix F现在将与F (Fix F)同构。我们可以让

type Nat = Fix F_Nat

(在Haskell中,我们需要一个围绕F_Nat的新类型包装器,为了清楚起见,我省略了它。)

最后,一般编码或catamorphism是:

cata :: (F k -> k) -> Fix F -> k

这假设F是一个仿函数。

对于Nat,我们得到

cata :: (Either () k -> k) -> Nat -> k
-- isomorphic to
cata :: (() -> k, k -> k) -> Nat -> k
-- isomorphic to
cata :: (k, k -> k) -> Nat -> k
-- isomorphic to
cata :: k -> (k -> k) -> Nat -> k

请注意“高中albegra”步骤,k^(1+k) = k^1 * k^kEither () k -> k ~= (() -> k, k -> k)

请注意,我们得到两个参数kk->k,分别对应OS。这不是巧合 - 我们总结了所有的构造函数。这意味着cata期望传递类型k的值,其中“扮演O”角色,然后传递角色k -> k的值S

更为非正式地,cata告诉我们,如果我们想要在k中映射自然,我们只需说明k内的“零”是什么以及如何取k中的“后继者”,然后可以映射每个Nat

对于我们得到的清单:

cata :: (Either () (a, k) -> k) -> List a -> k
-- isomorphic to
cata :: (() -> k, (a, k) -> k) -> List a -> k
-- isomorphic to
cata :: (k, a -> k -> k) -> List a -> k
-- isomorphic to
cata :: k -> (a -> k -> k) -> List a -> k

foldr

同样,这是cata告诉我们,如果我们说明如何将k中的“空列表”和“内部”ak置于其中k,我们可以在k中映射任何列表。

Maybe a是一样的:

cata :: (Either () a -> k) -> Maybe a -> k
-- isomorphic to
cata :: (() -> k, a -> k) -> Maybe a -> k
-- isomorphic to
cata :: (k, a -> k) -> Maybe a -> k
-- isomorphic to
cata :: k -> (a -> k) -> Maybe a -> k

如果我们可以在Nothing中映射k,并在Just中执行a映射k,我们就可以映射Maybe a中的任何k Bool

如果我们尝试对(a,b)和{{1}}采用相同的方法,我们会覆盖问题中发布的功能。

要查找更高级的理论主题:

  • (初始)类别理论中的F-代数
  • 类型理论中的消除器/递归器/归纳原理(这些也可以应用于GADT)