Haskell 在运行时使用存在主义变换类型

Haskell 在运行时使用存在主义变换类型,haskell,dependent-type,gadt,existential-type,combinators,Haskell,Dependent Type,Gadt,Existential Type,Combinators,我正在Haskell中玩弄存在主义和GADTs,我正在尝试为combinators(比如SKI)定义一个DSL。我有一个GADT工作,还有一个还原函数,它工作得很好(实际上与问题无关) 我现在要做的是定义一种在运行时从用户处读入组合符字符串的方法。显然,我需要一个存在类型来实现这一点,因为GADT的类型信息需要隐藏 data CombBox = forall a. CombBox { unCombBox :: Comb a } ($$) :: CombBox -> CombBox -&g

我正在Haskell中玩弄存在主义和GADTs,我正在尝试为combinators(比如SKI)定义一个DSL。我有一个GADT工作,还有一个还原函数,它工作得很好(实际上与问题无关)

我现在要做的是定义一种在运行时从用户处读入组合符字符串的方法。显然,我需要一个存在类型来实现这一点,因为GADT的类型信息需要隐藏

data CombBox = forall a. CombBox { unCombBox :: Comb a }

($$) :: CombBox -> CombBox -> Maybe CombBox
x $$ y = undefined -- ???
我希望
($$)
函数在运行时以某种方式“查看”组合框中的存在,如果可以使用
:$
组合这两个组合符并得到一个类型良好的结果,我希望结果是这样的。否则,我什么都不想要。那么比如说,

CombBox S $$ CombBox K ==> Just (CombBox (S :$ K))
CombBox W $$ CombBox I ==> Nothing

后者应该失败,因为
W
需要一个2元函数,其中
I
接受一个参数。但是我想把这个检查放在运行时进行,我不确定在Haskell(+GHC extensions)类型的系统中是否可能出现这种情况。

这不是一个正确的答案,但可能会有所帮助

参数不允许控制流依赖于类型,所以您需要一些类型的一阶表示。哈斯克尔:

使用它我们可以定义

castApply1 :: (Typeable a, Typeable b, Typeable ab) => Comb ab -> Comb a -> Maybe (Comb b)
castApply1 f x = (:$ x) <$> cast f
castApply :: (Typeable a, Typeable ab) => TypeRep -> Comb ab -> Comb a -> Maybe CombBox
castApply t = castApply4 (typeRepToSomeTypeable t)

($$) :: CombBox -> CombBox -> Maybe CombBox
CombBox f $$ CombBox x = funResultTy (typeRep f) (typeRep x) >>= \t -> castApply t f x
问题是
b
是在
castappy1
的返回类型中指定的,但是如果我们立即将
CombBox
应用到
castappy f x
,那么
b
就不会被指定,因此仍然是不明确的

我们可以通过提供
代理b
作为参数来指定
b

castApply2 :: (Typeable a, Typeable b, Typeable ab) => Proxy b -> Comb ab -> Comb a -> Maybe (Comb b)
castApply2 p = castApply1
它允许将结果包装在
组合框中

castApply3 :: (Typeable a, Typeable b, Typeable ab) => Proxy b -> Comb ab -> Comb a -> Maybe CombBox
castApply3 p f x = CombBox <$> castApply2 p f x
现在有

typeRepToSomeTypeable :: TypeRep -> SomeTypeable
我们可以定义

castApply1 :: (Typeable a, Typeable b, Typeable ab) => Comb ab -> Comb a -> Maybe (Comb b)
castApply1 f x = (:$ x) <$> cast f
castApply :: (Typeable a, Typeable ab) => TypeRep -> Comb ab -> Comb a -> Maybe CombBox
castApply t = castApply4 (typeRepToSomeTypeable t)

($$) :: CombBox -> CombBox -> Maybe CombBox
CombBox f $$ CombBox x = funResultTy (typeRep f) (typeRep x) >>= \t -> castApply t f x
funResultTy
Data.Typeable
中的一个函数,如果第一个参数的域与第二个参数匹配,它将返回第一个参数的密码域

但是如何定义
类型reptoSometypeable
?它似乎不是在什么地方实现的。至少我在
Data.Typeable
和中都没有找到它。

准备好学习依赖对和单例吧


我要重写一下你的系统来简化它

首先,我将把你的类型宇宙从Haskell缩小到一个更简单的宇宙,由一个原始类型和箭头组成

infixr 0 :->
data Type = Unit | Type :-> Type
希望您能够看到如何用更多的基本类型来扩展它

我还将从
Comb
中删除大部分位,因为它们都可以彼此表示

data Comb a where
    S :: Comb ((a :-> b :-> c) :-> (a :-> b) :-> a :-> c)
    K :: Comb (a :-> b :-> a)
    (:$) :: Comb (a :-> b) -> Comb a -> Comb b

i = S :$ K :$ i
b = (S :$ (K :$ S)) :$ K
c = S :$ (S :$ (K :$ (S :$ (K :$ S) :$ K)) :$ S) :$ (K :$ K)
w = S :$ S :$ (S :$ K)

现在谈谈你的问题。正如您正确推测的,当您阅读用户输入时,您无法静态地预测结果项的类型,因此您需要对其进行存在性量化

data Ex f = forall a. Ex (f a)
问题是:如何恢复类型信息以便能够操纵术语?我们可以将
Comb
与另一个值配对,您可以在运行时对其进行模式匹配,以了解
Comb
的类型。这里有一个用于配对的组合器

data (f :*: g) i = f i :*: g i
(我从中提取了这两种类型。)
:*:
将两种类型配对,确保它们的索引相等。我们将把它与
Ex
一起使用来模拟一个依赖对或sigma类型:一对值,第二个分量的类型取决于第一个分量的值。其思想是,
f
将是一个GADT,它告诉您有关其索引的一些信息,因此,
f
上的模式匹配为您提供有关
g
类型的信息

type Sg f g = Ex (f :*: g)
pattern Sg x y = Ex (x :*: y)
现在是聪明的部分:想出一个GADT,告诉你一个组合词的类型

data Typey t where
    Unity :: Typey Unit
    Arry :: Typey a -> Typey b -> Typey (a :-> b)
Typey
称为单例。对于给定的
t
,类型
Typey t
正好存在一个值。因此,如果你有一个
Typey t
值,你就知道关于
t
的一切

单例值最终是一种攻击<代码>类型Y不是
类型
;它是
类型
的复制类型级别副本的值级别替代。在一个真正的依赖类型系统中,你不需要使用单例胶水将值级别的东西粘贴到类型级别的东西上,因为这种区别一开始就不存在

我们的存在量化组合现在看起来是这样的
AComb
使用其类型的运行时表示形式打包一个
Comb
。这项技术使我们能够保证盒装的
类型正确;我们不能静态地说那是什么类型

type AComb = Sg Typey Comb

我们如何编写
($$)
,它试图将一个
AComb
应用到另一个
AComb
?我们需要对它们相关的
Typey
s进行模式匹配,以了解是否可以将一个应用于另一个。特别是,我们需要一种方法来知道两种类型是否相等

tyEq :: Typey t -> Typey u -> Maybe (t :~: u)
tyEq Unity Unity = Just Refl
tyEq (Arry a b) (Arry c d) = liftA2 arrEq (tyEq a c) (tyEq b d)
tyEq _ _ = Nothing

withTyEq :: Typey t -> Typey u -> (t ~ u => a) -> Maybe a
withTyEq t u x = fmap (\p -> withEq p x) (tyEq t u)
这里是命题等式,一个GADT证明,两个类型级别的事物是相等的。如果您可以向GHC解释
a
b
实际上是相同的,则只能给出
Refl
的值。相反,如果在
Refl
上进行模式匹配,则GHC将向键入上下文添加
a~b

data a :~: b where
    Refl :: a :~: a
withEq :: a :~: b -> (a ~ b => r) -> r
withEq Refl x = x
下面是一个帮助函数,用于通过
:->
构造函数提升一对等式

arrEq :: (a :~: c) -> (b :~: d) -> (a :-> b) :~: (c :-> d)
arrEq Refl Refl = Refl
正如所承诺的那样,我们可以写下一个函数来检查两个
Type
s是否相等。我们继续对它们相关的singleton
Typey
s进行模式匹配,如果我们发现这些类型不兼容,就会失败。如果相等性测试成功,则损坏是类型相等的证明

tyEq :: Typey t -> Typey u -> Maybe (t :~: u)
tyEq Unity Unity = Just Refl
tyEq (Arry a b) (Arry c d) = liftA2 arrEq (tyEq a c) (tyEq b d)
tyEq _ _ = Nothing

withTyEq :: Typey t -> Typey u -> (t ~ u => a) -> Maybe a
withTyEq t u x = fmap (\p -> withEq p x) (tyEq t u)
最后,我们可以编写
$$
。键入规则如下所示:

f : a -> b    y : a
------------------- App
      f y : b
也就是说,如果
$$
的左手项是函数类型,而右手项的类型与函数的域相匹配,则可以键入应用程序。因此,该规则的实现必须测试(使用
with tyeq
)相关
tyEq :: Typey t -> Typey u -> Maybe (t :~: u)
tyEq Unity Unity = Just Refl
tyEq (Arry a b) (Arry c d) = liftA2 arrEq (tyEq a c) (tyEq b d)
tyEq _ _ = Nothing

withTyEq :: Typey t -> Typey u -> (t ~ u => a) -> Maybe a
withTyEq t u x = fmap (\p -> withEq p x) (tyEq t u)
f : a -> b    y : a
------------------- App
      f y : b
($$) :: AComb -> AComb -> Maybe AComb
Sg (Arry a b) x $$ Sg t y = withTyEq a t $ Sg b (x :$ y)
_ $$ _ = Nothing
data Expr = S | K | Expr :$ Expr
parse :: String -> Parser Expr
typeCheck :: Expr -> Maybe AComb
data Void : Set where
Not : Set -> Set
Not a = a -> Void

data TypeError : Expr -> Set where
    notArr : Not (IsFunction f) -> TypeError (f :$ x)
    mismatch : Not (domain f :~: type x) -> TypeError (f :$ x)
    inFunc : TypeError f -> TypeError (f :$ x)
    inArg : TypeError x -> TypeError (f :$ x)

typeCheck : (e : Expr) -> Either (TypeError e) AComb