【问题标题】:How should types be used in Haskell type classes?Haskell 类型类中应该如何使用类型?
【发布时间】:2013-05-03 17:23:52
【问题描述】:

我是 Haskell 的新手,对类型类的工作方式有点困惑。这是我正在尝试做的事情的简化示例:

data ListOfInts = ListOfInts {value :: [Int]}
data ListOfDoubles = ListOfDoubles {value :: [Double]}

class Incrementable a where
    increment :: a -> a

instance Incrementable ListOfInts where
    increment ints = map (\x -> x + 1) ints

instance Incrementable ListOfDoubles where
    increment doubles = map (\x -> x + 1) doubles

(我意识到增加列表的每个元素可以非常简单地完成,但这只是一个更复杂问题的简化版本。)

编译器告诉我我有多个value 声明。如果我将ListOfIntsListOfDoubles 的定义更改如下:

type ListOfInts = [Int]
type ListOfDoubles = [Double]

然后编译器会说“'Incrementable ListOfInts' 的非法实例声明”(对于ListOfDoubles 也是如此。如果我使用新类型,例如newtype ListOfInts = ListOfInts [Int],那么编译器会告诉我“无法匹配预期的类型 'ListOfInts ' 实际类型为 '[b0]'" (ListOfDoubles 也是如此。

我对类型类的理解是它们促进了多态性,但我显然遗漏了一些东西。在上面的第一个示例中,编译器是否只看到类型参数a 引用了一个带有名为value 的字段的记录,并且看起来我正在尝试以多种方式为这种类型定义increment(而不是而不是看到两种不同的类型,一种具有类型为Ints 列表的字段,另一种类型为Doubles 列表的字段?其他尝试也是如此?

提前致谢。

【问题讨论】:

  • map 需要一个列表,你给它 ListOfInts

标签: haskell typeclass


【解决方案1】:

你确实看到了两个不同的问题,所以我会这样解决它们。

第一个是value 字段。 Haskell 记录以一种稍微特殊的方式工作:当您命名一个字段时,它会作为一个函数自动添加到当前范围。本质上,你可以想到

data ListOfInts = ListOfInts {value :: [Int]}

作为语法糖:

data ListOfInts = ListOfInts [Int]

value :: ListOfInt -> [Int]
value (ListOfInts v) = v

因此,拥有 两个 具有相同字段名称的记录就像拥有两个具有相同名称的不同函数一样——它们重叠。这就是为什么您的第一个错误告诉您您已多次声明 values

解决这个问题的方法是使用记录语法不使用来定义你的类型,就像我在上面所做的那样:

data ListOfInts = ListOfInts [Int]
data ListOfDoubles = ListOfDoubles [Double]

当您使用type 而不是data 时,您只是创建了一个类型同义词而不是一个新类型。使用

type ListOfInts = [Int]

表示ListOfInts[Int] 相同。由于各种原因,默认情况下您不能在类实例中使用类型同义词。这是有道理的——很容易犯错误,比如尝试为[Int] 编写一个实例以及为ListOfInts 编写一个实例,这会中断。

使用data 来包装像[Int][Double] 这样的单一类型与使用newtype 相同。但是,newtype 的优点是它根本不携带运行时开销。所以编写这些类型的最佳方式确实是使用newtype

newtype ListOfInts = ListOfInts [Int]
newtype ListOfDoubles = ListOfDoubles [Double]

需要注意的重要一点是,当您使用datanewtype 时,如果您想获取其内容,还必须“解包”该类型。您可以通过模式匹配来做到这一点:

instance Incrementable ListOfInts where
  increment (ListOfInts ls) = ListOfInts (map (\ x -> x + 1) ls)

这会解开ListOfInts,在其内容上映射一个函数并将其重新包装起来。

只要您以这种方式解开值,您的实例就应该可以工作。

在旁注中,您可以将map (\ x -> x + 1) 写为map (+ 1),使用称为“运算符部分”的东西。这意味着你隐式地创建了一个 lambda 来填充操作符缺少的任何参数。大多数人发现map (+ 1) 版本更易于阅读,因为不必要的噪音更少。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-03-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多