MvG
MvG

Reputation: 60858

How does Haskell pick a type for an ambiguous expression

If an expression can be typed in several ways, how does Haskell pick which one to use?

Motivating example

Take this example:

$ ghci
GHCi, version 8.8.4: https://www.haskell.org/ghc/  :? for help
Prelude> import Data.Ratio (Ratio)
Prelude Data.Ratio> f z s = show $ truncate $ z + (read s)
Prelude Data.Ratio> :type f
f :: (RealFrac a, Read a) => a -> String -> String
Prelude Data.Ratio> s = take 30 (cycle "12345")
Prelude Data.Ratio> s
"123451234512345123451234512345"
Prelude Data.Ratio> f 0 s
"123451234512345121227855101952"
Prelude Data.Ratio> f (0::Double) s
"123451234512345121227855101952"
Prelude Data.Ratio> f (0::Float) s
"123451235679745417161721511936"
Prelude Data.Ratio> f (0::Ratio Integer) (s ++ "%1")
"123451234512345123451234512345"
Prelude Data.Ratio> show $ truncate $ read s
"123451234512345121227855101952"

When I used 0 without any type, I got the same result as for (0::Double). So it seems to me that when I just invoke f 0 s, it uses a version of read that produces a Double, and a version of truncate that turns that Double into some integral type. I introduces the variable z so I could have some easy control over the type here. With that I could show that other interpretations are possible, e.g. using Float or exact ratios. So why Double? The last line, which omits the addition, shows that the behavior is independent of that zero constant.

I guess something tells Haskell that Double is a more canonical type than others, either in general or when used as a RealFrac, so if it can interpret an expression using Double as an intermediate result, but also some other types, then it will prefer the Double interpretation.

Core questions

Own research

I've read that https://en.wikibooks.org/wiki/Haskell/Type_basics_II#Polymorphic_guesswork writes

With no other restrictions, 5.12 will assume the default Fractional type of Double, so (-7) will become a Double as well.

That appears to confirm my assumption that Double is somehow blessed as the default type for some parent category of RealFrac. It still doesn't offer a name for that concept, nor a complete list of the rules around that.

Background

The situation I actually want to handle is more like this:

f :: Integer -> String -> Integer
f scale str = truncate $ (fromInteger scale) * (read str)

So I want a function that takes a string, reads it as a decimal fraction, multiplies it with a given number, then truncates the result back to an integer. I was very surprised to find that this compiles without me specifying the intermediate fractional type anywhere.

Upvotes: 1

Views: 66

Answers (1)

Li-yao Xia
Li-yao Xia

Reputation: 33389

If there is an ambiguous type variable v with a Num v constraint, it gets defaulted to Integer or Double, tried in that order, whichever satisfies all other constraints on v.

Those defaulting rules are explained in the Haskell Report: https://www.haskell.org/onlinereport/haskell2010/haskellch4.html#x10-620004

The GHC manual also explains additional defaulting rules in GHCi (this means trying things in GHCi will not give you an accurate picture of what is going on when you compile a program): https://downloads.haskell.org/ghc/latest/docs/html/users_guide/ghci.html#type-defaulting-in-ghci

Upvotes: 7

Related Questions