I was reading […], and I came across this function definition in scala:
def upper: String => String = _.toUpperCase
That is not a function definition. That's a method definition, which is something very different.
After trying it out, it seems that it works the same as this:
def upper = (str: String) => str.toUpperCase()
Yes, that's exactly the same thing.
Are both functions the same?
Both methods are the same. (They aren't functions, they are methods.)
If so, how do I read the above function (first one)? does this way of defining definitions have a specific name?
It's not quite clear what you mean by "this way". Both are just normal method definitions. There are four differences between the two:
- In the first definition, the return type of the method is explicitly annotated, in the second definition, it is left to the compiler to be inferred. The name for the first feature is "type annotation" or "explicit typing", the name for the second feature is "type inference" or "implicit typing".
- The first definition uses placeholder syntax to construct the function to be returned from the method, the second definition uses a function literal.
- In the second definition, the parameter for the anonymous function is explicitly annotated with a type, in the first definition, it isn't even mentioned, since we use placeholder syntax.
- In the first definition,
toUpperCase
is called without an argument list, in the second definition, toUpperCase
is called with an empty argument list. toUpperCase
is actually defined with an empty parameter list, so it should be called with an empty argument list; however, Scala allows calling a method that is defined with an empty parameter list without an argument list (but not the other way around, because of the obvious ambiguity). (Note that according to the community Scala style guide, toUpperCase
should be defined without a parameter list because it has no side-effects; however, in the original implementation of Scala, it is actually implemented as a Java method and Java methods must always have exactly one parameter list, it is impossible to define a Java method without a parameter list.)
Note that this should rather be a val
than a def
, though: a val
is only evaluated once, when it is initialized, a def
is evaluated every time it is called. But this def
always returns the same thing anyway, so you could just as well make it a val
. (Or a lazy val
if you want to avoid constructing a function that may not be needed.)
I also noticed that you tagged your question with callbyname, but there is no call-by-name here. Everything is call-by-value in this example.
Here are a couple of other ways to write that same method:
def upper: Function1[String, String] = (str: String) => str.toUpperCase()
This is the most explicit one, all the others are just leaving stuff out from this one and leaving it to the compiler to figure out. But in all cases, the compiler will figure out this and the compiled code will be identical.
Option 1: use infix type constructor syntax for the generic return type of the method:
def upper: String Function1 String = (str: String) => str.toUpperCase()
// ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
Option 2: use the syntactic sugar for function types for the generic return type of the method:
def upper: String => String = (str: String) => str.toUpperCase()
// ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
Option 3: leave out the explicit type of the function parameter:
def upper: Function1[String, String] = str => str.toUpperCase()
// ↑↑↑
Option 4: leave out the explicit return type of the method:
def upper = (str: String) => str.toUpperCase()
// ↑↑↑
Option 5: leave out the empty argument list of toUpperCase
:
def upper: Function1[String, String] = (str: String) => str.toUpperCase
// ↑↑
Option 6: use placeholder syntax for the function:
def upper: Function1[String, String] = _.toUpperCase()
// ↑
You can also combine several of these options, e.g. #3, #5, and #6:
def upper: String => String = _.toUpperCase
However! You need at least one type annotation, so that the compiler can either figure out the parameter type of the function from the return type of the method or figure out the return type of the method from the type of the function. Leaving out both won't work:
def upper = str => str.toUpperCase()
// <console>:11: error: missing parameter type
// def upper = str => str.toUpperCase()
// ^
def upper = _.toUpperCase()
// <console>:11: error: missing parameter type for expanded function ((x$1: <error>) => x$1.toUpperCase())
// def upper = _.toUpperCase()
// ^
(By the way, here you can also see how the compiler expands the placeholder syntax.)
Last but not least, you can use ⇒
instead of =>
both for the function type:
def upper: String ⇒ String = (str: String) => str.toUpperCase()
And the function literal:
def upper: Function1[String, String] = (str: String) ⇒ str.toUpperCase()
If I haven't miscounted, then this gives you a total of 44 different ways of expressing this same method:
def upper = (str: String) ⇒ str.toUpperCase
def upper = (str: String) ⇒ str.toUpperCase()
def upper = (str: String) => str.toUpperCase
def upper = (str: String) => str.toUpperCase()
def upper: Function1[String, String] = _.toUpperCase
def upper: Function1[String, String] = _.toUpperCase()
def upper: Function1[String, String] = (str: String) ⇒ str.toUpperCase
def upper: Function1[String, String] = (str: String) ⇒ str.toUpperCase()
def upper: Function1[String, String] = (str: String) => str.toUpperCase
def upper: Function1[String, String] = (str: String) => str.toUpperCase()
def upper: Function1[String, String] = str ⇒ str.toUpperCase
def upper: Function1[String, String] = str ⇒ str.toUpperCase()
def upper: Function1[String, String] = str => str.toUpperCase
def upper: Function1[String, String] = str => str.toUpperCase()
def upper: String ⇒ String = _.toUpperCase
def upper: String ⇒ String = _.toUpperCase()
def upper: String ⇒ String = (str: String) ⇒ str.toUpperCase
def upper: String ⇒ String = (str: String) ⇒ str.toUpperCase()
def upper: String ⇒ String = (str: String) => str.toUpperCase
def upper: String ⇒ String = (str: String) => str.toUpperCase()
def upper: String ⇒ String = str ⇒ str.toUpperCase
def upper: String ⇒ String = str ⇒ str.toUpperCase()
def upper: String ⇒ String = str => str.toUpperCase
def upper: String ⇒ String = str => str.toUpperCase()
def upper: String => String = _.toUpperCase
def upper: String => String = _.toUpperCase()
def upper: String => String = (str: String) ⇒ str.toUpperCase
def upper: String => String = (str: String) ⇒ str.toUpperCase()
def upper: String => String = (str: String) => str.toUpperCase
def upper: String => String = (str: String) => str.toUpperCase()
def upper: String => String = str ⇒ str.toUpperCase
def upper: String => String = str ⇒ str.toUpperCase()
def upper: String => String = str => str.toUpperCase
def upper: String => String = str => str.toUpperCase()
def upper: String Function1 String = _.toUpperCase
def upper: String Function1 String = _.toUpperCase()
def upper: String Function1 String = (str: String) ⇒ str.toUpperCase
def upper: String Function1 String = (str: String) ⇒ str.toUpperCase()
def upper: String Function1 String = (str: String) => str.toUpperCase
def upper: String Function1 String = (str: String) => str.toUpperCase()
def upper: String Function1 String = str ⇒ str.toUpperCase
def upper: String Function1 String = str ⇒ str.toUpperCase()
def upper: String Function1 String = str => str.toUpperCase
def upper: String Function1 String = str => str.toUpperCase()