Given the following code:
Prelude> let f x = if (x) then 55 else "foo"
Why does the compiler look for a Num [Char]
?
<interactive>:2:23:
No instance for (Num [Char]) arising from the literal `55'
In the expression: 55
In the expression: if (x) then 55 else "foo"
In an equation for `f': f x = if (x) then 55 else "foo"
However, in Scala, it will find the least-upper bound of 55
and "foo"
, which is Any
:
scala> def f(x: Boolean) = if (x) 55 else "foo"
f: (x: Boolean)Any
import scala.reflect.runtime.universe._
scala> lub( List[Type]( typeOf[Int], typeOf[String] ) )
res4: reflect.runtime.universe.Type = Any
What's the key difference between Haskell's and Scala's Type Inference?
You can add an instance for Num [Char]
in Haskell, if that's what you want:
{-# LANGUAGE FlexibleInstances #-}
import Data.Function (on)
import Data.Composition ((.:)) -- cabal install composition
helper :: (Integer -> Integer -> Integer) -> (String -> String -> String)
helper op = show .: on op read
cast :: (Integer -> Integer) -> (String -> String)
cast f = show . f . read
instance Num [Char] where
fromInteger = show
(+) = helper (+)
(-) = helper (-)
(*) = helper (*)
abs = cast abs
signum = cast signum
negate = cast negate
where this is just one possible implementation. This will make your example compile:
> let f x = if x then 55 else "foo"
> f True
"55"
> f False
"foo"
Haskell has polymorphic numeric literals, so 55 :: Num a => a
, and since both branches of an if
must return the same type, you are forcing a ~ [Char]
by having the else
branch return "foo"
. This leads to a somewhat confusing error message, but it can be a very powerful feature. It means that any numeric literal can act as the type you need it to be, and it's the same concept behind the OverloadedStrings
extension, allowing you to have polymorphic string literals instead of using pack
everywhere you need a Text
or ByteString
.
Scala uses subtyping and has a generic type for all values. It allows you to relax the type safety on a function and return literally Any
thing. Haskell does not have subtyping at all, so the only way to unify those types (similar to finding the LUB) is to use the fact that numeric literals are polymorphic under the Num
constraint, so in order to compile "foo"
has to implement Num
. Actually, if you enabled OverloadedStrings
f
will compile just fine with the type
f :: (Data.String.IsString a, Num a) => Bool -> a
There aren't any types by default that meet both constraints, but GHC will happily accept this as a valid function.
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