Haskell 如何使用更高秩(秩-N)类型多态性表示存在类型?

Haskell 如何使用更高秩(秩-N)类型多态性表示存在类型?,haskell,functional-programming,existential-type,higher-rank-types,Haskell,Functional Programming,Existential Type,Higher Rank Types,我们习惯于为多态函数提供通用的量化类型。存在量化的类型很少使用。我们如何使用通用类型量词来表达存在量化的类型?我在中找到了一个答案 假设我们有一个单参数类型t::*->*,并构造一个存在类型,它为一些a:存在a持有ta。t a。我们能用这种类型做什么?为了从中计算出一些东西,我们需要一个可以接受任意a的ta函数,这意味着所有a的类型的函数。t a->b。知道了这一点,我们可以简单地将一个存在类型编码为一个函数,该函数接受所有a的类型的函数。t a->b,向它们提供存在值并返回结果b: {-# L

我们习惯于为多态函数提供通用的量化类型。存在量化的类型很少使用。我们如何使用通用类型量词来表达存在量化的类型?

我在中找到了一个答案

假设我们有一个单参数类型
t::*->*
,并构造一个存在类型,它为一些
a
存在a持有
ta
。t a
。我们能用这种类型做什么?为了从中计算出一些东西,我们需要一个可以接受任意
a
ta
函数,这意味着所有a的
类型的函数。t a->b
。知道了这一点,我们可以简单地将一个存在类型编码为一个函数,该函数接受所有a的
类型的函数。t a->b
,向它们提供存在值并返回结果
b

{-# LANGUAGE RankNTypes #-}

newtype Exists t = Exists (forall b. (forall a. t a -> b) -> b)
创建存在价值现在很容易:

exists :: t a -> Exists t
exists x = Exists (\f -> f x)
如果我们想解压存在值,我们只需将其内容应用于产生结果的函数:

unexists :: (forall a. t a -> b) -> Exists t -> b
unexists f (Exists e) = e f
然而,纯粹的存在类型几乎没有用处。我们不能用我们一无所知的价值观去做任何合理的事情。通常我们需要一个带有类型类约束的存在类型。过程是一样的,我们只是为
a
添加了一个类型类约束。例如:

newtype ExistsShow t = ExistsShow (forall b. (forall a. Show a => t a -> b) -> b)

existsShow :: Show a => t a -> ExistsShow t
existsShow x = ExistsShow (\f -> f x)

unexistsShow :: (forall a. Show a => t a -> b) -> ExistsShow t -> b
unexistsShow f (ExistsShow e) = e f
data BST a where
    Nil  :: BST a
    Node :: Ord a => a -> BST a -> BST a -> BST a

注:在函数程序中使用存在量化通常被认为是一种错误。这表明我们还没有从面向对象的思维中解放出来。

事实证明,存在类型只是∑-类型(sigma类型)的一个特例。它们是什么

西格玛类型 正如∏-类型(pi类型)泛化了我们的普通函数类型,允许结果类型依赖于其参数的值,∑-类型泛化了对,允许第二个组件的类型依赖于第一个组件的值

在类似Haskell的组合语法中,∑-type如下所示:

data Sigma (a :: *) (b :: a -> *)
    = SigmaIntro
        { fst :: a
        , snd :: b fst
        }

-- special case is a non-dependent pair
type Pair a b = Sigma a (\_ -> b)
假设
*::*
(即不一致的
Set:Set
),我们可以定义
是否存在。a
作为:

Sigma * (\a -> a)
第一个组件是类型,第二个组件是该类型的值。一些例子:

foo, bar :: Sigma * (\a -> a)
foo = SigmaIntro Int  4
bar = SigmaIntro Char 'a'
showEx :: ExistsEncoded [] -> String
showEx (ExistsEncoded f) = f show

someList :: ExistsEncoded []
someList = ExistsEncoded $ \f -> f [1]

showEx someList == "[1]"
存在于a。一个
是相当无用的-我们不知道里面是什么类型,所以唯一可以使用它的操作是类型无关的函数,比如
id
const
。让我们将其扩展到
a。F a
甚至存在a。显示a=>F a。给定
F::*->*
,第一种情况是:

Sigma * F   -- or Sigma * (\a -> F a)
第二个有点棘手。我们不能只拿一个
显示一个
类型的类实例,然后把它放在里面的某个地方。但是,如果给我们一个
显示
字典(类型为
显示字典a
),我们可以将其与实际值打包:

Sigma * (\a -> (ShowDictionary a, F a))
-- inside is a pair of "F a" and "Show a" dictionary
这有点不方便使用,并且假设我们有一个
Show
字典,但它可以工作。打包字典实际上是GHC在编译存在类型时所做的事情,因此我们可以定义一个快捷方式使其更方便,但这是另一回事。我们很快就会了解到,编码实际上并没有遇到这个问题


题外话:由于约束类型的存在,可以将类型类具体化为具体的数据类型。首先,我们需要一些语言杂注和一个导入:

{-# LANGUAGE ConstraintKinds, GADTs, KindSignatures  #-}
import GHC.Exts -- for Constraint
GADT已经为我们提供了打包类型类和构造函数的选项,例如:

newtype ExistsShow t = ExistsShow (forall b. (forall a. Show a => t a -> b) -> b)

existsShow :: Show a => t a -> ExistsShow t
existsShow x = ExistsShow (\f -> f x)

unexistsShow :: (forall a. Show a => t a -> b) -> ExistsShow t -> b
unexistsShow f (ExistsShow e) = e f
data BST a where
    Nil  :: BST a
    Node :: Ord a => a -> BST a -> BST a -> BST a
然而,我们可以更进一步:

data Dict :: Constraint -> * where
    D :: ctx => Dict ctx
它的工作原理与上面的
BST
示例非常相似:通过
D::Dict ctx
上的模式匹配,我们可以访问整个上下文
ctx

show' :: Dict (Show a) -> a -> String
show' D = show

(.+) :: Dict (Num a) -> a -> a -> a
(.+) D = (+)

对于存在类型,我们也得到了相当自然的泛化,这些存在类型量化了更多的类型变量,例如
exists a b。F a b

Sigma * (\a -> Sigma * (\b -> F a b))
-- or we could use Sigma just once
Sigma (*, *) (\(a, b) -> F a b)
-- though this looks a bit strange
编码 现在,问题是:我们能用∏型编码∑型吗?如果是,那么存在类型编码只是一个特例。在所有荣耀中,我向您展示了实际的编码:

newtype SigmaEncoded (a :: *) (b :: a -> *)
    = SigmaEncoded (forall r. ((x :: a) -> b x -> r) -> r)
有一些有趣的相似之处。由于依赖对表示存在量化,从经典逻辑我们知道:

(∃x)R(x) ⇔ ¬(∀x)¬R(x) ⇔ (∀x)(R(x) → ⊥) → ⊥
forall r。r
几乎是
,因此通过一点重写,我们得到:

(∀x)(R(x) → r) → r
最后,将通用量化表示为从属函数:

forall r. ((x :: a) -> R x -> r) -> r

也让我们来看看教堂编码对的类型。我们得到了一个非常相似的外观类型:

Pair a b  ~  forall r. (a -> b -> r) -> r
我们只需要表示一个事实,
b
可能依赖于
a
的值,这可以通过使用依赖函数来实现。再一次,我们得到了相同的类型

相应的编码/解码功能包括:

encode :: Sigma a b -> SigmaEncoded a b
encode (SigmaIntro a b) = SigmaEncoded (\f -> f a b)

decode :: SigmaEncoded a b -> Sigma a b
decode (SigmaEncoded f) = f SigmaIntro
-- recall that SigmaIntro is a constructor
这个特殊的事例实际上简化了事情,使它在Haskell变得可表达,让我们来看一看:

newtype ExistsEncoded (F :: * -> *)
    = ExistsEncoded (forall r. ((x :: *) -> (ShowDictionary x, F x) -> r) -> r)
    -- simplify a bit
    = ExistsEncoded (forall r. (forall x. (ShowDictionary x, F x) -> r) -> r)
    -- curry (ShowDictionary x, F x) -> r
    = ExistsEncoded (forall r. (forall x. ShowDictionary x -> F x -> r) -> r)
    -- and use the actual type class
    = ExistsEncoded (forall r. (forall x. Show x => F x -> r) -> r)
请注意,我们可以将
f::(x::*)->x->x
视为
f::for all x。x->x
。也就是说,具有额外
*
参数的函数表现为多态函数

还有一些例子:

foo, bar :: Sigma * (\a -> a)
foo = SigmaIntro Int  4
bar = SigmaIntro Char 'a'
showEx :: ExistsEncoded [] -> String
showEx (ExistsEncoded f) = f show

someList :: ExistsEncoded []
someList = ExistsEncoded $ \f -> f [1]

showEx someList == "[1]"
请注意,
someList
实际上是通过
encode
构建的,但是我们删除了
a
参数。这是因为Haskell将推断出所有x的
部分中的
x
实际上是什么意思

从∏到∑? 奇怪的是(虽然超出了这个问题的范围),您可以通过∑-类型和正则函数类型对∏-类型进行编码:

newtype PiEncoded (a :: *) (b :: a -> *)
    = PiEncoded (forall r. Sigma a (\x -> b x -> r) -> r)
-- \x -> is lambda introduction, b x -> r is a function type
-- a bit confusing, I know

encode :: ((x :: a) -> b x) -> PiEncoded a b
encode f = PiEncoded $ \sigma -> case sigma of
    SigmaIntro a bToR -> bToR (f a)

decode :: PiEncoded a b -> (x :: a) -> b x
decode (PiEncoded f) x = f (SigmaIntro x (\b -> b))