haskell - 需要一个类型,但有种类 ‘* -> Constraint’

标签 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 类型类,因为这是我现在正在学习的主题。我做错了什么?

最佳答案

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

神秘符号 * -> Constraint 表示标识符 Operand,如果应用于类型(表示为 *),将为您提供一个约束。编译器表示它需要该上下文中的类型(例如 IntStringMaybe 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/

相关文章:

haskell - Haskell 中的管道字符串到 shell 命令

haskell - 对 GHC -Wall 风格的影响

exception - Haskell:列表理解到组合

haskell - 在 Haskell 中使用 Lens 修改值

haskell - 使用沙箱时如何让 cabal 忽略全局包数据库

haskell - GHCi中特殊情况下的函数定义

haskell - 在 Yesod 中显示动态生成的图像

haskell - 用于集中式 Haskell/Darcs 工具链的轻量级持续集成?

haskell - 模式匹配并非详尽无遗——为什么最后一个守卫中的 "otherwise"无法处理空列表?

haskell - 如何找到列表中最长的单词?