我正在写一个用于加减数字的计算器。我在这里有两个抽象,一个Expr
,它被建模为一棵树,另一个类型类是Operand
,它包含了树的左右节点。操作数具有函数combine
,该函数将函数应用于左右节点:
module Data.Calculator where
class Operand a where
combine :: a -> a
data Operator = Plus | Minus
data Expr = Node Operator Operand Operand | Value Integer
instance Operand Expr where
combine (Node op left right) =
case op of
Plus -> (combine left) + (combine right)
Minus -> (combine left) - (combine right)
combine (Value a) = (Value a)
instance Num Expr where
(+) (Value left) (Value right) = Value (left + right)
(*) (Value left) (Value right) = Value (left * right)
abs (Value a) = Value (abs a)
fromInteger i = Value i
negate (Value a) = Value (negate a)
当我尝试编译它时,我得到了错误
calculator/src/Data/Calculator.hs:7:35: error:
• Expecting one more argument to ‘Operand’
Expected a type, but ‘Operand’ has kind ‘* -> Constraint’
• In the type ‘Operand’
In the definition of data constructor ‘Node’
In the data declaration for ‘Expr’
|
| data Expr = Node Operator Operand Operand | Value Integer
这是什么意思?我知道无需将Operand定义为类型类即可解决此问题,但是我想使用Operand
类型类,因为这是我现在正在学习的主题。我在做什么错了?
答案 0 :(得分:6)
在您的代码中Operand
不是类型,而是类型的 class 。不能有类型Operand
的值,因为它不是数据类型。因此,不能将其用作Expr
定义的一部分。
隐含符号* -> Constraint
表示,如果将标识符Operand
应用于类型(表示为*
),则会给您Constraint
。编译器说,它希望在该上下文中使用一种类型(例如Int
或String
或Maybe Float
等),但是您给了它Operand
,其类型为{ {1}}。
从您的代码中,我猜测您实际上打算做什么,以构造* -> Constraint
的方式,使其可以包含 any 类型的值,只要这些类型有一个Expr
的实例。这是正确的假设吗?
如果是这样,那么执行此操作的方法是将这些值包装为存在的类型:
Operand
与GADT表示法相同:
data SomeOperand = forall a. Operand a => SomeOperand a
这些表示法从字面上说类型data SomeOperand where
SomeOperand :: forall a. Operand a => a -> SomeOperand
仅包装一个值SomeOperand
,该值必须具有a
的实例。
现在您可以在Operand
的定义中使用SomeOperand
:
Expr
现在,在data Expr = Node Operator SomeOperand SomeOperand | Value Integer
上进行匹配时,您将获得一个具有Expr
实例的值,因此可以将Operand
应用于它:>
combine
请注意,除了将这些操作数转换为相同类型的其他值之外,您实际上无法做任何事情,这又使您无处可去。为了使它以任何方式有用,类f :: Expr -> Expr
f (Node op (SomeOperand a) (SomeOperand b)) = Expr op (combine a) (combine b)
f (Value i) = Value i
必须具有一些转换为类型本身以外的东西的方法,例如:
Operand
现在我可以使用class Operand a where
combine :: a -> a
showOperand :: a -> String
来达到有用的目的:
showOperand