是否可以将Data.Type.Equality模式匹配提取到单独的函数中?

时间:2016-10-11 17:51:30

标签: haskell

对于某些测试代码,我编写了一个函数,允许我使用单态实现来实现多态函数。我使用Data.Type.Equality这样实现了它:

assertEq :: forall a b. (Show a, Typeable a, Typeable b) => a -> (a :~: b)
assertEq x = fromMaybe (error errorMessage) eqT
  where errorMessage =
            "expected value of type ‘" <> show (typeRep (Proxy :: Proxy b))
         <> "’, but got ‘" <> show x
         <> "’, which is of type ‘" <> show (typeRep (Proxy :: Proxy a)) <> "’"

使用它看起来像这样:

insertUser :: forall record m. (RelationalEntity record, Monad m) => record -> m (Either (Entity record) (Key record))
insertUser user = case (assertEq user :: record :~: User) of
  Refl -> return . Right $ UserId 1234

重要的是,类型签名适用于任何RelationalEntity,但它实际上需要User。通常情况下,这种功能可能是令人厌恶的,但它在测试代码中效果很好,因为引发的任何异常都会使测试失败,这正是我想要的。

当然,使用assertEq有点罗嗦。幸运的是,Haskell具有引用透明性,因此将case匹配打包成辅助函数应该非常容易,对吧?好吧,我试着这样做:

withAssertEq :: forall a b c. (Show a, Typeable a, Typeable b) => a -> (b -> c) -> c
withAssertEq x f = case (assertEq x :: a :~: b) of Refl -> f x

现在,我应该可以在withAssertEq内使用insertUser

insertUser :: forall record m. (RelationalEntity record, Monad m) => record -> m (Either (Entity record) (Key record))
insertUser x = withAssertEq x $ \(_ :: User) ->
  return . Right $ UserId 1234

不幸的是,这不是类似的。类型检查器从Refl中的withAssertEq模式匹配获得的信息不会传播到insertUser内的使用,因此类型检查器无法统一预期结果Key record,实际结果Key User

有没有办法编写一个可以传播这种类型信息的函数?或者我需要直接使用case表达式,以便告诉typechecker类型相等信息?

1 个答案:

答案 0 :(得分:4)

查看gcastWith

gcastWith :: a :~: b -> (a ~ b => r) -> r
gcastWith Refl x = x

该类型需要一些阅读。 gcastWith采用等式证明(a :~: b)和在假设a ~ ba ~ b => r)的情况下进行类型检查的值,并返回该值并假设已放弃(r })。

gcastWith机构中唯一正在进行的工作是Refl上的模式匹配。这会使a ~ b gcastWith的{​​{1}}相等的上下文成为上下文,GHC尽职尽责地追溯到x的类型。非常酷!

因此,您可以使用gcastWith的类型作为withAssertEq的模板:

withAssertEq :: forall a b r. (Typeable a, Typeable b) => Proxy a -> Proxy b -> (a ~ b => r) -> r
withAssertEq _ _ = gcastWith (fromJust eqT :: a :~: b)

Proxy用于启用呼叫网站,告诉GHC Typeable字典用于ab。没有它们,该类型无法进行模糊检查。

通常的警告适用,不要使用部分功能,等等等等。