Post on 18-Jul-2020
/ 84
CS320 - Scala Seminar
Introduction to Functional Programming
Jaemin Honghjm0901@gmail.com
September 5, 2018
!1
/ 84
What is Scala?• “Scalable Language”
• Object-oriented
• Functional
!2
/ 84!3 GitHub Octoverse 2017
/ 84!4 Stack Overflow Developer Survey Results 2018
/ 84
Who use Scala?• PLRG@KAIST
!5 github.com/sukyoung/safe
/ 84
Who use Scala?• Compiler: Scala, Fortress
!6 github.com/scala/scala, github.com/lampepfl/dotty
/ 84
Who use Scala?• Akka
!7 akka.io, github.com/akka/akka
/ 84
Who use Scala?• Apache Spark
!8 spark.apache.org, github.com/apache/spark
/ 84
Who use Scala?• Play Framework
!9 www.playframework.com, github.com/playframework/playframework
/ 84
Functional Languages in Industry• OCaml
!10fbinfer.com, github.com/facebook/infer
flow.org, github.com/facebook/flow
/ 84
Functional Languages in Industry• OCaml
• Docker
• Bloomberg
• Jane Street
• …
!11 ocaml.org/learn/companies.html
/ 84
Functional Languages in Industry• Haskell
• Microsoft
• Nvidia
• …
!12 wiki.haskell.org/Haskell_in_industry
/ 84
Functional Languages in Industry• Erlang / Elixir
• Goldman Sachs
• …
!13 codesync.global/media/successful-companies-using-elixir-and-erlang
/ 84
Why Functional?• Good to reason and to verify programs.
• Easy to parallelize.
• Make high-level abstraction available.
• Can model and treat complex structures.
!14
/ 84
Why Functional?• Good to reason and to verify programs.
• Easy to parallelize.
• Make high-level abstraction available.
• Can model and treat complex structures.
!15
Immutability
Higher-order functions
ADT & Pattern Matching
/ 84
WAE{with x {+ 1 2}
{+ x x}}
{with x {+ 1 2}
{with y 3
{- x y}}}
!16
/ 84
Imperative Stylex = 1
y = 0
if (x == 0)
y = y + 1
else
y = y - 1
!17
/ 84
Declaring Variables in Scalascala> val x = 1
x: Int = 1
scala> x = 2
<console>:12: error: reassignment to val
x = 2
scala> var x = 1
x: Int = 1
scala> x = 2
x: Int = 2
!18
/ 84
Why Immutable?• Easier to reason about
• No need for a defensive copy
• Safe to access concurrently
• Safe hash key
!19 Martin Odersky et al., Programming in Scala 3rd Ed., Artima
/ 84
Why Immutable?• Easier to reason about
val x = 1
…
…
…
f(x)
!20
var x = 1
…
…
…
f(x) // is x still 1?
/ 84
Why Immutable?• Easier to reason about
val x = List(1, 2)
…
f(x)
…
x
!21
val x = ListBuffer(1, 2)
…
f(x) // is x still [1, 2]?
…
x // is x still [1, 2]?
/ 84
Why Immutable?• Easier to reason about
def f(x: Int) = {
val y = g(x)
h(y)
}
!22
def f(x: Int) = {
val y = g(x, z)
// where and what is z?
h(y, w)
// where and what is w?
}
/ 84
Why Immutable?• No need for a defensive copy
val x = List(1, 2)
f(x)
…
x
!23
val x = ListBuffer(1, 2)
val y = x.clone
f(x) // x can be modified
…
y // want to use original x
/ 84
Why Immutable?• Safe to access concurrently
val x = ListBuffer(1, 2)
<Thread 1>
x += 3
f(y)
!24
<Thread 2>
g(x)
/ 84
Why Immutable?• Safe to access concurrently
val x = ListBuffer(1, 2)
<Thread 1>
// may need a lock here
x += 3
f(y)
!25
<Thread 2>
// may need a lock here
g(x)
/ 84
Why Immutable?• Safe to access concurrently
val x = List(1, 2)
<Thread 1>
val y = 0 :: x
f(y)
!26
<Thread 2>
g(x)
/ 84
Why Immutable?• Safe hash key
val x = List(0)
val y = Set(x, …)
y.contains(x) // true
!27
val x = ListBuffer(0)
val y = Set(x, …)
y.contains(x) // true
x += 1
y.contains(x) // false
/ 84
Why Immutable?• Safe hash key
val x = List(0)
val y = Map(x -> 0, …)
y(x) // 0
!28
val x = ListBuffer(0)
val y = Map(x -> 0, …)
y(x) // 0
x += 1
y(x) // NoSuchElementException
/ 84
Coding without Mutation• If nothing is changed, loops will not terminate.
• Recursion!
• Call the same function again with different arguments.
• Recursion reflects mathematical definition.
!29
/ 84
Example: Factorial• Define method factorial, which
• takes an integer, and
• returns the factorial of the integer.
• If an input is negative, then return 1.
!30
/ 84
Example: Factorialdef factorial(n: Int): Int = {
var i = 1
var res = 1
while (i <= n) {
res *= i
i += 1
}
res
}
!31
/ 84
Example: Factorialdef factorial(n: Int): Int = {
var res = 1
for (i <- 1 to n)
res *= i
res
}
!32
/ 84
Example: Factorialdef factorial(n: Int): Int = (1 to n).product
!33
/ 84
Example: Factorialdef factorial(n: Int): Int =
!34
/ 84
Defining List of Integers• A list is a finite sequence of integers
!35
0 1 2 3
0 1 2 3 4 5 6
/ 84
• Inductively, a list is
• the empty list, or
• a pair of an integer and a list
Defining List of Integers
!36
Empty List
Empty List
Empty List
1
0 1
/ 84
Defining List of Integerstrait List
case object Nil extends List
case class Cons(head: Int, tail: List) extends List
Nil
Cons(1, Nil)
!37
Empty List
Empty List1
/ 84
Singleton Objectcase object Nil extends List
vs
case class Nil() extends List
• Every empty list is equal.
!38
/ 84
Singleton Objectcase object Nil extends List
is something like
case class $Nil() extends List
val Nil = $Nil()
!39
/ 84
Creating ListsNil
Cons(0, Nil)
Cons(0, Cons(1, Nil))
Cons(0, Cons(1, Cons(2, Nil)))
Cons(0, Cons(1, Cons(2, Cons(3, Nil))))
Cons(0, Cons(1, Cons(2, Cons(3, Cons(4, Nil))))) …
!40
/ 84
Creating Listsdef List(elems: Int*): List =
if (elems.isEmpty) Nil
else Cons(elems.head, List(elems.tail: _*))
List()
List(0, 1)
List(0, 1, 2, 3)
!41
/ 84
Playing with Lists• Define method inc1, which
• takes a list, and
• returns a list, whose elements are increased by 1.
!42
/ 84
Playing with Listsdef inc1(l: List): List = l match {
case Nil =>
case Cons(h, t) =>
}
!43
/ 84
Playing with Lists• Generalize!
• Define method incBy, which
• takes a list and integer n, and
• returns a list, whose elements are increased by n.
!44
/ 84
Playing with Listsdef incBy(l: List, n: Int): List = l match {
case Nil =>
case Cons(h, t) =>
}
!45
/ 84
Playing with Lists• Define method square, which
• takes a list, and
• returns a list, whose elements are squared.
!46
/ 84
Playing with Listsdef square(l: List): List = l match {
case Nil =>
case Cons(h, t) =>
}
!47
/ 84
Playing with Lists• Define method odd, which
• takes a list, and
• returns a list, whose elements are odd.
!48
/ 84
Playing with Listsdef odd(l: List): List = l match {
case Nil =>
case Cons(h, t) =>
}
!49
/ 84
Playing with Lists• Define method positive, which
• takes a list, and
• returns a list, whose elements are positive.
!50
/ 84
Playing with Listsdef positive(l: List): List = l match {
case Nil =>
case Cons(h, t) =>
}
!51
/ 84
Playing with Lists• Generalize!
• Define method gt, which
• takes a list and integer n, and
• returns a list, whose elements are greater than n.
!52
/ 84
Playing with Listsdef gt(l: List, n: Int): List = l match {
case Nil =>
case Cons(h, t) =>
}
!53
/ 84
Playing with Lists• Define method length, which
• takes a list, and
• returns the length of the list.
!54
/ 84
Playing with Listsdef length(l: List): Int = l match {
case Nil =>
case Cons(h, t) =>
}
!55
/ 84
Playing with Lists• Define method sum, which
• takes a list, and
• returns the sum of the integers in the list.
!56
/ 84
Playing with Listsdef sum(l: List): Int = l match {
case Nil =>
case Cons(h, t) =>
}
!57
/ 84
Playing with Lists• Define method product, which
• takes a list, and
• returns the product of the integers in the list.
!58
/ 84
Playing with Listsdef product(l: List): Int = l match {
case Nil =>
case Cons(h, t) =>
}
!59
/ 84
Playing with Lists• Define method addBack, which
• takes a list and an integer, and
• returns the same list but the integer is added at the back of the list.
!60
/ 84
Playing with Listsdef addBack(l: List, n: Int): List = l match {
case Nil =>
case Cons(h, t) =>
}
!61
/ 84
Playing with Lists• Define method append, which
• takes two lists, and
• returns the concatenated list.
!62
/ 84
Playing with Listsdef append(l: List, l1: List): List = l match {
case Nil =>
case Cons(h, t) =>
}
!63
/ 84
Optimizing Recursion• Cons of recursion
• Method call overhead: both time and space
• Stack overflow
!64
/ 84
Optimizing Recursionscala> factorial(1000000)
java.lang.StackOverflowError
at .factorial
at .factorial
at .factorial
at .factorial
at .factorial
!65
/ 84
Tail Recursion• A method is tail recursive
• if it ends with
• returning a value, or
• calling itself
• Compiler converts tail recursion to loop automatically.
!66
/ 84
Tail Recursionfactorial(4)
4 * factorial(3)
4 * 3 * factorial(2)
4 * 3 * 2 * factorial(1)
4 * 3 * 2 * 1
4 * 3 * 2
4 * 6
24
!67
/ 84
Tail Recursionfactorial(4)
factorial(3, intermediate result = 4)
factorial(2, intermediate result = 12)
factorial(1, intermediate result = 24)
24
!68
/ 84
Tail Recursiondef factorial(n: Int, inter: Int): Int =
if (n <= 1)
else
!69
/ 84
Tail Recursionscala> factorial(1000)
res3: BigInt = 402387260077093773543702433923003985719374864210714632543799910…
scala> factorial(10000)
res4: BigInt = 284625968091705451890641321211986889014805140170279923079417999…
scala> factorial(100000)
res5: BigInt = 282422940796034787429342157802453551847749492609122485057891808…
!70
/ 84
Tail Recursioninc1([1, 2, 3])
2 :: inc1([2, 3])
2 :: 3 :: inc1([3])
2 :: 3 :: 4 :: inc1([])
2 :: 3 :: 4 :: []
2 :: 3 :: [4]
2 :: [3, 4]
[2, 3, 4]
!71
/ 84
Tail Recursioninc1([1, 2, 3])
inc1([2, 3], intermediate result = [2])
inc1([3], intermediate result = [2, 3])
inc1([], intermediate result = [2, 3, 4])
[2, 3, 4]
!72
/ 84
Tail Recursiondef inc1(l: List): List = {
@tailrec def aux(l: List, inter: List): List = l match {
case Nil =>
case Cons(h, t) =>
}
aux(l, )
}
!73
/ 84
Tail Recursionlength([1, 2, 3])
1 + length([2, 3])
1 + 1 + length([3])
1 + 1 + 1 + length([])
1 + 1 + 1 + 0
1 + 1 + 1
1 + 2
3
!74
/ 84
Tail Recursionlength([1, 2, 3])
length([2, 3], intermediate result = 1)
length([3], intermediate result = 2)
length([], intermediate result = 3)
3
!75
/ 84
Tail Recursiondef length(l: List): Int = {
@tailrec def aux(l: List, inter: Int): Int = l match {
case Nil =>
case Cons(h, t) =>
}
aux(l, )
}
!76
/ 84
Tail Recursiondef sum(l: List): Int = {
@tailrec def aux(l: List, inter: Int): Int = l match {
case Nil =>
case Cons(h, t) =>
}
aux(l, )
}
!77
/ 84
Tail Recursiondef product(l: List): Int = {
@tailrec def aux(l: List, inter: Int): Int = l match {
case Nil =>
case Cons(h, t) =>
}
aux(l, )
}
!78
/ 84
Tail Recursionreverse([1, 2, 3])
reverse([2, 3], intermediate result = [1])
reverse([3], intermediate result = [2, 1])
reverse([], intermediate result = [3, 2, 1])
[3, 2, 1]
!79
/ 84
Tail Recursiondef reverse(l: List): List = {
@tailrec def aux(l: List, inter: List): List = l match {
case Nil =>
case Cons(h, t) =>
}
aux(l, )
}
!80
/ 84
Tail Recursioninc1([3, 2, 1])
inc1([2, 1], intermediate result = [4])
inc1([1], intermediate result = [3, 4])
inc1([], intermediate result = [2, 3, 4])
[2, 3, 4]
!81
/ 84
Tail Recursioninc1([1, 2, 3])
inc1([2, 3], intermediate result = [2])
inc1([3], intermediate result = [2, 3])
inc1([], intermediate result = [2, 3, 4])
[2, 3, 4]
!82
/ 84
Tail Recursiondef inc1(l: List): List = {
@tailrec def aux(l: List, inter: List): List = l match {
case Nil =>
case Cons(h, t) =>
}
aux(, )
}
!83
/ 84
Summary• Functional programming is used actively in industry.
• Immutability is one of the key properties of functional programming.
• Coding without mutation is beneficial.
• Use recursion instead of loop.
• To optimize recursive methods, write methods tail recursively.
!84