Download - All About ... Functions
AGENDABasic syntaxArguments and parametersGeneric functionsFunction as valuesClosuresPartial application, curring and compositionPartial FunctionsSyntactic sugarChanges in Scala 2.12
BASIC SYNTAX: PARAMETER TYPES & RESULTTYPE
You must specify the types of all parameters.def factorial(n: Long): Long = // expression or block
// non-recursive, result type is inferred def factorial(n: Long) = { var r = 1 for (i <- 1 to n) r = r * i r }
If the function is not recursive, you need not specify theresult type.
BASIC SYNTAX: EXPRESSIONS & BLOCKSIf the body of the function requires more than one
expression, use a block { }.def factorial(n: Long) = { // more than 1 expresions var r = 1L for (i <- 1 to n) r = r * i r // <-- last expression, compiler infers result type }
The last expression of the block becomes the value that thefunction returns.
BASIC SYNTAX: RECURSIVE FUNCTIONSWith a recursive function, you must specify the return type.
// recursive, result type has to be specified def factorial(n: Long): Long = { if (n <= 0) 1 else n * factorial(n - 1) }
otherwise...// same recursive function definition without return type def factorial(n: Long) = // ... <console>:14: error: recursive method factorial needs result type else n * factorial(n - 1)
BASIC SYNTAX: RECURSIVE FUNCTIONS (2)Almost each recursive function can be written as tail
recursive one.// recusive, tail recursive @scala.annotation.tailrec def factorial(n: Long, acc: Long = 1): Long = if (n <= 0) acc else factorial(n - 1, n * acc)
Annotation @tailrec is not mandatory and just verifiesthat the method will be compiled with tail call optimization.
<console>:15: error: could not optimize @tailrec annotated method factorial: it contains a recursive call not in tail position else n * factorial(n - 1)
ARGS & PARAMS: PARAMETER LISTSFunction can have zero or more parameter lists.
def name: String = ... def toString(): String = ... def ifTrueDo(ex: Boolean)(code: => Unit): Unit = ...
Call side.name toString // parens can be ommited toString() ifTrue(true)( println("Moin") )
ARGS & PARAMS: DEFAULT AND NAMEDARGUMENTS
You can provide default arguments for functions that areused when you do not specify explicit values.
def show(value: String, left: String = "(", right: String = ")"): String
Call side.// uses defaults for both parameters show("Moin") // sets left in order how args are defined, right is default show("Moin", "{") // sets only right as named argument show("Moin", right = "}") // named arguments need not be in the same order as the parameters show(right = "}", left = "{", value = "Moin")
Named arguments can make a function call more readable.
ARGS & PARAMS: DEFAULT AND NAMEDARGUMENTS (2)
Each parameter list can contain default arguments.import Radix._ // imports type Radix and object Decimal def add(n1: Long, rdx1: Radix = Decimal)(n2: Long, rdx2: Radix = Decimal
Default arguments can be intermixed with normal ones.Normal ones still have to provided on call side.
def show(prefix: String = "(", value: String, sufix: String = ")"): String
show("Moin") <console>:9: error: not enough arguments for method show: (prefix: String Unspecified value parameter value. // successful forces the user to use name arguments show(value = "Moin")
Convention is to use default arguments in the end ofparameter list.
ARGS & PARAMS: DEFAULT ARGUMENTSOVERLOADING
When function uses default arguments, it cannot beoverloaded with another function with default arguments.
def show(prefix: String = "(", value: String): String = ... def show(value: Int, repeat: Int = 1): Int = ... <console>:6: error: in object Shows, multiple overloaded alternatives object Shows {
def show(value: Int, repeat: Int): Int = ... // compiles
ARGS & PARAMS: VARIABLE ARGUMENTSSyntax for variable arguments. Use * behind type.
def sum(ns: Int*): Int = { val _ns: Seq[Int] = ns // internaly it is Seq ... }
Parameter allowing variable arguments has to be last one inparameter list.
def sum(ns: Int*, n: Int): Int = ... <console>:7: error: *-parameter must come last def sum(ns: Int*, n: Int): Int = ...
ARGS & PARAMS: VARIABLE ARGUMENTS (2)sum(0, 1, 2) // any number of arguments sum() // even none
If you already have a sequence of values, you cannot pass itdirectly to function with variable arguments.
val ns = Seq(0, 1, 2, 3, 4, 5) sum(ns) // Seq cannot be used directly <console>:10: error: type mismatch; found : Seq[Int] required: Int
sum(ns: _*) // successful sum( (0 to 5): _* ) // successful
_* tells the compiler that you want the parameter to beexpanded as a sequence of arguments.
GENERIC FUNCTIONS: TYPE PARAMETERSFunctions like classes and traits can have type parameters.
def show[T](value: T): String = ... def add[F, S, R](first: F, second: S): R = ...
There can be only one type parameter list for function.
GENERIC FUNCTIONS: TYPE PARAMETERBOUNDS
Sometimes it is useful to place restrictions on function typeparameters.
def lessThan[T <: Comparable[T]](first: T, second: T): Boolean = { first.compareTo(second) < 0 }
lessThan("Ahoi", "Moin")
<: is called upper bounds and means that T has to be asubtype of Comparable[T].
GENERIC FUNCTIONS: TYPE PARAMETERBOUNDS (2)
It is also possible to specify lower bounds.
Expressed as >: declare a type to be a supertype of anothertype.
case class Node[+T](h: T, t: Node[T]) { // work when Node is invariant // def prepend(elem: T): Node[T] = Node(elem, this) def prepend[U >: T](elem: U): Node[U] = Node(elem, this) }
Typical usage is when type parameter of enclosing class iscovariant.
GENERIC FUNCTIONS: TYPE PARAMETERBOUNDS (3)
It is possible to have both bounds for one type parameter.T <: Upper >: Lower
But ... it is not possible to have multiple upper or lowerbounds.
It is still possible to require that type parameter implementsmultiple traits.
T <: Comparable[T] with Serializable with Clonable
GENERIC FUNCTIONS: IMPLICIT PARAMETERSFunction can have parameter list marked as implicit.sealed trait Affix case class Prefix(value: String) extends Affix case class Sufix(value: String) extends Affix
def show(value: String)(implicit prefix: Prefix, sufix: Sufix): String
Function can still be called with explicit arguments...show("Moin")(Prefix("("), Sufix(")"))
GENERIC FUNCTIONS: IMPLICIT PARAMETERS(2)
But the compiler can look for default values to supply withthe function call.
object Affixes { implicit val prefix = Prefix("(") implicit def sufix = Sufix(")") }
import Affixes._ // i.e. import of implicit values show("Moin")
Default value has to be val or def declared as implicit.
Usual implicits resolution mechanism is applied.
GENERIC FUNCTIONS: CONTEXT BOUNDSFor parameter list with 1 implicit parameter there is a
syntactic sugar called context bound.// suppose that Prefix uses type parameter for value case class Prefix[T](value: T) extends Affix
// generic definition of show will look like def show[T](value: T)(implicit prefix: Prefix[T]): String = { prefix.value // accessing implicit parameter, directly ... }
// shorthand version defined via context bound def show[T : Prefix](value: T): String = { val prefix = implicitly[Prefix[T]] // retrieving implicit parameter prefix.value // accessing implicit parameter ... }
Context bound is important concept by encoding typeclasses in scala.
GENERIC FUNCTIONS: CONTEXT BOUNDS (2)You can define multiple contexts for one type parameter T.
case class Prefix[T](value: T) extends Affix case class Sufix[T](value: T) extends Affix
def show[T : Prefix : Sufix](value: T): String = ... def show[T <: Comparable[T] : Prefix : Sufix](value: T): String = ...
If more than 1 type parameter is required, contextbounds cannot be used.
case class Infix[T1, T2](left: T1, right: T2) extends Affix
// def show[T1, T2 : Infix] won't compile def show[T1, T2](left: T1, right: T2)(implicit infix: Infix[T1, T2]):
GENERIC FUNCTIONS: TYPE CONSTRAINTSScala Predef defines type constraints. Purpose is to
contraint type parameters.// usage def show[T](value: T)(implicit ev: T =:= String): String = ... // call side show("Moin") show(1) <console>:17: error: Cannot prove that Int =:= String. show(1)
A =:= B, which means A must be equal to BA <:< B, which meand A must be subtype of B
FUNCTIONS AS VALUES: FUNCTIONS ASVALUES
Functions are "first-class citizens" in Scala.
You can:
call a functionstore a function into the variablepass a function into another function as an argument
FUNCTIONS AS VALUES: ANNONYMOUSFUNCTIONS
Scala provides lightweight syntax for annonymousfunctions.
(x: Int) => x + 1
It is called function literal.val addOne = (x: Int) => x + 1 // assignment to variable
Just like numbers or strings have literals: 3 or "Moin",functions have too.
FUNCTIONS AS VALUES: ANNONYMOUSFUNCTIONS (2)
Behind the scenes, compiler will convert it into annonymousclass definition.
val addOne = new Function1[Int, Int] { def apply(x: Int): Int = x + 1 // special rule for apply }
addOne(2) addOne.apply(2)
Provided are traits from Function0 until Function22.// compiled into Function2[Int, String, String] (x: Int, y: String) => x + "-" + y
// compiled into Function0[String] () => "Moin"
FUNCTIONS AS VALUES: FUNCTIONS WITHFUNCTIONS PARAMETERS
Functions can take other fuctions as parameters or returnthem as results.
// function as parameterdef fiftyPercent(fn: Double => Double): Double = fn(0.5) // function as result def multiplyBy(factor: Int) = (x: Int) => x * factor
// supplying function as argument fiftyPercent( (x: Double) => x * x )
val twice = multiplyBy(2) // assignment to variable twice(2) // call
Such functions are called high-order functions.
Syntax Double => Double represents functiontype which can be used as parameter type or result type.
FUNCTIONS AS VALUES: PARAMETERINFERENCE
By passing annonymous function to another function, Scaladeduces types when possible.
def fiftyPercent(fn: Double => Double): Double = fn(0.5)
fiftyPercent( (x: Double) => x * x ) fiftyPercent( (x) => x * x ) // type is inferred to be Double fiftyPercent( x => x * x ) // parens can be omitted
For function with 1 parameter it is possible to omit ().
FUNCTIONS AS VALUES: TYPE INFERENCEHigh-order functions can use type parameters too.
def dropWhile[A](l: List[A], f: A => Boolean): List[A] = ...
val numbers = List(1, 2, 3) dropWhile(numbers, (x: Int) => x < 2) // Int is required
Type parameters are inferred from le� to right acrossparameter lists.
def dropWhile[A](l: List[A])(f: A => Boolean): List[A] = ...
val numbers = List(1, 2, 3) dropWhile(numbers, x => x < 2)
To help compiler use multiple parameter lists.
CLOSURESScala allows you to define function inside any scope:
package, class, object or even in another function.
In body of function it is possible to access any variables froman enclosing scope.
def show(items: List[String], sufix: String): String = { // inner function, which accesses sufix from enclosing scope def loop(items: List[String], buffer: StringBuilder) = if (items.isEmpty) buffer else { buffer.append(head).append(sufix) loop(items.tail, buffer) } loop(items, StringBuilder.newBuilder).toString }
Such function which access variables from enclosing scopeis called closure.
CLOSURES (2)Variables accesed from enclosing scope of closure are
called free variables.def multiplyBy(factor: Int) = (x: Int) => x * factor
val doubled = multiplyBy(2) val tripled = multiplyBy(3)
def calc(n: Int) = doubled(n) + tripled(n)
Free variables are bound lexically on call side.
PARTIAL APPLICATIONScala allow you to apply the functions partially. That
means applying some but not all the arguments of thefunction.
// helper which applies A in any (A, B) => C function def partial[A, B, C](a: A, fn: (A, B) => C): B => C = (b: B) => fn(a, b)
val multiply = (x: Int, y: Int) => x * y
// partialy applied multiply, to always multiply by 2 val doubled = partial(2, multiply) doubled: Int => Int = $$Lambda$1197/1032689422@2fac80a8
The process is called partial application and resultis partially applied function.
PARTIAL APPLICATION (2)Partial application is so common, that Scala
dedicated syntax for that.def divide(x: Int, y: Int): Int = x / y
val oneByN = divide(1, _: Int) // applying 1st argument oneByN: Int => Int = $$Lambda$1336/1775639151@2696b687
val nBy100 = divide(_: Int, 100) // applying 2nd argument nBy100: Int => Int = $$Lambda$1337/1973093841@ea45a5b
_: Type represents "hole", yet not applied argument.
PARTIAL APPLICATION (3)Partial application also allows to obtain normally defined
function as value.def divide(x: Int, y: Int): Int = x / y
// Not applying any of the arguments val division = divide(_: Int, _: Int) val division = divide _ // shorter way
Technically we are converting method into instance ofFunction object.
CURRYINGFunction with more parameters can be expressed as chain of
one-parameter functions and vice versa.// helper which transforms (A, B) => C into A => B => C def curry[A, B, C](fn : (A, B) => C): A => B => C = (a: A) => (b: B) => fn(a, b) // helper which transforms A => B => C into (A, B) => C def uncurry[A, B, C](fn: A => B => C): (A, B) => C = (a: A, b: B) => fn(a)(b)
val multiply = (x: Int, y: Int) => x * y val multiplyChain = curry(multiply) // Int => Int => Int val againMultiply = uncurry(multiplyChain)
multiplyChain(2)(3) val doubled = multiplyChain(2) // partially applied function
CURRYING (2)It can be useful in relation to partial application.def divide(x: Int, y: Int): Int = x / y val multiply = (x: Int, y: Int) => x * y
val oneByN = (divide _).curried(1) // divide(1, _: Int) oneByN: Int => Int = $$Lambda$1336/1775639151@2696b687
val twice = multiply.curried(2) // multiply(2, _: Int) twice: Int => Int = scala.Function2$$Lambda$1411/1255024717@236861da
curried is provided by all Function* traits.
COMPOSITIONAnother very useful operation over functions is function
composition.// helper which composes two functions def compose[A, B, C](f: A => B, g: B => C): A => C = (a: A) => g(f(a))
val addOne = (x: Int) => x + 1 val multiplyBy100 = (x: Int) => x * 100
val plus1Muliplied100 = compose(addOne, multiplyBy100)
It composes 2 functions A => B and B => C into A => B.
Types of the functions must be in alignment.
COMPOSITION (2)Function composition is such a useful thing that, Scala
standard library provides methods for function composition.val addOne = (x: Int) => x + 1 val multiplyBy100 = (x: Int) => x * 100
val plus1Muliplied100 = addOne.andThen(multiplyBy100) val multiply100plus1 = addOne.compose(multiplyBy100)
They are only defined in trait Function1, thus functionwith more parameters must be curried first.
PARTIAL FUNCTIONS: CONCEPTConcept comes from mathematics, where we talk about
total and partial functions.// total function, defined for each Int def addOne(i: Int) = i + 1 // partial function, undefined for 0 def divideOne(d: Int) = 1 / d
PARTIAL FUNCTIONS: DEFINITIONSet of case statements within { } is partial
function.// another form of annonymous function val divideOne: PartialFunction[Int, Int] = { case d: Int if d != 0 => 42 / d }
// it is translated into (more or less) val divideOne = new PartialFunction[Int, Int] { def apply(d: Int) = 1 / d def isDefinedAt(d: Int) = d != 0 }
PartialFunction is trait which extends Function1
PARTIAL FUNCTIONS: USAGEUsage and transformations.
val plusMinus: PartialFunction[Char, Int] = { case '+' => 1 case '-' => -1 }
// application like total function scala> plusMinus('+') res15: Int = 1 scala> plusMinus('o') // undefined scala.MatchError: o (of class java.lang.Character) at scala.PartialFunction$$anon$1.apply(PartialFunction.scala:254)
// check if defined scala> plusMinus.isDefinedAt('0') res16: Boolean = false
PARTIAL FUNCTIONS: USAGE (2)Usage (part 2)
// substitute undefined values scala> plusMinus.applyOrElse('o', (_: Char) => 0) res21: Int = 0 // conversion to total functions scala> plusMinus.lift res22: Char => Option[Int] = <function1>
PARTIAL FUNCTIONS: USAGE (3)True benefit.
// supplying PartialFunction into call where Function1 is expected someMap.foreach { // ... destructuring for free case (k, v) => println(k + " -> " + v) }
case class Greeting(greeting: String)
List(Greeting("Moin"), Int).collect { case Greeting(greeting) => println(greeting) greeting }
Pattern matching like casting-done-right, guards, anddestructuring for free.
SYNTACTIC SUGAR: CALL BY NAMEPAREMETERS
Sequence of statements can be modeled as function with noparameters.
// code parameter has type `Function0` def runInThread(code: () => Unit): Thead = { ... code() ... }
// you have to supply function () => Unit runInThread(() => println("Moin"))
SYNTACTIC SUGAR: CALL BY NAMEPAREMETERS (2)
Usually parameters are called by value, i.e. value of theparameter is evaluated before it is given to function.
// call by name parameter def runInThread(code: => Unit): Thread = ...
// no need for () runInThread(println("Moin"))
=> before paremeter type says compiler that parameter iscalled by name.
Its value is not evaluated before it is given to function. It isconsidered lazy on call side.
SYNTACTIC SUGAR: CALL BY NAMEPAREMETERS (3)
Evaluation of call by name parameter happenseverytime the parameter is accessed.
def until(condition: => Boolean)(code: => Unit): Unit = { while(!condition) { // evaluated by aech access code // no parens necessary } }
var x = 0 until(x == 10)({ x += 1; println(x) }) // loop is terminated
SYNTACTIC SUGAR: CONTROL ABSTRACTIONSCaller can use { } instead of ( ) for any parameter list
with just one parameter.def until(condition: => Boolean)(code: => Unit): Unit = { while(!condition) { // evaluated by aech access code // no parens necessary } }
var x = 0 until (x == 10) { x += 1 println(x) } until { x == 20 } { x += 1; println(x) }
This allows to write control abstractions, i. e.functions which looks like build in language keywords.
SYNTACTIC SUGAR: CONTROL ABSTRACTIONS(2)
Using return to return a value from an anonymousfunction.
def indexOf(str: String, ch: Char): Int = { var i = 0 until (i == str.length) { // closure // will return from enclosing functions if (str(i) == ch) return i i += 1 } return -1 }
indexOf("Moin", 'o') res8: Int = 1
SYNTACTIC SUGAR: CONTROL ABSTRACTIONS(3)
Use return judiciously.
If it is used in a named function it has to provide result type.scala> def show(value: String, sufix: String = "") = { return value + sufix }<console>:11: error: method show has return statement; needs result type def show(value: String, sufix: String = "") = { return value + sufix }
Very fragile construct.// return expression is captured and not evaluated def somewhereDeepInTheCodebase: () => Int = () => return () => 1 // passed thru a lot of code val x = somewhereDeepInTheCodebase // finally used x() scala.runtime.NonLocalReturnControl // !!! RESULT !!! // Btw. NonLocalReturnControl extends NoStackTrace // i.e. so you are given no clue about origin.
SCALA 2.12: FUNCTION LITERALSType checker accepts a function literal as a valid expression
for any Single Abstract Method (SAM) typescala> val r: Runnable = () => println("Run!")
r: Runnable = $$Lambda$1073/754978432@7cf283e1 scala> r.run()
SCALA 2.12: FUNCTION LITERALS (2)Only lambda expressions are converted to SAM typeinstances, not arbitrary expressions of FunctionN.
scala> val f = () => println("Faster!") scala> val fasterRunnable: Runnable = f <console>:12: error: type mismatch; found : () => Unit required: Runnable
SCALA 2.12: FUNCTION LITERALS (2)Scala's built-in FunctionN traits are compiled to SAM
interfaces.scala> val addOne = (n: Int) => n + 1 addOne: Int => Int = $$Lambda$1342/671078904@642c6461
scala> val addTwo = (n: Int) => n + 3 addTwo: Int => Int = $$Lambda$1343/205988608@5f6494c0
scala> addOne andThen addTwo res14: Int => Int = scala.Function1$$Lambda$1335/1630903943@464aeb09
SCALA 2.12: PARAMETER INFERENCEThe parameter type in a lambda expression can be omitted
even when the invoked method is overloaded.scala> trait MyFun { def apply(x: Int): String } scala> object T { | def m(f: Int => String) = 0 | def m(f: MyFun) = 1 | } scala> T.m(x => x.toString) res0: Int = 0
Note that though both methods are applicable, overloadingresolution selects the one with the Function1 argument
type.
SCALA 2.12: BREAKING CHANGESOverloading resolution has been adapted to prefer methods
with Function-typed arguments over methods withparameters of SAM types.
scala> object T { | def m(f: () => Unit) = 0 | def m(r: Runnable) = 1 | } scala> val f = () => () scala> T.m(f) res0: Int = 0
In Scala 2.11, the first alternative was chosen because it isthe only applicable.
In Scala 2.12, both methods are applicable, but most specificalternative is picked.
SCALA 2.12: BREAKING CHANGESSAM conversion precedes implicits.
trait MySam { def i(): Int } implicit def convert(fun: () => Int): MySam = new MySam { def i() = 1 }val sam1: MySam = () => 2 // Uses SAM conversion, not the implicit sam1.i() // Returns 2
Note that SAM conversion only applies to lambdaexpressions, not to arbitrary expressions with Scala
FunctionN typesval fun = () => 2 // Type Function0[Int] val sam2: MySam = fun // Uses implicit conversion sam2.i() // Returns 1