我正在编写一个计算器来加减数字。我这里有两个抽象,一个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
类型类,因为这是我现在正在学习的主题。我做错了什么?
最佳答案
在您的代码中,Operand
不是类型,而是类型的类。不能有 Operand
类型的值,因为它不是数据类型。因此,您不能将其用作 Expr
定义的一部分。
神秘符号 * -> Constraint
表示标识符 Operand
,如果应用于类型(表示为 *
),将为您提供一个约束
。编译器表示它需要该上下文中的类型(例如 Int
或 String
或 Maybe Float
等),但您给出了它Operand
,具有类型* -> Constraint
。
从你的代码中,我猜你真正想要做的是构造 Expr
,使其可以包含任何类型的值,只要因为这些类型有一个 Operand
实例。这是一个正确的假设吗?
如果是这样,那么方法是将这些值包装在存在类型中:
data SomeOperand = forall a. Operand a => SomeOperand a
或者 GADT 表示法中的相同内容:
data SomeOperand where
SomeOperand :: forall a. Operand a => a -> SomeOperand
这些符号的字面意思是类型 SomeOperand
恰好包装了一个值 a
,该值必须具有 Operand
的实例。
现在您可以在 Expr
的定义中使用 SomeOperand
:
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
达到有用的目的:
showExpr :: Expr -> Expr
showExpr (Node op (SomeOperand a) (SomeOperand b)) = showOperand a ++ show op ++ showOperand b
showExpr (Value i) = show i
关于haskell - 需要一个类型,但有种类 ‘* -> Constraint’,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52267261/