我试图用类型族来表示表达式,但我似乎无法弄清楚如何编写我想要的约束,而我开始觉得这是不可能的。这是我的代码:
class Evaluable c where
type Return c :: *
evaluate :: c -> Return c
data Negate n = Negate n
instance (Evaluable n, Return n ~ Int) => Evaluable (Negate n) where
type Return (Negate n) = Return n
evaluate (Negate n) = negate (evaluate n)
这一切都很好,但它并不能完全表达我想要的东西。在Negate
Evaluable
实例的约束中,我说Negate
中的表达式的返回类型必须是Int
(带Return n ~ Int
)所以我可以称之为否定,但这太限制了。返回类型实际上只需要是具有Num
函数的negate
类型类的实例。这样Double
s,Integer
或Num
的任何其他实例也可以被否定,而不仅仅是Int
。但我不能只写
Return n ~ Num
因为Num
是一个类型类,而Return n
是一个类型。我也不能把
Num (Return n)
因为Return n
是一个类型而不是类型变量。
我正在尝试用Haskell做什么?如果不是,它应该是,还是我误解了背后的一些理论?我觉得Java可以添加这样的约束。如果这个问题更清楚,请告诉我。
编辑:谢谢大家,回复正在帮助并且正在达到我的怀疑。如果没有UndecidableInstances,类型检查器似乎无法处理我想要做的事情,所以我的问题是,我想表达的是真正无法判断的吗?它是Haskell编译器,但它是否一般?也就是说甚至存在约束,这意味着“检查返回n是Num的实例”,这可以判断为更高级的类型检查器吗?
答案 0 :(得分:6)
实际上,你可以完全按照你提到的那样做:
{-# LANGUAGE TypeFamilies, FlexibleContexts, UndecidableInstances #-}
class Evaluable c where
type Return c :: *
evaluate :: c -> Return c
data Negate n = Negate n
instance (Evaluable n, Num (Return n)) => Evaluable (Negate n) where
type Return (Negate n) = Return n
evaluate (Negate n) = negate (evaluate n)
Return n
当然是一个类型,它可以像Int
一样可以是类的实例。你的困惑可能是关于约束的论证。答案是“任何正确的东西”。 Int
的种类为*
,Return n
的种类也是Num
。 * -> Constraint
有*
种类,因此类Num Int
的任何都可以成为其参数。写Num (a :: *)
作为约束完全合法(尽管是空洞的),就像{{1}}合法一样。
答案 1 :(得分:2)
为了补充Eric的答案,让我提出一个可能的选择:使用函数依赖而不是类型族:
class EvaluableFD r c | c -> r where
evaluate :: c -> r
data Negate n = Negate n
instance (EvaluableFD r n, Num r) => EvaluableFD r (Negate n) where
evaluate (Negate n) = negate (evaluate n)
这让我更容易谈论结果类型。例如,你可以写
foo :: EvaluableFD Int a => Negate a -> Int
foo x = evaluate x + 12
你也可以使用ConstraintKinds
部分地应用它(这就是为什么我把参数放在那个看起来很滑稽的顺序中):
type GivesInt = EvaluableFD Int
你也可以和你的班级一起做这件事,但这会更烦人:
type GivesInt x = (Evaluable x, Result x ~ Int)