Question

I am testing the code for Write yourself a Scheme in 48 hours with GHC-7.8.2, which gives me an error about ambiguity that I don't recall encountering in previous versions of GHC. The excerpt is below, with the problem line marked:

data LispVal = Atom String
             | List [LispVal]
             | DottedList [LispVal] LispVal
             | Number Integer
             | String String
             | Bool Bool
unpackNum :: LispVal -> Integer
unpackNum (Number n) = n
unpackNum (String n) = let parsed = reads n in  --problem line 
                          if null parsed 
                            then 0
                            else fst $ parsed !! 0
unpackNum (List [n]) = unpackNum n
unpackNum _ = 0

, and the error says:

No instance for (Read a0) arising from a use of ¡®parsed¡¯
The type variable ¡®a0¡¯ is ambiguous
Note: there are several potential instances:
  instance Read a => Read (Control.Applicative.ZipList a)
    -- Defined in ¡®Control.Applicative¡¯
  instance Read () -- Defined in ¡®GHC.Read¡¯
  instance (Read a, Read b) => Read (a, b) -- Defined in ¡®GHC.Read¡¯
  ...plus 26 others

If I change the problem line to

unpackNum (String n) = let parsed = reads n ::[(Integer,String)] in 

then everything works fine.

I don't see why GHC failed to infer the type for ReadS from the signature of unpackNum. Can someone please explain what triggered the error?

(

-- EDIT --

Just some follow-up. From what I understand, the function type unpackNum :: LispVal -> Integer and the fact that fst $ parsed !! 0 is a return value of it tells that parsed has type [(Integer,b)], and from type ReadS a = String -> [(a,String)], the parsed should be [(a, String)]. Shouldn't these two types unify to [(Integer, String)] and fix the type for parsed?

Can someone please explain why NoMonomorphismRestriction would break the above reasoning?

-- EDIT2 --

From the answers, I can understand how NoMonomorphismRestriction could cause the issue here. Still, what I don't understand is the fact that how this "two type for the same expression" behavior consistent with laziness in Haskell. In the example parsed or reads n is the same expression in one block and should be evaluated only once. How can it have type a the first time of evaluation and Integer the second time?

)

Thanks,

Was it helpful?

Solution 2

The types should unify but don't in the presence of the NoMonomorphismRestriction (as noted in the comments by @FedorGogolev and @kosmikus). However, the following more idiomatic approach removes the need for the type annotation in any case:

data LispVal = Atom String
             | List [LispVal]
             | DottedList [LispVal] LispVal
             | Number Integer
             | String String
             | Bool Bool
unpackNum :: LispVal -> Integer
unpackNum (Number n) = n
unpackNum (String n) = case reads n of
                           [] -> 0
                           ((x, _):xs) -> x
unpackNum (List [n]) = unpackNum n
unpackNum _ = 0

The Difference Between Case and Null

It boils down to the fact that null is a function whereas case is straight syntax.

null :: [a] -> Bool

So with -XNoMonomorphismRestriction enabled, this is left as polymorphic as possible when the argument is supplied. The function doesn't restrict the argument type in any way, and so the compiler is unable to determine the return type of reads, causing the error. At the site of the function call, the type is ambiguous. In the case of the case statement, the compiler has the entire expression to work with, and so has the pattern matches to refine the return type of reads.

OTHER TIPS

This is triggered if NoMonomorphismRestriction is active; which, btw, is now the case by default in GHCi since 7.8 (see release notes, Section 1.5.2.3).

If the monomorphism restriction is disabled, the definition of parsed gets a polymorphic type, namely

parsed :: Read a => [(a, String)]

and then the first use in null parsed doesn't have sufficient contextual information to resolve what a is.

This happens to be one of the few cases where the monomorphism restriction actually does some good. Because with the polymorphic type, even if both use sites had sufficient type information to resolve the class constraint, the actual parsing would happen twice.

The best solution is still to use pattern matching as suggested in acomar's answer.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top