47

The type inference engine of Haskell is much more powerful than Scala's. In Haskell I rarely have to explicitly write the types whereas in Scala the types can only be inferred in expressions but not in method definitions.

For example, see following Haskell code snippet:

size xs = loop xs 0
  where
    loop [] acc = acc
    loop (_ : xs) acc = loop xs (acc+1)

It returns the size of a List. The Haskell compiler can recognize what types are used and what the function definition is. The equivalent Scala code:

def size[A]: List[A] => Int = xs => {
  def loop: (List[A], Int) => Int = {
    case (Nil, acc) => acc
    case (_ :: xs, acc) => loop(xs, acc+1)
  }
  loop(xs, 0)
}

Or with method definitions:

def size[A](xs: List[A]) = {
  def loop(xs: List[A], acc: Int): Int = xs match {
    case Nil => acc
    case _ :: xs => loop(xs, acc+1)
  }
  loop(xs, 0)
}

My question is: Why can't I write them like the following?

def size = xs => {
  def loop = {
    case (Nil, acc) => acc
    case (_ :: xs, acc) => loop(xs, acc+1)
  }
  loop(xs, 0)
}

Once again with method definitions:

def size(xs) = {
  def loop(xs, acc) = xs match {
    case Nil => acc
    case _ :: xs => loop(xs, acc+1)
  }
  loop(xs, 0)
}

Is it because nobody has implemented it yet? Is the type system of Scala not as powerful as needed for this case? Or are there other reasons?

Clutch
  • 7,404
  • 11
  • 45
  • 56
kiritsuku
  • 52,967
  • 18
  • 114
  • 136
  • 1
    possible duplicate of [Disadvantages of Scala type system versus Haskell?](http://stackoverflow.com/questions/3689407/disadvantages-of-scala-type-system-versus-haskell) – Kevin Wright Aug 29 '11 at 18:44
  • 3
    @Kevin: Doesn't look like a duplicate to me. – missingfaktor Aug 29 '11 at 18:49
  • @missingfaktor - Inference is just a subset of the type system as a whole, but "duplicate" is the closest way that SO has to expressing this relationship. – Kevin Wright Aug 29 '11 at 19:15

5 Answers5

59

The main reason is that the type system of Scala allows sub-typing, which the Hindley-Milner type inference algorithm does not support.

Haskell does not have sub-typing, so the algorithm works much better there, although many popular type system extensions supported by GHC cause type inference to fail again, forcing you to provide explicit type signatures for some expressions.

In the end, it's a trade-off between the power of the type system and the amount of type inference that can be done. Scala and Haskell have simply made different trade-offs.

hammar
  • 138,522
  • 17
  • 304
  • 385
  • 11
    You could almost say that Haskell's is 'less powerful' because it doesn't deal with subtype polymorphism *BUT* in a practical sense, "power" => # of language expressions that can be type-inferred correctly. Most of us wish Scala's type inference handled as much of the language as Haskell's does. – jsuereth Aug 29 '11 at 18:43
  • 7
    So how does O'Caml pull off inheritance in the presence of strong type inference? Do you end up having to specify a lot of types explicitly? (I don't know because I've never found myself wanting to use classes in ML.) – Nate C-K Aug 29 '11 at 19:29
  • 7
    @Nate: OCaml is a structurally typed language. The type of variable therefore is the set of operations that it supports. Check out [these](http://www.cs.cmu.edu/~neelk/rows.pdf) excellent slides on the subject. – missingfaktor Aug 30 '11 at 08:52
  • 3
    @jsuereth: Well, all else equal, lacking subtyping *does* make it less powerful than a type system providing that. It's also less powerful than the type systems of theorem provers like Agda. It's that by avoiding tricky features (with small marginal benefit) you get much better leverage to make what you do have easy to use. – C. A. McCann Aug 30 '11 at 17:52
  • @missingfaktor: Thanks, I haven't looked through the slideshow yet but I glanced at it and it looks useful. – Nate C-K Aug 30 '11 at 17:59
  • Isn't [this answer wrong](https://groups.google.com/group/scala-language/browse_thread/thread/3c886611e320f8cb/6461b8d88e64b06d?#6461b8d88e64b06d)? Haskell apparently supports subtyped dynamic dispatch. Apparently Haskell does not support diamond multiple virtual inheritance. Are there other subtyping issues Haskell can't do? – Shelby Moore III Nov 05 '12 at 16:01
  • Follow the link in my prior comment to see the discussion that the chosen answer is not wrong and that Haskell does not support *nominal* subtyping in a fundamentally general way. – Shelby Moore III Nov 06 '12 at 04:07
29

I guess the main reasons have already been given, but I find this quote by Scala's creator Martin Odersky to be particularly informative,

The reason Scala does not have Hindley/Milner type inference is that it is very difficult to combine with features such as overloading (the ad-hoc variant, not type classes), record selection, and subtyping. I’m not saying impossible — there exist a number of extensions that incorporate these features; in fact I have been guitly of some of them myself. I’m just saying it’s very difficult to make this work well in practice, where one needs to have small type expressions, and good error messages. It’s not a shut case either — many researchers are working on pushing the boundaries here (look for instance at Remy’s MLF). But right now it is a tradeoff of better type inferencing vs better support for these features. You can make the tradeoff both ways. The fact that we wanted to integrate with Java tipped the scales in favor of subtyping and away from Hindley/Milner.

Source: comment under post Universal Type Inference is a Bad Thing.

Kipton Barros
  • 21,002
  • 4
  • 67
  • 80
  • 1
    Much better quote than the blog post itself. – Nate C-K Aug 31 '11 at 19:14
  • Can't arbitrary Java-style overloading be done pretty easily with `TypeFamilies`? With types of functions like `divide :: Divide a b => a -> b -> DivResult a b`. I am almost positive any overloaded function can be written in such a way. – semicolon Oct 31 '16 at 23:52
19

hammar gave the biggest reason. Here are two others:

  • Scala uses types to resolve names

Consider

def foo(x) = x.a + x.b

How could Scala possibly infer the type of the argument? Should it look for every class with an a and b field? What if there are more than 1? In Haskell

foo x = (a x) + (b x)

record names are unique, which presents its own problems, but means you can always infer what kind of record is being referred to.

  • For your example: case expressions in Scala can be heterogeneous

In Scala the type of the object being matched can be used either as part of the match, or to decide how matching should be done. So even if all the constructors in the case are for List, you might want to pass something other than a list to it, and have it fail.

Owen
  • 38,836
  • 14
  • 95
  • 125
  • what are "record names" in haskell? can i not have an "a" method on more than one type in scala? – Adam Rabung Aug 29 '11 at 18:46
  • 6
    @Adam: Haskell records do not provide a namespace like Scala's classes do. They live in the outer namespace. – missingfaktor Aug 29 '11 at 18:48
  • @Adam sorry "record fields" would be a better word. Analogous to class members in Scala. – Owen Aug 29 '11 at 19:07
  • 2
    wow, so only type in my system could have a ".x" field? That would really, really make type inference easier (and coding harder?), it seems. – Adam Rabung Aug 29 '11 at 19:08
  • 3
    @Adam: Using the module system, you can reuse the same field names, but they will have to be explicitly qualified if more than one is in scope at a time, e.g. `Foo.x` and `Bar.x`. It's still a mild annoyance, though. – hammar Aug 29 '11 at 19:41
  • 2
    @Adam Rabung: Keep in mind that it's by far not the most common way of accessing data in Haskell. It's an occasional annoyance, but usually doesn't matter because many data types don't even have named fields in the first place. – C. A. McCann Aug 30 '11 at 17:45
  • Even if it could, I don't think I would even want scalac to infer the class/record type just by a field name. Sounds a bit too magical for my taste. Inferring a structural type containing the field sounds more reasonable. In general I don't mind specifying the types of public definitions, it helps me when I create/modify the implementation. – Jesper Nordenberg Aug 31 '11 at 07:03
  • This records issue is in the process of being fixed: http://hackage.haskell.org/trac/ghc/wiki/Records – Abdulsattar Mohammed Nov 24 '11 at 04:22
  • 1
    Row polymorphism fixes this. Scala should not infer a class, but an ad hoc interface that has those fields. – Fresheyeball Jan 09 '19 at 13:17
  • @Fresheyeball That is a good point, though I would argue whether and when it is preferable to infer a minimal yet more structurally complicated type over a broader yet human-readable type is a difficult judgment call. It is not hard to imagine the enormous type signatures that could be result after a few levels of inferring structural-within-structural types. – Owen Jan 11 '19 at 19:15
  • @Owen. Agreed, however having used systems that make this choice; I would say it's quite ergonomic in practice – Fresheyeball Jan 16 '19 at 14:30
  • @Fresheyeball Interesting, what's an example? – Owen Jan 16 '19 at 22:22
  • Check out a PureScript project. It's broadly in use. – Fresheyeball Jan 17 '19 at 17:35
14

Another thing that doesn't play well with Hindley-Milner type inference is method overloading, and related features like default arguments and varargs. That's the reason why it is so hard to write things like zipWithN in Haskell (which is trivial in Scala).

Community
  • 1
  • 1
Landei
  • 54,104
  • 13
  • 100
  • 195
  • 1
    `zipWithN` is a nice example. I imagine a complete list of all the constructs in Scala that make type inference not "work backwards" could be quite long. – Owen Aug 29 '11 at 20:01
0

I have read some responses above, however some of such conclusions may be set in doubt when you realize that F# embraces full sub-typing with OOP within Hindley-Milner type inference since 2006.

  1. Addition of method overload resolution and object-expressions for interoperability with .NET (2004)
  1. Addition of class/interface constructs for object programming (2005)
  1. Addition of “statically resolved type parameters” for handling overloaded arithmetic in a way that fits with Hindley-Milner type inference (2005)
  1. Addition of a treatment of subtyping within Hindley-Milner type inference (2006)

Source history of F#

mindlid
  • 1,679
  • 14
  • 17