Download presentation

Presentation is loading. Please wait.

Published bySimon Newnam Modified over 2 years ago

1
More about functions Plus a few random things

2
2 Tail recursion A function is said to be tail recursive if the recursive call is the very last thing it does, in any recursive branch Example #1: def collatz(n: Int): Int = { if (n == 1) 1 else if (n % 2 == 0) collatz(n / 2) else collatz(3 * n + 1) } There is no recursion if n == 1 In each other case, the last operation is a recursive call A good compiler can replace tail recursion with a loop def collatz(n: Int): Int = { var nn = n while (nn != 1) { if (nn % 2 == 0) nn = nn / 2 else nn = 3 * nn + 1 } nn } This is desirable because loops don’t cause the stack to grow

3
3 Factorial Here’s everybody’s first recursive function: def factorial(n: Long): Long = { if (n == 0 || n == 1) 1 else n * factorial(n - 1) } This is not tail recursive, because there is a multiplication that is performed after the recursive call Can the factorial function be rewritten to be tail recursive? Yes it can, and the technique for doing so can be applied to a lot of other functions, so it’s worth learning I’ll leave this as an exercise, since if you figure it out yourself you are much more likely to remember how to do it Here are three hints, which you can uncover one at at time if you get stuck (the text is white; select it and change the color) 1.Use two functions; the second one should do all the recursive work 2.The second function should have two parameters 3.One of the parameters should be 1

4
4 Currying Here’s a function to find the hypotenuse of a right triangle: def hyp(a: Double, b: Double) = sqrt(a * a + b * b) Suppose you were to call it this way: val h = hyp(5.0, 12.0) You can think of this as the compiler substituting a value for each variable Suppose we substitute 5.0 for a first: sqrt(a * a + b * b) becomes sqrt(5.0 * 5.0 + b * b) And afterwards substitute 12.0 for b : sqrt(5.0 * 5.0 + b * b) becomes sqrt(5.0 * 5.0 + 12.0 * 12.0) which is then calculated (giving 13.0 ) Now suppose we stop after step 1 What we have after step 1 is a partially applied function Currying is a way to make partially applied functions

5
5 Currying in Scala Instead of giving our hyp function one parameter list, we can give it two parameter lists: scala> def hyp(a: Double)(b: Double) = sqrt(a * a + b * b) hyp: (a: Double)(b: Double)Double Now when we call it, we have to give it two argument lists, not just one list with two arguments: scala> hyp(5)(12) res2: Double = 13.0 We can stop with just the first list (but we need to use an underscore to tell Scala we know we left something out) scala> val h5 = hyp(5) _ h5: (Double) => Double = scala> h5(12) res3: Double = 13.0 With more parameters, we can break up the parameter list any way we like def foo(a: Int)(b: Int, c: String)(d: Double) = {...}

6
6 Another way to curry Here’s a function written in the usual way: scala> def avg(a: Double, b: Double) = (a + b) /2.0 avg: (a: Double,b: Double)Double Here we’re calling it in the usual way: scala> avg(5, 12) res6: Double = 8.5 Here we’re calling it with the first argument specified ( 5 ) but the second argument unspecified ( _ ) scala> val a5 = avg(5, _: Double) a5: (Double) => Double = Notice that we had to specify the type of the omitted parameter Also pay careful attention to the type of the result Now let’s call our new, curried function: scala> a5(12) res7: Double = 8.5

7
7 Call-by-name parameters When using a function as a argument, the function parameter is used only when requested: Example: def foo(f: Int => Int) { println("In foo with " + f(10)) } foo(factorial) // prints "In foo with 3628800" Other types of parameters are first evaluated, then their values are passed into the function: Example: def foo(n: Long) { println(n} } foo(factorial(10)) What is passed into the function is the number 3628800 By using => as the first part of the type in a function definition, the function will treat a parameter as a function Example: def foo(f: => Int) { println("In foo with " + f(10)) } foo(factorial) // prints "In foo with 3628800" This is an example of call-by-name

8
8 Lazy evaluation Lazy evaluation is a technique whereby a computation is not actually performed until and unless the result is needed lazy val xxx = someLongAndExpensiveComputation... if (we decide that we need xxx) { use xxx // The computation is done here } Ranges in Scala are lazy scala> println((1 to Int.MaxValue) take 3) Range(1, 2, 3) The Stream class supports lazy evaluation, and therefore allows “infinite” data structures (example from Programming Scala by Wampler & Payne) scala> lazy val fib: Stream[Int] = Stream.cons(0, Stream.cons(1, fib.zip(fib.tail).map(p => p._1 + p._2))) fib: Stream[Int] = cons is like :: scala> fib.take(10).print 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, empty

9
9 Building control structures When calling a curried function, you can replace the last set of parentheses with braces scala> hyp(5) { 12 } res27: Double = 13.0 You can only put expressions inside parentheses, but you can put multiple “statements” inside braces This, plus currying, plus lazy evaluation, allows you to build your own control structures scala> def repeat(n: Int)(body: => Unit) { | for (i Unit)Unit scala> repeat(3) { | println("Hello") | } Hello Hello Hello

10
XML XML is built into Scala as a data type—not just as something supported by some package scala> val name = "Dave" name: java.lang.String = Dave scala> val x = Hello, {name}! x: scala.xml.Elem = Hello, Dave! 10

11
DSL Scala has superb support for Domain Specific Languages A parser combinator is a function that takes only other functions as parameters and returns only functions BNF is essentially built into Scala as a combinator library Example: Arithmetic expressions (adapted from Odersky, pp. 620-621) BNF: ::= { “+” | “-” } ::= { “*” | “/” } ::= | “(” “)” Scala ( ~ is used to indicate one thing follows another in sequence): import scala.util.parsing.combinator._ class Arith extends JavaTokenParsers { def expr: Parser[Any] = term ~ rep("+" ~ term | "-" ~ term) def term: Parser[Any] = factor ~ rep("*" ~ factor | "/" ~ factor) def factor: Parser[Any] = floatingPointNumber | "(" ~ expr ~ ")" } 11

12
The End 12

Similar presentations

OK

Python uses boolean variables to evaluate conditions. The boolean values True and False are returned when an expression is compared or evaluated.

Python uses boolean variables to evaluate conditions. The boolean values True and False are returned when an expression is compared or evaluated.

© 2017 SlidePlayer.com Inc.

All rights reserved.

Ads by Google