首页 文章

行类型级别列表的多态相等

提问于
浏览
6

TL;DR: 我想使用 type family RIso (a :: [*]) (b :: [*]) :: Bool 实现行多态的统一,但是已经卡住了 .


背景

我正在为一种具有效果系统的语言编写一个编译器,该系统使用行多态,如Extensible Records with Scoped Labels,表示附加到函数类型的效果集 . 也就是说,在这种语言中,函数箭头的类型是:

(->) :: * -> * -> Effect -> *

其中 Effect 是一系列效果标签,例如 IOUnsafe - 标签的“缺点”,具有另一个效果行,效果类型变量或“纯”效果(空行) . 一个简单的例子是 map 的类型:

<A, B, +E> (List<A>, (A -> B +E) -> List<B> +E)

在假设的Haskell语法中:

forall (a :: *) (b :: *) (e :: Effect).
  (a -> Eff e b) -> [a] -> Eff e [b]

这意味着 map 采用 A 类型的 List ,以及 AB 的函数和一组效果 +E ,并返回 ListB ,也需要效果 +E . 纯函数的效果是完全多态的,默认为空行;所以 map 是纯粹的,如果它的功能论证是纯粹的;否则它需要与其参数相同的效果 .

我正在尝试在Haskell的类型系统中表示这种语言的类型系统,这样我就可以依靠Haskell的类型检查器以this blog post的方式编写经过验证的类型检查器 .

但是,当我开始统一函数类型时,我需要通过常规统一(相等)来统一输入和输出,但我需要通过行统一来统一效果,其定义如下:

s ≃ { l | s′ } : θ₁
tail(r) ∉ dom(θ₁)    -- †
θ₁(r) ~ θ₁(s′) : θ₂
---------------------------------------- [uni-row]
{ l | r } ~ s : θ₂ ∘ θ₁

这表示“如果 s 可以被替换为 { l | s′ } ,取代 θ₁rs′ 统一替换 θ₂ ,那么 { l | r }s 统一替换 θ₂ ∘ θ₁ ,再加上标有匕首(†)的边条件我将立即解释 .

(≃)关系表示根据以下三个规则,两行是同构的:

-------------------------- [row-head]
{ l | r } ≃ { l | r } : []

l ≠ l′
r ≃ { l | r′ } : θ
-------------------------------- [row-swap]
{ l′ | r } ≃ { l | l′ | r′ } : θ

fresh(β)
----------------------------- [row-var]
α ≃ { l | β } : [α ↦ {l | β}]

在我目前未经验证的类型检查器中,这两个函数在Haskell中看起来像这样:

unify typeEnv0 (Row l r) s = do

  -- Attempt to rewrite.
  maybeSubstitution <- isomorphicRows typeEnv0 l s (rowLast r)
  case maybeSubstitution of

    -- If rows are isomorphic:
    Just (s', substitution, typeEnv1) -> case substitution of

      -- Apply substitution, if any, and unify the tails.
      Just (x, t) -> let
        typeEnv2 = typeEnv1
          { typeEnvVars = Map.insert x t $ typeEnvVars typeEnv1 }
        in unify typeEnv2 r s'
      Nothing -> unify typeEnv1 r s'

    -- Non-isomorphic rows do not unify.
    Nothing -> reportTypeMismatch (Row l r) s

-- Gets the last element in a row.
rowLast :: Type -> Type
rowLast (Row _ r) = rowLast r
rowLast t = t

isomorphicRows 实现了论文中描述的规则:

当一行{l | r}与某些行统一,我们首先尝试以{l |形式重写s s'} [...]如果这成功了,统一就会通过统一......行尾 .

因此,给定 lrs ,以及统一规则, isomorphicRows 用于断言 s 可以在某些替换下被重写为 { l | r } ,失败( Nothing )或返回( Just )重写行的尾部,替换( Maybe 因为它总是空的或单例),以及更新的类型环境 . 我们实际上并没有传递 r 因为我们只需要它的最后一个元素 rt .

isomorphicRows
  :: TypeEnv    -- Type environment
  -> Type       -- Effect label l
  -> Type       -- Effect row s
  -> Type       -- Effect row rt
  -> Typecheck
    (Maybe
      ( Type
      , Maybe (TypeId, Type)
      , TypeEnv
      ))

[row-head] 规则:已经以给定标签开头的行由身份替换轻易地重写 .

isomorphicRows typeEnv0 l (Row l' r') _rt
  | l == l'
  = pure $ Just (r', Nothing, typeEnv0)

[row-swap] 规则:在其中包含标签的行可以被重写以将该标签放在头部 .

isomorphicRows typeEnv0 l (Row l' r') rt
  | l /= l' = do
  maybeSubstitution <- isomorphicRows typeEnv0 l r' rt
  pure $ case maybeSubstitution of
    Just (r'', substitution, typeEnv1) -> Just
      (Row l r'', substitution, typeEnv1)
    Nothing -> Nothing

[row-var] 规则:当与类型变量统一时,不存在标签,因此我们不能直接测试相等性,并且必须为行尾返回一个新变量 .

†这会强制执行侧面条件,以防止统一具有共同尾部但具有不同前缀的行,从而确保即使我们添加新类型变量也会终止 .

isomorphicRows typeEnv0 l r@(TypeVar a) rt
  | r /= rt = do
  (b, typeEnv1) <- freshTypeVar typeEnv0
  pure $ Just (b, Just (a, Row l b), typeEnv1)

在任何其他情况下,行不是同构的 .

isomorphicRows _ _ _ _ = Nothing

我试过的

我正在尝试将行统一与 isomorphicRows 转换为类型级别,使用在类型级别类型列表上运行的类型族:

type family RIso (ra :: [*]) (rb :: [*]) :: Bool where

  -- [row-head]
  RIso ((l) ': r) ((l) ': s) = RIso r s

  -- [row-swap]
  RIso ((l) ': (l') ': r) ((l') ': (l) ': r)
    = TNot (TEq l l')

  RIso r r = 'True

  RIso _ _ = 'False

type family TNot (a :: Bool) :: Bool where
  TNot 'False = 'True
  TNot 'True = 'False

type family TEq (a :: Bool) (b :: Bool) :: Bool where
  TEq a a = 'True
  TEq _ _ = 'False

这似乎适用于基本情况:

> :kind! RIso '[] '[]
'True

> :kind! RIso '[Int] '[Int]
'True

> :kind! RIso '[Int, Char] '[Char, Int]
'True

> :kind! RIso '[Int, Char] '[Int, String]
'False

> :kind! RIso '[Int, Char, String] '[Char, Int, String]
'True

但在更复杂的情况下失败:

> :kind! RIso '[Int, String, Char] '[Char, Int, String]
'False

我相信这是因为我可能错误地实施了 [row-swap] 规则( TNot 看起来很可疑),而且我错过了 [row-var] 规则 . 我坚持 [row-var] 的实现,因为我不知道如何将新的类型变量带入范围(或者这是否是正确的方法) . 我试过这个:

type family REq (ra :: [*]) (rb :: [*]) :: Bool where
  …
  -- [row-var]
  REq (l ': r) rt = TAnd (TNot (TEq r rt)) (TEq r (Row l b))
  …

type family TAnd (a :: Bool) (b :: Bool) :: Bool where
  TAnd 'True 'True = 'True
  TAnd _ _ = 'False

但是我发现免费的 b 会产生类型错误并不奇怪:

error: Not in scope: type variable ‘b’

问题

  • 是否可以使用 forall 引入一个新变量,或者我应该放弃尝试直接实现输入规则并采取不同的方法?

  • 或者,是否有一些现有的可扩展记录库专门使用行统一(而不是其他方法),所以我可以使用该库或从中学习技术?

请注意,这与简单的集合相等不同,因为可能存在重复的标签,并且它们是在我在本问题顶部链接的论文中描述的意义上的“范围” . 这有两个重要原因:首先,我打算将来扩展它,以便效果标签可以有类型参数,例如 ST s ;第二,我打算用这个机器来实现可扩展的记录和变体语言 .

1 回答

  • 1

    是否可以使用forall或其他东西引入新变量

    不幸的是不是这种方式存在不可靠性,在Haskell中没有好的故事 .

    通过类型族计算一旦检查变量就会卡住 . 您必须将客户语言的变量编码(行)作为具体类型,以便对它们执行任何操作 .

相关问题