Haskell 如何为不明确的表达式选择类型

MvG*_*MvG 1 haskell types type-inference

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"
Run Code Online (Sandbox Code Playgroud)

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

  • Is my interpretation observed behavior correct, and there is an implicit type default here?
  • What is the name for this kind of preference?
  • Is there a way to disable such a choice of canonical type and enforce explicit type specifications for things that can be interpreted in multiple ways?

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)
Run Code Online (Sandbox Code Playgroud)

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.

Li-*_*Xia 7

如果存在v带有Num v约束的不明确类型变量,则它默认为Integeror Double,按该顺序尝试,以满足 上所有其他约束的为准v

Haskell 报告中解释了这些默认规则:https://www.haskell.org/onlinereport/haskell2010/haskellch4.html#x10-620004

GHC 手册还解释了 GHCi 中的其他默认规则(这意味着在 GHCi 中尝试不会让您准确了解编译程序时发生的情况): https: //downloads.haskell.org/ghc/latest/ docs/html/users_guide/ghci.html#type-defaulting-in-ghci

  • ...并且您可以使用“default”声明更改尝试的类型的顺序(但不能更改定义何时尝试的规则)。 (3认同)