views:

374

answers:

5

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
A: 

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.

oxbow_lakes
yes, but if you use the 'return' clause - it won't)
Bubba88
return is superfluous and can be ommitted - although it's a good question to ask *"Why does an explicit return require an explicit return type?"*
oxbow_lakes
+1  A: 

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.

Kevin Peterson
+3  A: 

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 =)

Nikolay Ivanov
-"I suspect it connected to the fact that compiler can't predict that expression will be the last one, since return changes execution flow." ...Don't quite understand)
Bubba88
see me thoughts in edit
Nikolay Ivanov
Yep, that's a trouble)
Bubba88
However, I guess, you could eliminate the 'return' clauses and it will change nothing. (I mean the ambiguity will be still present)
Bubba88
Surely declaring `return` explicitly *guarantees* that this is the last expression! Then scala could just do a common-supertype of all of the `return` statements...
oxbow_lakes
@Bubba88 No, if you eliminate return it will change all other expressions ( in my example just '2' but presume there more) will be executed. @oxbow_lakes Nope. What is last expression in code I gave in edit?It depends of if either of guards fuctions returing true. I alreeady mentioned most common supertype solution BTW
Nikolay Ivanov
A: 

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.

Daniel
I cannot agree, that "the cost in compiler complexity was deemed to high for the return." We can check all return clauses and paths, leading to them at compile time; after all - doesn't the compiler need to check the type of every expression in function body to resolve the final one?
Bubba88
very silly example would be declary two 'typeless' variables (initialized by numbers) in a function and multiplying them as the final expression: the compiler must check both variables before telling the result's type, cause it can be both integer or float or long etc. I don't actually know 'scalac'-s type inference algorithm, but something tells me, that recursive drivedown cannot be actually avoided.
Bubba88
The type inferences works statement by statement. If something cannot be figured out by the end of the statement, then it barfs, even if something further on would have provided enough information to decide. Now, the type inferencer algorithm is likely the most complex part of the Scala compiler and, in fact, it is not even spec'ed, so extending it is not a trivial task, and the increase in complexity has consequences for the correctness and maintainability of the compiler. The line for that, as far as I can see, is drawn on a loose cost/benefit analysis.
Daniel
Daniel, sorry but it seems that you're not completely right. We actually can (automatically) infer what is the type of 'return' expression, cause all we need is the types of expressions preceding it. Coupled with that 'the type inferences works statement by statement', i think it's easy to get - we do not need to calculate any further than 'return' clause!) Cause, as we know, the return statement is always the last statement in execution flow. And about specs, Nikolay said: "Well most obvious answer is: because it stated in specification see part 6.20 of scala reference.."
Bubba88
The `return` statement is not always the last. See my example for instance.
Daniel
A: 

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.

GClaramunt
I do think so, too. But I wonder how method overloading is related to recursion; I mean, overloaded method is just two or more different methods without any logical relationship.
Bubba88
Have you try asking the question in the Scala mailing list? You'll get either the why or a fix for the compiler :)
GClaramunt