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.
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
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With