Upgrade to Pro — share decks privately, control downloads, hide ads and more …

All About ... Functions

Michal Bigos
November 27, 2016

All About ... Functions

Talk from "All About" series at Hamburg Scala UG.

Michal Bigos

November 27, 2016
Tweet

More Decks by Michal Bigos

Other Decks in Technology

Transcript

  1. AGENDA Basic syntax Arguments and parameters Generic functions Function as

    values Closures Partial application, curring and composition Partial Functions Syntactic sugar Changes in Scala 2.12
  2. 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.
  3. 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.
  4. 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)
  5. 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)
  6. 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") )
  7. 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.
  8. 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.
  9. 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
  10. 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 = ...
  11. 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.
  12. 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.
  13. 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].
  14. 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.
  15. 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
  16. 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(")"))
  17. 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.
  18. 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.
  19. 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]):
  20. 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
  21. 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
  22. 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.
  23. 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"
  24. 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.
  25. 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 ().
  26. 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.
  27. 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.
  28. 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.
  29. 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.
  30. 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.
  31. 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.
  32. 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
  33. 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.
  34. 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.
  35. 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.
  36. 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
  37. 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
  38. 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
  39. 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>
  40. 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, and destructuring for free.
  41. 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"))
  42. 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.
  43. 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
  44. 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.
  45. 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
  46. 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.
  47. 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()
  48. 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
  49. 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
  50. 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.
  51. 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.
  52. 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