我正在玩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扩展)类型系统中是否可以这样做。
答案 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解释Refl
和a
实际上是相同的,那么您只能给出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
funResultTy
是Data.Typeable
中的函数,如果第一个参数的域与第二个参数匹配,则返回第一个参数的codomain。
但如何定义typeRepToSomeTypeable
?它似乎并没有在某处实现。至少我还没有在Data.Typeable
和Singletons中找到它。