How should types be used in Haskell class classes?

I'm new to Haskell and a little confused about how class classes work. Here's a simplified example of what 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 understand that incrementing each item in a list can be done very simply, but it's just a simplified version of a more complex task.)

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

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

Then the compiler says "Invalid instance declaration for" Incrementable ListOfInts "(and similarly for ListOfDoubles . If I use newtype, for example, newtype ListOfInts = ListOfInts [Int] , then the compiler tells me" Could not match expected type 'ListOfInts' with actual type' [b0] '"(and similarly for ListOfDoubles .

My understanding of type classes is that they contribute to polymorphism, but I am clearly missing something. In the first example above, the compiler simply sees that a parameter of type a refers to a record with a field called value and that it appears. I'm trying to determine the increment for this type in several ways (rather than looking at two different types, one of which has a field whose type is an Int s list and the other type is a Double s list)? And similarly for other attempts?

Thanks in advance.

+6
source share
1 answer

You really see two separate problems, so I will consider them as such.

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

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

as syntactic sugar for:

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

Thus, the presence of two records with the same field name is similar to the presence of two different functions with the same name - they overlap. This is why your first error tells you that you declared values several times.

A way to fix this would be to define your types without using the syntax of the entry, 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, not a new type. Using

 type ListOfInts = [Int] 

means ListOfInts matches [Int] . For various reasons, you cannot use type synonyms in instances of a class by default. This makes sense - it would be very easy to make a mistake, for example, try to write an instance for [Int] , and also one for ListOfInts , which will break.

Using data to wrap one type of type like [Int] or [Double] same as using newtype . However, newtype has the advantage that it does not incur overhead at runtime. Therefore, the best way to write these types would really be with newtype :

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

It is important to note that when using data or newtype you also need to "expand" the type if you want to get its contents. You can do this using pattern matching:

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

This expands ListOfInts , displays the function above its contents, and wraps it.

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

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

+17
source

Source: https://habr.com/ru/post/944185/


All Articles