all about ... functions

63
ALL ABOUT... FUNCTIONS Created by Michal Bigos (everywhere) @teliatko

Upload: michal-bigos

Post on 11-Jan-2017

112 views

Category:

Technology


0 download

TRANSCRIPT

ALL ABOUT...

FUNCTIONSCreated by Michal Bigos

(everywhere)@teliatko

AGENDABasic syntaxArguments and parametersGeneric functionsFunction as valuesClosuresPartial application, curring and compositionPartial FunctionsSyntactic sugarChanges in Scala 2.12

BASIC SYNTAX

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)

ARGUMENTS & PARAMETERS

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

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 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.

CLOSURES

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 APPLICATION, CURRYINGAND COMPOSITION

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

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

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.

CHANGES IN SCALA 2.12Taken from official .release notes

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

THANK YOU