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

时间:2016-06-25 02:47:01

标签: haskell dependent-type gadt existential-type combinators

我正在玩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

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

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

($$) :: CombBox -> CombBox -> Maybe CombBox
x $$ y = undefined -- ???

我希望($$)函数以某种方式在运行时“查看”CombBox存在的内部,如果可以使用:$组合两个组合子并得到一个好的 - 输入结果,我希望结果如此。否则,我想要Nothing。所以,例如,

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

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

2 个答案:

答案 0 :(得分:6)

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

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

首先,我要将所有Haskell 中的类型范围缩小为由单个基本类型和箭头组成的更简单的Universe。

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

(我从the Hasochism paper解除了这两种类型。):*:将两种类型配对,确保它们的索引相等。我们将与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被称为 singleton 。对于给定的t,只存在一个Typey t类型的值。因此,如果您有Typey t值,就会知道有关t的所有信息。

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

我们的存在量化组合器现在看起来像这样。 AComb打包Comb,其中包含其类型的运行时表示。这种技术使我们能够保证盒装的Comb输入良好;我们不能静态地说这种类型是什么。

type AComb = Sg Typey Comb

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

这里有命题相等,这是两个类型级别事物相等的GADT证明。如果您可以向GHC解释Refla实际上是相同的,那么您只能给出b的值。相反,如果您在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是否相等。我们继续对其关联的单个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)

最后,我们可以写$$。打字规则如下:

f : a -> b    y : a
------------------- App
      f y : b

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

($$) :: AComb -> AComb -> Maybe AComb
Sg (Arry a b) x $$ Sg t y = withTyEq a t $ Sg b (x :$ y)
_ $$ _ = Nothing

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

所以我建议将用户输入解析为无类型语法树,该语法树会接受格式错误的术语,然后单独生成输入信息。

data Expr = S | K | Expr :$ Expr
parse :: String -> Parser Expr
typeCheck :: Expr -> Maybe AComb

一个有趣的练习(在更依赖类型的语言中)是修改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

您还可以通过确保它不会改变您提供的术语(另一项练习)来使typeCheck更精确。

如需进一步阅读,请参阅The View from the Left,其中包含经过验证的lambda演算类型检查器。

答案 1 :(得分:2)

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

Parametricity不允许控制流依赖于类型,因此您需要一些类型的一阶表示。 Haskell有Typeable

deriving instance Typeable Comb

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

使用它我们可以定义

castApply1 :: (Typeable a, Typeable b, Typeable ab) => Comb ab -> Comb a -> Maybe (Comb b)
castApply1 f x = (:$ x) <$> cast f

然而

($$) :: CombBox -> CombBox -> Maybe CombBox
CombBox f $$ CombBox x = CombBox <$> castApply f x

引发

Could not deduce (Typeable a0) arising from a use of `CombBox' …
    from the context (Typeable a)
    or from (Typeable a1)
    The type variable `a0' is ambiguous

问题是在b的返回类型中指定了castApply1,但如果我们立即将CombBox应用于castApply f x,那么b就不会得到指定,因此仍然含糊不清。

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

castApply2 :: (Typeable a, Typeable b, Typeable ab) => Proxy b -> Comb ab -> Comb a -> Maybe (Comb b)
castApply2 p = castApply1

允许将结果包装在CombBox

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

我们最终可以定义一些没有提到恼人的b

的东西
data SomeTypeable = forall a. Typeable a => SomeTypeable (Proxy a)

castApply4 :: (Typeable a, Typeable ab) => SomeTypeable -> Comb ab -> Comb a -> Maybe CombBox
castApply4 (SomeTypeable p) = castApply3 p

现在有了

typeRepToSomeTypeable :: TypeRep -> SomeTypeable

我们可以定义

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

funResultTyData.Typeable中的函数,如果第一个参数的域与第二个参数匹配,则返回第一个参数的codomain。

但如何定义typeRepToSomeTypeable?它似乎并没有在某处实现。至少我还没有在Data.TypeableSingletons中找到它。