The document discusses functions in Scala. It covers basic syntax including parameter types, recursive functions, and default arguments. It also discusses functions as values that can be passed as arguments or returned from other functions. Generic functions and type parameters are explained. The document also covers closures where functions can access variables from outer scopes, and partial application, currying, and function composition.
4. BASIC SYNTAX: PARAMETER TYPES & RESULT
TYPE
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 the
result type.
5. BASIC SYNTAX: EXPRESSIONS & BLOCKS
If 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 the
function returns.
6. BASIC SYNTAX: RECURSIVE FUNCTIONS
With 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)
7. 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 verifies
that the method will be compiled with tail call optimization.
<console>:15: error: could not optimize @tailrec annotated method factorial:
else n * factorial(n - 1)
9. ARGS & PARAMS: PARAMETER LISTS
Function 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") )
10. ARGS & PARAMS: DEFAULT AND NAMED
ARGUMENTS
You can provide default arguments for functions that are
used 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.
11. ARGS & PARAMS: DEFAULT AND NAMED
ARGUMENTS (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 of
parameter list.
12. ARGS & PARAMS: DEFAULT ARGUMENTS
OVERLOADING
When function uses default arguments, it cannot be
overloaded 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
13. ARGS & PARAMS: VARIABLE ARGUMENTS
Syntax 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 in
parameter list.
def sum(ns: Int*, n: Int): Int = ...
<console>:7: error: *-parameter must come last
def sum(ns: Int*, n: Int): Int = ...
14. 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 it
directly 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 be
expanded as a sequence of arguments.
16. GENERIC FUNCTIONS: TYPE PARAMETERS
Functions 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.
17. GENERIC FUNCTIONS: TYPE PARAMETER
BOUNDS
Sometimes it is useful to place restrictions on function type
parameters.
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 a
subtype of Comparable[T].
18. GENERIC FUNCTIONS: TYPE PARAMETER
BOUNDS (2)
It is also possible to specify lower bounds.
Expressed as >: declare a type to be a supertype of another
type.
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 is
covariant.
19. GENERIC FUNCTIONS: TYPE PARAMETER
BOUNDS (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 lower
bounds.
It is still possible to require that type parameter implements
multiple traits.
T <: Comparable[T] with Serializable with Clonable
20. GENERIC FUNCTIONS: IMPLICIT PARAMETERS
Function 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(")"))
21. GENERIC FUNCTIONS: IMPLICIT PARAMETERS
(2)
But the compiler can look for default values to supply with
the 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.
22. GENERIC FUNCTIONS: CONTEXT BOUNDS
For 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 type
classes in scala.
23. 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, context
bounds 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]):
24. GENERIC FUNCTIONS: TYPE CONSTRAINTS
Scala 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 B
A <:< B, which meand A must be subtype of B
26. FUNCTIONS AS VALUES: FUNCTIONS AS
VALUES
Functions are "first-class citizens" in Scala.
You can:
call a function
store a function into the variable
pass a function into another function as an argument
27. FUNCTIONS AS VALUES: ANNONYMOUS
FUNCTIONS
Scala provides lightweight syntax for annonymous
functions.
(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.
28. FUNCTIONS AS VALUES: ANNONYMOUS
FUNCTIONS (2)
Behind the scenes, compiler will convert it into annonymous
class 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"
29. FUNCTIONS AS VALUES: FUNCTIONS WITH
FUNCTIONS PARAMETERS
Functions can take other fuctions as parameters or return
them as results.
// function as parameter
def 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 function
type which can be used as parameter type or result type.
30. FUNCTIONS AS VALUES: PARAMETER
INFERENCE
By passing annonymous function to another function, Scala
deduces 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 ().
31. FUNCTIONS AS VALUES: TYPE INFERENCE
High-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 across
parameter 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.
33. CLOSURES
Scala 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 from
an 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 scope
is called closure.
34. 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.
36. PARTIAL APPLICATION
Scala allow you to apply the functions partially. That
means applying some but not all the arguments of the
function.
// 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 result
is partially applied function.
37. 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.
38. 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 of
Function object.
39. CURRYING
Function 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
40. 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.
41. COMPOSITION
Another 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.
42. 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 function
with more parameters must be curried first.
44. PARTIAL FUNCTIONS: CONCEPT
Concept 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
45. PARTIAL FUNCTIONS: DEFINITION
Set 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
46. PARTIAL FUNCTIONS: USAGE
Usage 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
50. SYNTACTIC SUGAR: CALL BY NAME
PAREMETERS
Sequence of statements can be modeled as function with no
parameters.
// code parameter has type `Function0`
def runInThread(code: () => Unit): Thead = {
...
code()
...
}
// you have to supply function () => Unit
runInThread(() => println("Moin"))
51. SYNTACTIC SUGAR: CALL BY NAME
PAREMETERS (2)
Usually parameters are called by value, i.e. value of the
parameter 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 is
called by name.
Its value is not evaluated before it is given to function. It is
considered lazy on call side.
52. SYNTACTIC SUGAR: CALL BY NAME
PAREMETERS (3)
Evaluation of call by name parameter happens
everytime 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
53. SYNTACTIC SUGAR: CONTROL ABSTRACTIONS
Caller 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.
54. SYNTACTIC SUGAR: CONTROL ABSTRACTIONS
(2)
Using return to return a value from an anonymous
function.
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
55. 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.
57. SCALA 2.12: FUNCTION LITERALS
Type checker accepts a function literal as a valid expression
for any Single Abstract Method (SAM) type
scala> val r: Runnable = () => println("Run!")
r: Runnable = $$Lambda$1073/754978432@7cf283e1
scala> r.run()
58. SCALA 2.12: FUNCTION LITERALS (2)
Only lambda expressions are converted to SAM type
instances, not arbitrary expressions of FunctionN.
scala> val f = () => println("Faster!")
scala> val fasterRunnable: Runnable = f
<console>:12: error: type mismatch;
found : () => Unit
required: Runnable
59. 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
60. SCALA 2.12: PARAMETER INFERENCE
The 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, overloading
resolution selects the one with the Function1 argument
type.
61. SCALA 2.12: BREAKING CHANGES
Overloading resolution has been adapted to prefer methods
with Function-typed arguments over methods with
parameters 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 is
the only applicable.
In Scala 2.12, both methods are applicable, but most specific
alternative is picked.
62. SCALA 2.12: BREAKING CHANGES
SAM 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 lambda
expressions, not to arbitrary expressions with Scala
FunctionN types
val fun = () => 2 // Type Function0[Int]
val sam2: MySam = fun // Uses implicit conversion
sam2.i() // Returns 1