3
votes

I have been looking at type inference in Scala and there are a couple of things I'd like to understand a bit better around why expression/method-return types have to be explicitly declared in a few cases.

Explicit return declaration

Example (works if return keyword is ommitted):

def upCase(s: String) = {
  if (s.length == 0)
    return s    // COMPILE ERROR - forces return type of upCase to be declared.
  else
    s.toUpperCase()
}

Why can't I use the explicitly typed parameter as a return value without declaring the return type? And that's not only for direct parameter references, just for any 'type-inferable' expression.

Method overloading

Example (fails to compile when the second joiner method is added):

def joiner(ss: List[String], sep: String) = ss.mkString(sep)

def joiner(ss: List[String]) = joiner(strings, " ")   // COMPILE ERROR WHEN ADDED
5
Perhaps an example illustrating what Scala should do and what it does do would be a good idea!oxbow_lakes
Your second example doesn't compile without explicit return types due to the joiner method being overloaded - although again it's not entirely clear why Scala requires this restrictionoxbow_lakes
I have edited this question in the hope that it will get better treatment from the community. It was a bit argumentative but there is an interesting question in here. OP - feel free to rollback (or ask me to rollback) if you're not happy with my edits.oxbow_lakes
Thank you, again, for your attention. I didn't want to be 'agressive'; your revision is great.Bubba88
My current suspect about these quirks is that designer's concern was to write a single-pass compiler; also, I think, that the compilation speed is involved here. I say that, because the features I want could easily be added causing a (very) little overhead. Actually, I do not know whether 'scalac' is single-pass)Bubba88

5 Answers

3
votes

Well most obvious answer is: because it stated in specification see part 6.20 of scala reference. But why it was designed this way is indeed very intresting question. I suspect it connected to the fact that compiler can't predict that expression will be the last one, since return changes execution flow.

EDIT:

Consider if return doesn't require explicit return type following code:

def bar() = {   
  if(guard())  
    return "SS"  
  else if(gurard1())  
    return true   
  2  
}

that return type should bar have in this situation? Well there is option with most common supertype, but I think it will get us to returning Any in many cases. Well this is just my thoughts which may be totally incorrect =)

2
votes

The type of a function or method is inferred from the type of its last statement. Usually, that's an expression.

Now, "return" breaks the control flow. It is an "immediate interrupt", so to speak. Because of that, the normal rules used to infer the type of an expression can't be used anymore. It still could be done, of course, but I'm guessing the cost in compiler complexity was deemed to high for the return.

Here's an example of how the flow is broken:

def toNumber(s: String) = {
  if (s == null)
    return ""

  if (s matches """\d+""")
    s.toInt
  else
    0
}

Normally, the type of the second if statement would be used to infer the type of the whole function. But the return on the first if introduces a second return point from the function, so this rule won't work.

1
votes

Type inference infers the return type of a method when it can, which is more or less in any case that the method isn't recursive.

Your example would work if you changed it to:

def upCase(s: String) = {
 if (s.length == 0)
   s    // note: no return
 else
   s.toUpperCase()
}

I don't know offhand why the return changes this.

0
votes

Disclaimer - this answer was directed to the question as it was originally posted

Scala's type inference already does infer the return type of a method / expression:

scala> def foo(s : String) = s + " Hello"
foo: (String)java.lang.String

scala> var t = foo("World")
t: java.lang.String = World Hello

and:

scala> def bar( s : String) = s.toInt
bar: (String)Int

scala> var i = bar("3")
i: Int = 3

and:

scala> var j = if (System.getProperty("user.name") == "oxbow") 4 else "5".toInt
j: Int = 5

EDIT - I didn't realize that the inclusion of the return keyword meant that the return type of an expression had to be explicitly declared: I've pretty much stopped using return myself - but it's an interesting question. For the joiner example, the return type must be declared because of overloading. Again, I don't know the details as to why and would be interested to find out. I suspect a better-phrased question subject would elicit an answer from the likes of James Iry, Dan Spiewak or Daniel Sobral.

0
votes

I suspect the method overloading (lack of) inference is related to the similar problem with recursive calls, because if the overloaded methods doesn't call each other, it works perfectly:

  def joiner1(ss: List[String], sep: String) = ss.mkString(sep)
  def joiner(ss: List[String], sep: String) = ss.mkString(sep)
  def joiner(ss: List[String]) = joiner1(ss, " ")  

There's two overloaded joiner methods, but the types are inferred correctly the code compiles.