Revealing the Scala magician’s code: method vs function
Join the DZone community and get the full member experience.
Join For FreeHow’s a method different from a function in Scala?
A method can appear in an expression as an internal value (to be called with arguments) but it can’t be the final value, while a function can:
//a simple method scala> def m(x: Int) = 2*x m: (x: Int)Int //a simple function scala> val f = (x: Int) => 2*x f: (Int) => Int = <function1> //a method can't be the final value scala> m <console>:6: error: missing arguments for method m in object $iw; follow this method with `_' if you want to treat it as a partially applied function m ^ //a function can be the final value scala> f res11: (Int) => Int = <function1>
Parameter list is optional for methods but mandatory for functions
A method can have no parameter list or have one (empty or not), but a function must have one (empty or not):
//a method can have no parameter list scala> def m1 = 100 m1: Int //a method can have an empty parameter list scala> def m2() = 100 m2: ()Int //a function must have a parameter list scala> val f1 = => 100 <console>:1: error: illegal start of simple expression val f1 = => 100 ^ //a function's parameter list could be empty scala> val f2 = () => 100 f2: () => Int = <function0>
Why a method can have no parameter list? See below.
Method name means invocation while function name means the function itself
Because methods can’t be the final value of an expression, so if you write a method name and if it doesn’t take any argument (no argument list or an empty argument list), the expression is meant to call that method to get the final value. Because functions can be the final value, if you just write the function name, no invocation will occur and you will get the function as the final value. To force the invocation, you must write ():
//it doesn't have a parameter list scala> m1 res25: Int = 100 //it has an empty parameter list scala> m2 res26: Int = 100 //get the function itself as the value. No invocation. scala> f2 res27: () => Int = <function0> //invoke the function scala> f2() res28: Int = 100
Why we can provide a method when a function is expected?
Many Scala methods such as map() and filter() take functions arguments, but why can we provide methods to them like:
scala> val myList = List(3, 56, 1, 4, 72) myList: List[Int] = List(3, 56, 1, 4, 72) //the argument is a function scala> myList.map((x)=>2*x) res29: List[Int] = List(6, 112, 2, 8, 144) //try to pass a method as the argument instead scala> def m3(x: Int) = 3*x m3: (x: Int)Int //still works scala> myList.map(m3) res30: List[Int] = List(9, 168, 3, 12, 216)
This is because when a function is expected but a method is provided, it will be automatically converted into a function. This is called the ETA expansion. This makes it a lot easier to use the methods we created. You can verify this behavior with the tests below:
//expecting a function scala> val f3: (Int)=>Int = m3 f3: (Int) => Int = <function1> //not expecting a function, so the method won't be converted. scala> val v3 = m3 <console>:5: error: missing arguments for method m3 in object $iw; follow this method with `_' if you want to treat it as a partially applied function val v3 = m3 ^
With this automatic conversion, we can write concise code like:
//10.< is interpreted as obj.method so is still a method. Then it is converted to a function. scala> myList.filter(10.<) res31: List[Int] = List(56, 72)
Because in Scala operators are interpreted as methods:
- prefix: op obj is interpreted as obj.op.
- infix: obj1 op obj2 is interpreted as obj1.op(obj2).
- postfix: obj op is interpreted as obj.op.
You could write 10< instead of 10.<:
scala> myList.filter(10<) res33: List[Int] = List(56, 72)
How to force a method to become a function?
When a function is not expected, you can still explicitly convert a method into a function (ETA expansion) by writing an underscore after the method name:
scala> def m4(x: Int) = 4*x m4: (x: Int)Int //explicitly convert the method into a function scala> val f4 = m4 _ f4: (Int) => Int = <function1> scala> f4(2) res34: Int = 8
A call by name parameter is just a method
A call by name parameter is just a method without a parameter list. That's why you can invoke it by writing its name without using ():
//use "x" twice, meaning that the method is invoked twice. scala> def m1(x: => Int) = List(x, x) m1: (x: => Int)List[Int] scala> import util.Random import util.Random scala> val r = new Random() r: scala.util.Random = scala.util.Random@ad662c //as the method is invoked twice, the two values are different. scala> m1(r.nextInt) res37: List[Int] = List(1317293255, 1268355315)
If you "cache" the method in the body, you'll cache the value:
//cache the method into y scala> def m1(x: => Int) = { val y=x; List(y, y) } m1: (x: => Int)List[Int] //get the same values scala> m1(r.nextInt) res38: List[Int] = List(-527844076, -527844076)
Is it possible to maintain the dynamic nature of x in the body? You could cache it as a function by explicitly converting it:
//explicit conversion, but then you must invoke the function with (). scala> def m1(x: => Int) = { val y=x _; List(y(), y()) } m1: (x: => Int)List[Int] scala> m1(r.nextInt) res39: List[Int] = List(1413818885, 958861293)
From http://agileskills2.org/blog/2011/08/21/revealing-the-scala-magician%E2%80%99s-code-method-vs-function/
Opinions expressed by DZone contributors are their own.
Comments