应该如何在Haskell类型类中使用类型?

Chr*_*ris 6 haskell typeclass

我是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
Run Code Online (Sandbox Code Playgroud)

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

编译器告诉我,我有多个声明value.如果我改变的定义ListOfIntsListOfDoubles如下:

type ListOfInts = [Int]
type ListOfDoubles = [Double]
Run Code Online (Sandbox Code Playgroud)

然后编译器说"'增量ListOfInts'的非法实例声明"(并且类似于ListOfDoubles.如果我使用newtype,例如newtype ListOfInts = ListOfInts [Int],那么编译器告诉我"无法将预期类型'ListOfInts'与实际类型'[b0]'匹配"(同样适用于ListOfDoubles.

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

提前致谢.

Tik*_*vis 17

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

第一个是与该value领域.Haskell记录的工作方式略有不同:当您命名字段时,它会自动作为函数添加到当前范围.从本质上讲,你可以想到

data ListOfInts = ListOfInts {value :: [Int]}
Run Code Online (Sandbox Code Playgroud)

作为语法糖:

data ListOfInts = ListOfInts [Int]

value :: ListOfInt -> [Int]
value (ListOfInts v) = v
Run Code Online (Sandbox Code Playgroud)

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

修复此问题的方法是在使用记录语法的情况下定义类型,如上所述:

data ListOfInts = ListOfInts [Int]
data ListOfDoubles = ListOfDoubles [Double]
Run Code Online (Sandbox Code Playgroud)

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

type ListOfInts = [Int]
Run Code Online (Sandbox Code Playgroud)

意思ListOfInts是和刚刚一样[Int].由于各种原因,默认情况下不能在类实例中使用类型同义词.这是有道理的-这将是很容易犯错误就像试图写一个实例[Int],以及一个ListOfInts,这将打破.

使用data包装一个单一类型的像[Int][Double]相同使用newtype.但是,newtype它的优点是根本不承载运行时开销.因此,编写这些类型的最佳方法确实是newtype:

newtype ListOfInts = ListOfInts [Int]
newtype ListOfDoubles = ListOfDoubles [Double]
Run Code Online (Sandbox Code Playgroud)

需要注意的一件重要事情是,当您使用data或者newtype,如果您想要获取其内容,您还必须"打开"该类型.你可以用模式匹配来做到这一点:

instance Incrementable ListOfInts where
  increment (ListOfInts ls) = ListOfInts (map (\ x -> x + 1) ls)
Run Code Online (Sandbox Code Playgroud)

这解开ListOfInts,将函数映射到其内容并将其包装回来.

只要您以这种方式展开值,您的实例就可以正常工作.

在一个侧面说明,你可以写map (\ x -> x + 1)map (+ 1),用的东西,被称为"运算符节".所有这些意味着你隐式地创建一个lambda填充,无论运算符的哪个参数丢失.大多数人发现该map (+ 1)版本更容易阅读,因为不必要的噪音.