Chris
Chris

Reputation: 941

How should types be used in Haskell type classes?

I'm new to Haskell, and a little confused about how type classes work. Here's a simplified example of something I'm trying to do:

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

(I realize that incrementing each element of a list can be done very simply, but this is just a simplified version of a more complex problem.)

The compiler tells me that I have multiple declarations of value. If I change the definitions of ListOfInts and ListOfDoubles as follows:

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

Then the compiler says "Illegal instance declaration for 'Incrementable ListOfInts'" (and similarly for ListOfDoubles. If I use newtype, e.g., newtype ListOfInts = ListOfInts [Int], then the compiler tells me "Couldn't match expected type 'ListOfInts' with actual type '[b0]'" (and similarly for ListOfDoubles.

My understanding of type classes is that they facilitate polymorphism, but I'm clearly missing something. In the first example above, does the compiler just see that the type parameter a refers to a record with a field called value and that it appears I'm trying to define increment for this type in multiple ways (rather than seeing two different types, one which has a field whose type of a list of Ints, and the other whose type is a list of Doubles)? And similarly for the other attempts?

Thanks in advance.

Upvotes: 6

Views: 288

Answers (1)

Tikhon Jelvis
Tikhon Jelvis

Reputation: 68172

You're really seeing two separate problems, so I'll address them as such.

The first one is with the value field. Haskell records work in a slightly peculiar way: when you name a field, it is automatically added to the current scope as a function. Essentially, you can think of

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

as syntax sugar for:

data ListOfInts = ListOfInts [Int]

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

So having two records with the same field name is just like having two different functions with the same name--they overlap. This is why your first error tells you that you've declared values multiple times.

The way to fix this would be to define your types without using the record syntax, as I did above:

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

When you used type instead of data, you simply created a type synonym rather than a new type. Using

type ListOfInts = [Int]

means that ListOfInts is the same as just [Int]. For various reasons, you can't use type synonyms in class instances by default. This makes sense--it would be very easy to make a mistake like trying to write an instance for [Int] as well as one for ListOfInts, which would break.

Using data to wrap a single type like [Int] or [Double] is the same as using newtype. However, newtype has the advantage that it carries no runtime overhead at all. So the best way to write these types would indeed be with newtype:

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

An important thing to note is that when you use data or newtype, you also have to "unwrap" the type if you want to get at its content. You can do this with pattern matching:

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

This unwraps the ListOfInts, maps a function over its contents and wraps it back up.

As long as you unwrap the value this way, your instances should work.

On a side note, you can write map (\ x -> x + 1) as map (+ 1), using something that is called an "operator section". All this means is that you implicitly create a lambda filling in whichever argument of the operator is missing. Most people find the map (+ 1) version easier to read because there is less unnecessary noise.

Upvotes: 17

Related Questions