我有一个术语类型,其本质是:
data Term a = Term a Bool
deriving Show
这Bool
是一种辅助功能,只是偶尔需要。如果不需要,我希望用户可以跳过它。这意味着以下内容应该有效:
main :: IO ()
main = print v
where v = "a"
=: Term "b" True
=: []
在哪里:
class Join a b where
(=:) :: a -> [b] -> [b]
infixr 1 =:
instance Join a (Term a) where
x =: y = Term x False : y
instance Join (Term a) (Term a) where
x =: y = x : y
唉,GHC 抱怨道:
a.hs:15:8-12: error: [GHC-39999]
• Ambiguous type variable ‘b0’ arising from a use of ‘print’
prevents the constraint ‘(Show b0)’ from being solved.
Relevant bindings include v :: [b0] (bound at a.hs:17:8)
但如果我main
这样写:
main :: IO ()
main = print v
where v :: [Term String]
v = "a"
=: Term "b" True
=: []
那么一切都好了。(请注意,唯一的区别是我给出了v
类型签名。)
我很好奇是否存在一种现代 GHC 技巧(类型系列、Injective-Type-families、Closed-type-families、GADT 等,或者一些神奇的标志),可以使类型签名变得不必要,并让 GHC 无需任何帮助即可推断出类型本身。
以下是完整程序:
data Term a = Term a Bool
deriving Show
class Join a b where
(=:) :: a -> [b] -> [b]
infixr 1 =:
instance Join a (Term a) where
x =: y = Term x False : y
instance Join (Term a) (Term a) where
x =: y = x : y
main :: IO ()
main = print v
where v :: [Term String] -- Want to avoid this signature and let GHC deduce it.
v = "a"
=: Term "b" True
=: []
问题是,我是否可以应用一些 GHC 功能或技巧,这样v
就不需要类型签名了。我也很乐意更改Join
类或添加新参数/依赖项等,只要的局部定义v
可以按原样编写而无需类型签名。
如果有充分的正当理由说明我所要求的是不合理的,我也希望得到一个解释,说明为什么这是不可能的。在我看来,GHC 有足够的类型信息来推断类型本身,但我怀疑某种“开放世界”假设正在发挥作用,并且所有“封闭类型系列”之类的技巧都不会奏效;如果确实如此。要清楚的是,我很高兴该类Join
永远不会在任何其他类型上实例化。
到目前为止,最简单的做法就是在包含字段时使用
(:)
而不是。然后你甚至不需要任何类,只需编写:(=:)
Bool
您可能需要 一个固定性声明
=:
。我认识到这不是一个完整的答案,因为现在有两个不同的函数名称,所以它可能不适用于问题的非最小化版本,但无论如何请考虑它......
这是一种方法。您可以使用封闭类型系列,根据左侧类型选择正确的右侧类型:
并使用这个系列将运算符定义
=:
为普通的多态函数:现在,您的
main
编译可以通过,因为每个调用的左侧都=:
强制使用明确的右侧类型:当然,
(=:) = undefined
这显然是一个次优的实现。为了=:
正确实现,您需要使用约束来分派到正确的定义:使用适当的类和实例定义:
整个程序似乎按预期工作: