2

Having a function f defined as:

def f(i1: Int, i2: Int)(i3: Int) = i1 + i2 + i3

It is possible to define a partially applied function as follows using _:

val f12 = f(1, 2) _  // f12: Int => Int = <function>
f12(3)  // res0: Int = 6

Now when I return a partially applied function from a function, I do not need to use _:

def f23(f: (Int, Int) => Int => Int) = f(2, 3)  // f23: (f: (Int, Int) => Int => Int)Int => Int
val f23f = f23(f)  // f23f: Int => Int = <function>
f23f(4)  // res1: Int = 9

If I place _ at the f23 definition I will get an Error:

def f23(f: (Int, Int) => Int => Int) = f(2, 3) _
Error:(6, 49) _ must follow method; cannot follow Int => Int
def f23(f: (Int, Int) => Int => Int) = f(2, 3) _

What is the reason for this inconsistency?

Krzysztof Słowiński
  • 6,239
  • 8
  • 44
  • 62

2 Answers2

3

f is a method, not a function. You can read about some of the differences here.

f12 is a function derived from f via eta-expansion. It is not a partial function. A PartialFunction is a function defined over a limited domain of input values. If, say, f12 was defined only for Int values less than 500, for example, and undefined for input values greater than 500, then it would be a partial function.

def f23(f: (Int, Int) => Int => Int) = f(2, 3) _

This fails because f, as defined here, is a function that takes 2 Int values and returns a function that takes an Int and returns an Int. In this situation what is the underscore supposed to be standing in for? f(2,3) is a complete invocation that returns an Int=>Int function. It's a bit like writing 5 + 7 _. It's not clear what the _ is substituting for.

You can, on the other hand, do this: ... = f(2,3)(_). Then it's clear that the returned function is being invoked with a missing, i.e. _, parameter. Which is the same thing as: ... = f(2,3).

jwvh
  • 50,871
  • 7
  • 38
  • 64
  • 3
    I'd just add that the reason eta-expansion applies only to methods and not to function values is because it exists exactly to turn methods into function values. – Alexey Romanov Apr 24 '19 at 10:12
  • My mistake with `partial function` - I have changed it to `partially applied function` – Krzysztof Słowiński Apr 24 '19 at 15:19
  • Is it possible to describe the type of `f` in the argument of `f23` as a method? – Krzysztof Słowiński Apr 24 '19 at 15:26
  • 1
    The `f` method can be passed as an argument to a method/function, but the received parameter is a function. Methods can't be passed as arguments. It appears to happen all the time but they are actually being promoted to functions. – jwvh Apr 24 '19 at 18:32
1

This is by design in Scala to prevent developer confusion. If you tell the compiler the type of f12 explicitly it will work as you expect:

`val f12:Int=>Int = f(1, 2)`

This is explained by the language originator (Martin Odersky):

Why the trailing underscore?

Scala's syntax for partially applied functions highlights a difference in the design trade-offs of Scala and classical functional languages such as Haskell or ML. In these languages, partially applied functions are considered the normal case. Furthermore, these languages have a fairly strict static type system that will usually highlight every error with partial applications that you can make. Scala bears a much closer relation to imperative languages such as Java, where a method that's not applied to all its arguments is considered an error. Furthermore, the object-oriented tradition of subtyping and a universal root type accepts some programs that would be considered erroneous in classical functional languages.

For instance, say you mistook the drop(n: Int) method of List for tail(), and you therefore forgot you need to pass a number to drop. You might write, "println(drop)". Had Scala adopted the classical functional tradition that partially applied functions are OK everywhere, this code would type check. However, you might be surprised to find out that the output printed by this println statement would always be ! What would have happened is that the expression drop would have been treated as a function object. Because println takes objects of any type, this would have compiled OK, but it would have given an unexpected result.

To avoid situations like this, Scala normally requires you to specify function arguments that are left out explicitly, even if the indication is as simple as a `_'. Scala allows you to leave off even the _ only when a function type is expected.

Erez Ben Harush
  • 833
  • 9
  • 26
  • Why compiler can infer the type for `def` but can't infer it for `val`? – Bogdan Vakulenko Apr 23 '19 at 13:47
  • This is because type inference is a "best effort" feature. The scala compiler tries to inffer the type but if the type is undecisive it will make the developer state it explicitly by rasing a compile time error. you can find more information about type inference in scala doc's, for example :https://docs.scala-lang.org/tour/type-inference.html – Erez Ben Harush Apr 23 '19 at 18:14
  • the original question was exactly about explaining how type inference works in these two cases. Not how to make `f12 ` works without `_`. – Bogdan Vakulenko Apr 23 '19 at 18:35
  • I clearly miss understood your question and fixed he answer accordingly. – Erez Ben Harush Apr 23 '19 at 19:54
  • Does it have to do with the type of `f` that `f23` expects (`(Int, Int) => Int => Int`)? I have updated the description with the error that is thrown at runtime when `_` is used in `f23` definition. – Krzysztof Słowiński Apr 24 '19 at 08:21