Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Evaluation, let and where in Haskell

Tags:

haskell

I'm currently learning Haskell and trying to understand how typeclasses are evaluated, and how let and where work. This code runs fine:

{-# LANGUAGE FlexibleInstances #-}
class Expr a where
    literal :: Integer -> a

instance Expr Integer where
    literal = id

instance Expr [Integer] where
    literal i = [i]

coerceInteger :: Integer -> Integer
coerceInteger = id

main = print $ coerceInteger (literal 100) : literal 100 -- Prints [100,100]

but changing the main function to

main = print $ coerceInteger expr : expr
    where expr = literal 200

causes a compiler error:

Couldn't match expected type `[Integer]' with actual type `Integer'
In the second argument of `(:)', namely `expr'
In the second argument of `($)', namely `coerceInteger expr : expr'
In the expression: print $ coerceInteger expr : expr

I'm guessing this is because in the first main method the literal 100 is evaluated twice, whereas in the second example literal 200 is only evaluated once and so the compiler is forced to choose a type.

How can I factor out that code to avoid repeating myself, without causing this error? I tried using let expr = literal 300 in ... but ran into the same issue.

like image 250
Leo Avatar asked Jul 15 '14 00:07

Leo


1 Answers

The problem is that the literal 200 is interpreted differently in the two different contexts with your first example. Think of it as

((:) :: a -> [a] -> [a])
    ((coerceInteger :: Integer -> Integer) (literal 100 :: Expr a => a))
    (literal 100 :: Expr a => a)

Just based off the types, the compiler determines that the first literal 100 must have type Integer because it's being passed to coerceInteger, since it has to take a value of type Integer. This also sets the type of (:) to now be Integer -> [Integer] -> [Integer], implying that the last literal 100 has to have type [Integer].

In the second example, you're saying that both of them have the same value, and therefore the same type, which is impossible because the second must be a list for (:) to type check.

This actually occurs because of the dreaded Monomorphism restriction. You can fix this problem in two ways: One, turn off the monomorphism restriction with {-# LANGUAGE NoMonomorphismRestriction #-}, or you can provide an explicit type to expr that keeps it generalized:

main :: IO ()
main = print $ coerceInteger expr : expr
    where
        expr :: Expr a => a
        expr = literal 100

Either of these approaches work, and whatever you decide to do I would recommend always providing type signatures to help avoid these problems.


In fact, once you add the type signature you can even do things like

main :: IO ()
main = print $ coerceInteger expr : expr : expr : expr : expr : expr
    where
        expr :: Expr a => a
        expr = literal 100

without any problems, this will print out [100, 100, 100, 100, 100, 100]. The initial coerceInteger is needed though, because otherwise the compiler won't know what to instantiate it as and therefore won't have a Show instance for print.

like image 198
bheklilr Avatar answered Sep 21 '22 01:09

bheklilr