Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Type Inference in Haskell v. Scala

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?

like image 472
Kevin Meredith Avatar asked Dec 11 '22 23:12

Kevin Meredith


1 Answers

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 Anything. 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.

like image 114
bheklilr Avatar answered Jan 08 '23 02:01

bheklilr