Type inference in Haskell v. Scala

Given the following code:

Prelude> let f x = if (x) then 55 else "foo"

      

Why is the compiler looking for 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 smallest upper bound 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 and Scala Type Inference?

+3


source to share


3 answers


You can add an instance for Num [Char]

in Haskell if you need it:

{-# 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 if

must return the same type, you force a ~ [Char]

with else

return return "foo"

. This results in a somewhat confusing error message, but it can be a very powerful feature. This means that any numeric literal can act as the type you need, and this is the same concept behind expansion OverloadedStrings

, allowing you to have polymorphic string literals instead of being used pack

wherever you need Text

or ByteString

.

Scala uses subtyping and has a common type for all values. This allows you to relax type safety on functions and literally return a Any

thing. Haskell has no subtypes at all, so the only way to unify these types (similar to finding a LUB) is to take advantage of the fact that numeric literals are polymorphic by constraint Num

, so "foo"

it needs to be implemented in order to compile Num

. In fact, if you included OverloadedStrings

f

it will just compile with the type

f :: (Data.String.IsString a, Num a) => Bool -> a

      

By default, there are no types that meet both constraints, but GHC will happily accept this as a valid feature.

+8


source


This is because of how number literals are interpreted in Haskell.

λ> :type 412
412 :: Num a => a

      

What this does behind the scenes is calling the Num

class function fromInteger

to convert, 412

or in your case, 55

to any instance.

We can look at the class Num

like this:

λ :info Num
class Num a where
  (+) :: a -> a -> a
  (*) :: a -> a -> a
  (-) :: a -> a -> a
  negate :: a -> a
  abs :: a -> a
  signum :: a -> a
  fromInteger :: Integer -> a

      



So when you write 55

and "foo"

, Haskell first understands that the return value must be of a type [Char]

because it "foo" :: [Char]

tries to find an instance Num

that matches that. Of course he fails.

Scala is a much less strongly typed language, and since it does not have class types in the same way as Haskell's, it can only be used to describe similar types Any

. I don’t think I need to explain why it’s not, because type errors are never fun.


If you want to return either Int

or String

, you can use Either

:

f :: Bool -> Either Int String
f b = if b then Left 55 else Right "foo"

      

+4


source


If you're still a little confused, it helps to understand a little about how the back-end works.

In Haskell, a type class is a dictionary of functions associated with a type . Each type can only have one dictionary of functions for each type. When this happens, we say that it satisfies the associated constraints. So if you type :t 5

in GHCi it will tell you that type 5 is equal Num a => a

, in other words, there are many types that matter 5

, and those types are the ones that have a Num

Dictionary. This is true because the dictionary Num

defines a function fromInteger :: Num a => Integer -> a

that takes a particular 5

type Integer

and passes it on to that other type.

So he complains that he doesn't have a dictionary Num

defined for the type String

, and of course he doesn't, because people don't usually use strings as numbers. (Type String

is an alias for [Char]

.)

So when you write:

let f x = if x then 55 else "foo"

      

then this is what happens:

  • Haskell is strongly typed, and if

    is an expression with a specific return value with a specific type. Both branches must be of the same type.
  • The GHC knows this and recognizes first that the type x

    is equal x :: Bool

    (since it looks like an if-branch test, and Haskell's strong types do not allow implicit coercion), and that for 55 :: Num x => x

    now "foo" :: String

    .
  • GHC now combines those last two types. It does this by merging constraints separately from merging types, so it merges x

    with String

    to find out what's x = String

    at the level of the level.
  • GHC extends result constraint to say what type Num String => String

    .
  • GHC is complaining because it doesn't know what the dictionary looks like Num

    for String

    .

How do you solve this problem? The easiest way is to use a data structure data Either x y = Left x | Right y

that is built into Haskell:

let f x = if x then Left 55 else Right "foo"

      

The type of output is then Num x => Either x String

, which is perfectly normal and does not combine the two. Caution . Because of what's called the "monomorphism constraint", which is the default in GHC but not GHCi, unless you provide explicit type annotation that says otherwise, it f

can be reduced Num a => Either a String

to Either Integer String

:

Prelude> :set -XMonomorphismRestriction
Prelude> let f x = if x then Left 55 else Right "foo"
Prelude> :t f
f :: Num a => Bool -> Either a [Char]
Prelude> let f = \x -> if x then Left 55 else Right "foo"
Prelude> :t f
f :: Bool -> Either Integer [Char]

      

+1


source







All Articles