对于某些测试代码,我编写了一个函数,允许我使用单态实现来实现多态函数。我使用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类型相等信息?
答案 0 :(得分:4)
查看gcastWith
。
gcastWith :: a :~: b -> (a ~ b => r) -> r
gcastWith Refl x = x
该类型需要一些阅读。 gcastWith
采用等式证明(a :~: b
)和在假设a ~ b
(a ~ 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
字典用于a
和b
。没有它们,该类型无法进行模糊检查。
通常的警告适用,不要使用部分功能,等等等等。