我是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
的多个声明。如果我按如下方式更改ListOfInts
和ListOfDoubles
的定义:
type ListOfInts = [Int]
type ListOfDoubles = [Double]
然后编译器说“'可递增ListOfInts'的非法实例声明”(对于ListOfDoubles
也是如此。如果我使用newtype,例如newtype ListOfInts = ListOfInts [Int]
,那么编译器告诉我“无法匹配预期类型'ListOfInts',实际类型为'[b0]'“(类似于ListOfDoubles
。
我对类型类的理解是它们促进了多态性,但我显然遗漏了一些东西。在上面的第一个示例中,编译器是否只看到类型参数a
引用了一个名为value
的字段的记录,并且看起来我正在尝试为此定义increment
以多种方式输入(而不是看到两种不同的类型,一种具有Int
列表类型的字段,另一种类型是Double
的列表?其他尝试也一样吗?
提前致谢。
答案 0 :(得分:17)
你真的看到了两个不同的问题,所以我会这样解决它们。
第一个是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]
需要注意的一点是,当您使用data
或newtype
时,如果要获取其内容,还必须“解包”该类型。您可以使用模式匹配来完成此操作:
instance Incrementable ListOfInts where
increment (ListOfInts ls) = ListOfInts (map (\ x -> x + 1) ls)
解包ListOfInts
,将函数映射到其内容并将其包装回来。
只要您以这种方式解开值,您的实例就可以正常工作。
在旁注中,您可以使用称为“操作符部分”的内容将map (\ x -> x + 1)
写为map (+ 1)
。所有这些意味着你隐式地创建一个lambda填充,无论运算符的哪个参数丢失。大多数人发现map (+ 1)
版本更容易阅读,因为噪音较少。