我最近了解了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
现在我注意到bool
和either
等功能的相似性,基本上就像GADT
定义一样:
Type -> (the letter of step 2)
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
这种模式是什么?在我看来,这些函数减轻了模式匹配的需要(因为它们为每种情况提供了一般方法),因此对该类型进行操作的每个函数都可以根据此函数进行定义。
答案 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
(在Haskell中很少使用,但理论上很好)Integer
,...)当某个值构造函数采用被定义为参数的类型时,将使用类型级递归。典型的例子是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^k
,Either () k -> k ~= (() -> k, k -> k)
。
请注意,我们得到两个参数k
和k->k
,分别对应O
和S
。这不是巧合 - 我们总结了所有的构造函数。这意味着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
中的“空列表”和“内部”a
和k
置于其中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}}采用相同的方法,我们会覆盖问题中发布的功能。
要查找更高级的理论主题: