在运行时输入带有存在的杂耍

Sil*_*olo 4 haskell combinators existential-type gadt dependent-type

我正在玩Haskell中的存在感和GADT,我正在尝试为组合器定义DSL(例如SKI).我有GADT工作,以及一个工作正常的减少功能(并且与问题无关)

{-# LANGUAGE GADTs, ExistentialQuantification #-}

import Control.Applicative
import Data.Monoid
import Control.Monad

data Comb t where
    S :: Comb ((a -> b -> c) -> (a -> b) -> a -> c)
    K :: Comb (a -> b -> a)
    I :: Comb (a -> a)
    B :: Comb ((b -> c) -> (a -> b) -> a -> c)
    C :: Comb ((b -> a -> c) -> a -> b -> c)
    W :: Comb ((a -> a -> b) -> a -> b)
    (:$) :: Comb (a -> b) -> Comb a -> Comb b
Run Code Online (Sandbox Code Playgroud)

我现在要做的是定义一种在运行时从用户读取组合字符串的方法.显然,我需要一个存在类型来执行此操作,因为需要隐藏GADT的类型信息.

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

($$) :: CombBox -> CombBox -> Maybe CombBox
x $$ y = undefined -- ???
Run Code Online (Sandbox Code Playgroud)

我希望($$)函数能够以某种方式"查看" CombBox运行时的存在性,如果有可能将两个组合使用:$并得到一个良好类型的结果,我希望结果是这样的.否则,我想要Nothing.所以,例如,

CombBox S $$ CombBox K ==> Just (CombBox (S :$ K))
CombBox W $$ CombBox I ==> Nothing
Run Code Online (Sandbox Code Playgroud)

后者应该失败,因为W期望2-ary函数I需要一个参数.但是我想把这个检查放到运行时,我不确定在Haskell(+ GHC扩展)类型系统中是否可以这样做.

Ben*_*son 6

准备好了解依赖对单身人士!


我将稍微改写你的系统以简化它.

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

infixr 0 :->
data Type = Unit | Type :-> Type
Run Code Online (Sandbox Code Playgroud)

希望您应该能够看到如何使用更原始的类型扩展它.

我也将删除大部分比特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)
Run Code Online (Sandbox Code Playgroud)

现在回答你的问题.正如您正确推测的那样,当您正在阅读用户输入时,您无法静态地预测结果术语的类型,因此您需要对其进行存储量化.

data Ex f = forall a. Ex (f a)
Run Code Online (Sandbox Code Playgroud)

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

data (f :*: g) i = f i :*: g i
Run Code Online (Sandbox Code Playgroud)

(我把这些类型从Hasochism.):*:对两种类型的保证,他们的指数都是平等的.我们将它与Ex模拟依赖对西格玛类型一起使用:一对值,第二个组件的类型取决于第一个组件的值.我的想法是,f它将是一个GADT,告诉你它的索引,所以模式匹配f为你提供有关类型的信息g.

type Sg f g = Ex (f :*: g)
pattern Sg x y = Ex (x :*: y)
Run Code Online (Sandbox Code Playgroud)

现在聪明的部分:提出一个GADT,告诉你一个组合词的类型.

data Typey t where
    Unity :: Typey Unit
    Arry :: Typey a -> Typey b -> Typey (a :-> b)
Run Code Online (Sandbox Code Playgroud)

Typey被称为单身人士.对于给定的t,只存在一个类型的值Typey t.因此,如果您Typey t有价值,您就会知道所有需要了解的内容t.

单身人士价值最终是一个黑客.Typey不是Type; 它Type是重复类型级副本的价值级替身.在一个真正依赖类型的系统中,你不需要使用单独的胶水将值级别的东西粘贴到类型级别的东西,因为首先不存在区别.

我们的存在量化组合现在看起来像这样.AComb打包一个Comb具有其类型的运行时表示.这种技术使我们能够保证盒装Comb是好的; 我们不能静态地说那种类型是什么.

type AComb = Sg Typey Comb
Run Code Online (Sandbox Code Playgroud)

我们如何写($$),试图应用于AComb另一个AComb?我们需要对其关联的Typeys 进行模式匹配,以便了解是否可以将一个应用于另一个.特别是,我们需要一种方法来了解两种类型是否相等.

这就是命题平等,一个GADT证明两个类型级别的东西是平等的.Refl如果您可以向GHC解释a并且b实际上是相同的,那么您只能给出一个值.相反,如果你模式匹配,Refl那么GHC将添加a ~ b到输入上下文.

data a :~: b where
    Refl :: a :~: a
withEq :: a :~: b -> (a ~ b => r) -> r
withEq Refl x = x
Run Code Online (Sandbox Code Playgroud)

这是一个辅助函数,通过:->构造函数提升一对等式.

arrEq :: (a :~: c) -> (b :~: d) -> (a :-> b) :~: (c :-> d)
arrEq Refl Refl = Refl
Run Code Online (Sandbox Code Playgroud)

正如所承诺的,我们可以写下一个函数来检查两个Types是否相等.我们继续对它们的相关单例进行模式匹配Typey,如果我们发现类型不兼容则失败.如果相等测试成功,那么战利品就是类型相同的证明.

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)
Run Code Online (Sandbox Code Playgroud)

最后,我们可以写$$.输入规则如下:

f : a -> b    y : a
------------------- App
      f y : b
Run Code Online (Sandbox Code Playgroud)

也就是说,如果左手术语$$是函数类型,并且右手术语的类型与函数的域匹配,我们可以键入应用程序.因此,此规则的实现必须测试(使用withTyEq)相关类型是否匹配才能返回结果项.

($$) :: AComb -> AComb -> Maybe AComb
Sg (Arry a b) x $$ Sg t y = withTyEq a t $ Sg b (x :$ y)
_ $$ _ = Nothing
Run Code Online (Sandbox Code Playgroud)

生成Typey术语对应于类型检查的行为.换句话说,函数parse :: String -> AComb必须同时进行解析类型检查.在实际的编译器中,这两个阶段是分开的.

因此,我建议将用户输入解析为无类型语法树,该语法树允许格式错误的术语,然后单独生成键入信息.

data Expr = S | K | Expr :$ Expr
parse :: String -> Parser Expr
typeCheck :: Expr -> Maybe AComb
Run Code Online (Sandbox Code Playgroud)

一个有趣的练习(在更依赖类型的语言中)是修改typeCheck以返回更详细的解释为什么类型检查失败的原因,比如这个伪Agda:

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
Run Code Online (Sandbox Code Playgroud)

您也可以typeCheck通过确保它不会改变您给出的术语(另一项练习)来更精确.

有关进一步阅读,请参阅左侧视图,其中包含经验证的lambda演算类型检查器.

  • 您的编码存在问题:`Typey`是单态的.即你不能将`aid :: AComb`应用于自己.为此,您需要添加显式类型变量并将`tyEq`更改为`tyUnify`. (2认同)