应为类型,但类型为“ *->约束”

时间:2018-09-11 00:50:09

标签: haskell

我正在写一个用于加减数字的计算器。我在这里有两个抽象,一个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类型类,因为这是我现在正在学习的主题。我在做什么错了?

1 个答案:

答案 0 :(得分:6)

在您的代码中Operand不是类型,而是类型的 class 。不能有类型Operand的值,因为它不是数据类型。因此,不能将其用作Expr定义的一部分。

隐含符号* -> Constraint表示,如果将标识符Operand应用于类型(表示为*),则会给您Constraint。编译器说,它希望在该上下文中使用一种类型(例如IntStringMaybe 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