Practically Functional Daniel Spiewak whoami Author of Scala

  • Slides: 77
Download presentation
Practically Functional Daniel Spiewak

Practically Functional Daniel Spiewak

whoami �Author of Scala for Java Refugees and other articles on Scala and FP

whoami �Author of Scala for Java Refugees and other articles on Scala and FP �Former editor Javalobby / Eclipse. Zone �Engaged in academic research involving Scala DSLs and text parsing (Scala. Bison, GLL Combinators, Scala. QL)

Agenda �Define “functional programming” (sort of) �See some common elements of FP �Motivate why

Agenda �Define “functional programming” (sort of) �See some common elements of FP �Motivate why this stuff is useful in the real world (hopefully) �Show practical functional techniques and design patterns �Explain monads! �Hopefully pique your interest in learning and applying more of this

Definitions �Q: What is “functional programming”?

Definitions �Q: What is “functional programming”?

Definitions �Q: What is “functional programming”? ◦ A: Nobody knows!

Definitions �Q: What is “functional programming”? ◦ A: Nobody knows!

Definitions �Q: What is “purely-functional”?

Definitions �Q: What is “purely-functional”?

Definitions �Q: What is “purely-functional”? ◦ Everything is immutable (no variables)

Definitions �Q: What is “purely-functional”? ◦ Everything is immutable (no variables)

Definitions �Q: What is “purely-functional”? ◦ Everything is immutable (no variables) ◦ Absolutely no

Definitions �Q: What is “purely-functional”? ◦ Everything is immutable (no variables) ◦ Absolutely no side-effects println("Hello, World!")

Definitions �Q: What is “purely-functional”? ◦ Everything is immutable (no variables) ◦ Absolutely no

Definitions �Q: What is “purely-functional”? ◦ Everything is immutable (no variables) ◦ Absolutely no side-effects ◦ Referential transparency

Definitions �Q: ◦ ◦ What is “purely-functional”? Everything is immutable (no variables) Absolutely no

Definitions �Q: ◦ ◦ What is “purely-functional”? Everything is immutable (no variables) Absolutely no side-effects Referential transparency Bondage discipline?

Definitions �Scala is not purely-functional ◦ vars ◦ Mutable collections ◦ Uncontrolled side-effects (println)

Definitions �Scala is not purely-functional ◦ vars ◦ Mutable collections ◦ Uncontrolled side-effects (println)

Definitions �Scala is not purely-functional ◦ vars ◦ Mutable collections ◦ Uncontrolled side-effects (println)

Definitions �Scala is not purely-functional ◦ vars ◦ Mutable collections ◦ Uncontrolled side-effects (println) �Is Scala a “functional language”?

Functional Trademarks �Higher-order functions def foreach(f: String=>Unit) { f("What") f("is") f("going") f("on? ") }

Functional Trademarks �Higher-order functions def foreach(f: String=>Unit) { f("What") f("is") f("going") f("on? ") }

Functional Trademarks �Higher-order functions foreach { s => println(s) }

Functional Trademarks �Higher-order functions foreach { s => println(s) }

Functional Trademarks �Higher-order functions �Closures are anonymous functions ◦ Ruby, Groovy, Python; none of

Functional Trademarks �Higher-order functions �Closures are anonymous functions ◦ Ruby, Groovy, Python; none of these count! foreach(println)

Functional Trademarks �Higher-order functions �Closures are anonymous functions ◦ Ruby, Groovy, Python; none of

Functional Trademarks �Higher-order functions �Closures are anonymous functions ◦ Ruby, Groovy, Python; none of these count! �Singly-linked immutable lists (cons cells) val names = "Chris" : : "Joe" : : Nil val names 2 = "Daniel" : : names

Functional Trademarks �Higher-order functions �Closures are anonymous functions ◦ Ruby, Groovy, Python; none of

Functional Trademarks �Higher-order functions �Closures are anonymous functions ◦ Ruby, Groovy, Python; none of these count! �Singly-linked immutable lists (cons cells) �Usually some form of type-inference val me = "Daniel" // equivalent to. . . val me: String = "Daniel"

Functional Trademarks �Higher-order functions �Closures are anonymous functions ◦ Ruby, Groovy, Python; none of

Functional Trademarks �Higher-order functions �Closures are anonymous functions ◦ Ruby, Groovy, Python; none of these count! �Singly-linked immutable lists (cons cells) �Usually some form of type-inference foreach { s => println(s) }

Functional Trademarks �Higher-order functions �Closures are anonymous functions ◦ Ruby, Groovy, Python; none of

Functional Trademarks �Higher-order functions �Closures are anonymous functions ◦ Ruby, Groovy, Python; none of these count! �Singly-linked immutable lists (cons cells) �Usually some form of type-inference �Immutable by default (or encouraged) val me = "Daniel" var me = "Daniel"

What does this buy you? �Modularity (separation of concerns) �Understandability �No more “spooky action

What does this buy you? �Modularity (separation of concerns) �Understandability �No more “spooky action at a distance” �…

What does this buy you? public class Company { private List<Person> employees; public List<Person>

What does this buy you? public class Company { private List<Person> employees; public List<Person> get. Employees() { return employees; } public void add. Employee(Person p) { if (p. is. Alive()) { employees. add(p); } } }

What does this buy you? �Modularity (separation of concerns) �Understandability �No more “spooky action

What does this buy you? �Modularity (separation of concerns) �Understandability �No more “spooky action at a distance” �Flexible libraries (more on this later) �Syntactic power (internal DSLs)

What does this buy you? "vector" should { "store a single element" in {

What does this buy you? "vector" should { "store a single element" in { val prop = for. All { (i: Int, e: Int) => i >= 0 ==> { (vector(0) = e)(0) must. Equal e } } prop must pass } "implement length" in { val prop = for. All { list: List[Int] => val vec = Vector(list: _*) vec. length must. Equal list. length } prop must pass } }

Functional Idioms �Recursion instead of loops ◦ Scala helps with this by allowing methods

Functional Idioms �Recursion instead of loops ◦ Scala helps with this by allowing methods within methods

Functional Idioms �Recursion instead of loops ◦ Scala helps with this by allowing methods

Functional Idioms �Recursion instead of loops ◦ Scala helps with this by allowing methods within methods def factorial(n: Int) = { var back = 1 for (i <- 1 to n) { back *= i } back }

Functional Idioms �Recursion instead of loops ◦ Scala helps with this by allowing methods

Functional Idioms �Recursion instead of loops ◦ Scala helps with this by allowing methods within methods def factorial(n: Int): Int = { if (n == 1) 1 else n * factorial(n - 1) }

Functional Idioms �Recursion instead of loops ◦ Scala helps with this by allowing methods

Functional Idioms �Recursion instead of loops ◦ Scala helps with this by allowing methods within methods def factorial(n: Int) = { def loop(n: Int, acc: Int): Int = { if (n == 1) acc else loop(n - 1, acc * n) } loop(n, 1) }

Functional Idioms �Recursion instead of loops ◦ Scala helps with this by allowing methods

Functional Idioms �Recursion instead of loops ◦ Scala helps with this by allowing methods within methods �Higher-order recursion functions instead of

Functional Idioms �Recursion instead of loops ◦ Scala helps with this by allowing methods

Functional Idioms �Recursion instead of loops ◦ Scala helps with this by allowing methods within methods �Higher-order functions instead of recursion �Combinators instead of higher-order functions

Functional Idioms �Recursion instead of loops ◦ Scala helps with this by allowing methods

Functional Idioms �Recursion instead of loops ◦ Scala helps with this by allowing methods within methods �Higher-order functions instead of recursion �Combinators instead of higher-order functions �Monads!

Example #1 Retrieve structured, formatted data from across multiple. properties files and multiple keys

Example #1 Retrieve structured, formatted data from across multiple. properties files and multiple keys within those files. # daniel. properties # tim. properties name. first = Daniel name. last = Spiewak age = 21 name. first = Timothy name. last = Olmsted age = 22

Example #1 �Using loops

Example #1 �Using loops

def to. Int(s: String) = try { s. to. Int } catch { case

def to. Int(s: String) = try { s. to. Int } catch { case _ => null } // uninteresting and ugly def read. File(file: String): Map[String, String] = { import collection. jcl. Hashtable try { val is = new Buffered. Input. Stream(new File. Input. Stream(file)) val p = new Properties p. load(is) is. close() new Hashtable(p). as. Instance. Of[Hashtable[String, String]] } catch { case _ => null } }

import collection. mutable. List. Buffer def read. People(files: List[String]): List[Person] = { val back

import collection. mutable. List. Buffer def read. People(files: List[String]): List[Person] = { val back = new List. Buffer[Person] for (file <- files) { val props = read. File(file) if (props != null) { if (props. contains("name. first") && props. contains("name. last") && props. contains("age")) { val age = to. Int(props("age")) if (age != null) back += new Person(props("name. first"), props("name. last"), age) } } } back. to. List }

Example #1 �Using loops �Recursive

Example #1 �Using loops �Recursive

def read. People(files: List[String]): List[Person] = files match { case file : : tail

def read. People(files: List[String]): List[Person] = files match { case file : : tail => { val props = read. File(file) val back = if (props != null) { if (props. contains("name. first") && props. contains("name. last") && props. contains("age")) { val age = to. Int(props("age")) if (age != null) new Person(props("name. first"), props("name. last"), age) else null } else null if (back != null) back : : read. People(tail) else read. People(tail) } case Nil => Nil }

Example #1 �Loops �Recursion �Higher-order functions

Example #1 �Loops �Recursion �Higher-order functions

def read. People(files: List[String]): List[Person] = { files. fold. Right(List[String]()) { (file, people) =>

def read. People(files: List[String]): List[Person] = { files. fold. Right(List[String]()) { (file, people) => val props = read. File(file) val back = if (props != null) { if (props. contains("name. first") && props. contains("name. last") && props. contains("age")) { val age = to. Int(props("age")) if (age != null) new Person(props("name. first"), props("name. last"), age) else null } else null if (back != null) back : : people else people } }

Example #1 �Loops �Recursion �Higher-order �… �Monads! functions

Example #1 �Loops �Recursion �Higher-order �… �Monads! functions

def to. Int(s: String) = try { Some(s. to. Int) } catch { case

def to. Int(s: String) = try { Some(s. to. Int) } catch { case _ => None } // uninteresting and ugly def read. File(file: String): Option[Map[String, String]] = { import collection. jcl. Hashtable try { val is = new Buffered. Input. Stream(new File. Input. Stream(file)) val p = new Properties p. load(is) is. close() Some(new Hashtable(p). as. Instance. Of[Hashtable[String, String]]) } catch { case _ => None } }

def read. People(files: List[String]): List[Person] = { for { file <- files props <-

def read. People(files: List[String]): List[Person] = { for { file <- files props <- read. File(file) first. Name <- props get "name. first" last. Name <- props get "name. last" age. String <- props get "age" age <- to. Int(age. String) } yield new Person(first. Name, last. Name, age) }

Example #1 �Loops �Recursion �Higher-order �Combinators �Monads! functions

Example #1 �Loops �Recursion �Higher-order �Combinators �Monads! functions

def read. People(files: List[String]) = { import Function. _ files flat. Map read. File

def read. People(files: List[String]) = { import Function. _ files flat. Map read. File flat. Map { props => val f. Names = props get "name. first" val names = f. Names flat. Map { (_, props get "name. last") } val data = names flat. Map { case (fn, ln) => (fn, ln, props get "age" map to. Int) } data map tupled(new Person _) } }

What did we just see? �fold. Left / fold. Right ◦ Catamorphisms ◦ Use

What did we just see? �fold. Left / fold. Right ◦ Catamorphisms ◦ Use when you want to reduce all of the elements of a collection into a single result ◦ Capable of almost anything!

What did we just see? �fold. Left / fold. Right def sum(nums: List[Int]) =

What did we just see? �fold. Left / fold. Right def sum(nums: List[Int]) = { nums. fold. Left(0) { (x, y) => x + y } }

What did we just see? �fold. Left / fold. Right def sum(nums: List[Int]) =

What did we just see? �fold. Left / fold. Right def sum(nums: List[Int]) = { nums. fold. Left(0) { _ + _ } }

What did we just see? �fold. Left / fold. Right def sum(nums: List[Int]) =

What did we just see? �fold. Left / fold. Right def sum(nums: List[Int]) = { (0 /: nums) { _ + _ } }

What did we just see? �fold. Left / fold. Right �map ◦ Use when

What did we just see? �fold. Left / fold. Right �map ◦ Use when you want to transform every element of a collection, leaving the results in the corresponding location within a new collection

What did we just see? �fold. Left / fold. Right �map val nums =

What did we just see? �fold. Left / fold. Right �map val nums = List("1", "2", "3", "4", "5") nums map { str => str. to. Int }

What did we just see? �fold. Left / fold. Right �map val nums =

What did we just see? �fold. Left / fold. Right �map val nums = List("1", "2", "3", "4", "5") nums map { _. to. Int }

What did we just see? �fold. Left / fold. Right �map �flat. Map (has

What did we just see? �fold. Left / fold. Right �map �flat. Map (has two meanings) ◦ Collections: Use when you want to transform every element optionally ◦ Monads: Should have really been called “bind” (or >>=). More later…

What did we just see? �fold. Left / fold. Right �map �flat. Map (has

What did we just see? �fold. Left / fold. Right �map �flat. Map (has two meanings) def to. Char. Array(arr: Array[String]) = { arr flat. Map { _. to. Char. Array } } to. Char. Array("Daniel", "Spiewak")) // => Array('D', 'a', 'n', 'i', 'e', 'l', 'S', 'p', 'i', 'e', 'w', 'a', 'k')

Other Common Util Methods �filter (used in for-comprehensions) val nums = List(1, 2, 3,

Other Common Util Methods �filter (used in for-comprehensions) val nums = List(1, 2, 3, 4, 5) nums filter { _ % 2 == 0 }

Other Common Util Methods �filter (used in for-comprehensions) val nums = List(1, 2, 3,

Other Common Util Methods �filter (used in for-comprehensions) val nums = List(1, 2, 3, 4, 5) nums filter (0 == 2 %)

Other Common Util Methods �filter (used in for-comprehensions) �zip / zip. With. Index ◦

Other Common Util Methods �filter (used in for-comprehensions) �zip / zip. With. Index ◦ zip. With (not available pre-2. 8. 0) val evens = List(2, 4, 6) val odds = List(1, 3, 5) evens zip odds // => List((1, 2), (3, 4), (5, 6))

Other Common Util Methods �filter (used in for-comprehensions) �zip / zip. With. Index ◦

Other Common Util Methods �filter (used in for-comprehensions) �zip / zip. With. Index ◦ zip. With (not available pre-2. 8. 0) �forall and exists val nums = List(1, 2, 3, 4, 5) nums forall { _ % 2 == 0 } // => false

Other Common Util Methods �filter (used in for-comprehensions) �zip / zip. With. Index ◦

Other Common Util Methods �filter (used in for-comprehensions) �zip / zip. With. Index ◦ zip. With (not available pre-2. 8. 0) �forall and exists val nums = List(1, 2, 3, 4, 5) nums exists { _ % 2 == 0 } // => true

Other Common Util Methods �filter (used in for-comprehensions) �zip / zip. With. Index ◦

Other Common Util Methods �filter (used in for-comprehensions) �zip / zip. With. Index ◦ zip. With (not available pre-2. 8. 0) �forall and exists �take and drop val nums = List(5, 4, 3, 2, 1) nums take 2 // => List(5, 4)

Other Common Util Methods �filter (used in for-comprehensions) �zip / zip. With. Index ◦

Other Common Util Methods �filter (used in for-comprehensions) �zip / zip. With. Index ◦ zip. With (not available pre-2. 8. 0) �forall and exists �take and drop val nums = List(5, 4, 3, 2, 1) nums drop 2 // => List(3, 2, 1)

Other Common Util Methods �filter (used in for-comprehensions) �zip / zip. With. Index ◦

Other Common Util Methods �filter (used in for-comprehensions) �zip / zip. With. Index ◦ zip. With (not available pre-2. 8. 0) �forall and exists �take and drop �foreach val names = List("Daniel", "Chris") names foreach println

Example #2 Comparing the prefix of a List[Char] to a given string. List[Char] String

Example #2 Comparing the prefix of a List[Char] to a given string. List[Char] String Result List('d', 'a', 'n', 'i', 'e', 'l') "dan" true List('d', 'a', 'n', 'i', 'e', 'l') "iel" false List('t', 'e', 's', 't') "testing" false List('t', 'e', 's', 't') "test" true

def is. Prefix(chars: List[Char], str: String) = { if (chars. length. Compare(str. length) <

def is. Prefix(chars: List[Char], str: String) = { if (chars. length. Compare(str. length) < 0) { false } else { val trunc = chars take str. length trunc. zip. With. Index forall { case (c, i) => c == str(i) } } }

More About Combinators �The “Essence of Functional Programming” �Combine simple things to solve complex

More About Combinators �The “Essence of Functional Programming” �Combine simple things to solve complex problems �Very high level �Think about Lego™ bricks

More About Combinators �The best example: Parser Combinators def expr: Parser[Int] = ( num

More About Combinators �The best example: Parser Combinators def expr: Parser[Int] = ( num ~ "+" ~ expr ^^ { case (x, _, y) => x + y } | num ~ "-" ~ expr ^^ { case (x, _, y) => x - y } | num ) def num = """d+""". r ^^ { _. to. Int }

More About Combinators �The best example: Parser Combinators def expr: Parser[Int] = ( num

More About Combinators �The best example: Parser Combinators def expr: Parser[Int] = ( num ~ "+" ~ expr ^^ { case (x, _, y) => x + y } | num ~ "-" ~ expr ^^ { case (x, _, y) => x - y } | num ) def num = """d+""". r ^^ { _. to. Int } expr("12 + 7 - 4") // => Success(15) expr("42") // => Success(42)

More About Combinators �Three Types of Combinators ◦ Sequential (first a, then b) a

More About Combinators �Three Types of Combinators ◦ Sequential (first a, then b) a ~ b

More About Combinators �Three Types of Combinators ◦ Sequential (first a, then b) ◦

More About Combinators �Three Types of Combinators ◦ Sequential (first a, then b) ◦ Disjunctive (either a, or b) a | b

More About Combinators �Three Types of Combinators ◦ Sequential (first a, then b) ◦

More About Combinators �Three Types of Combinators ◦ Sequential (first a, then b) ◦ Disjunctive (either a, or b) ◦ Literal (exactly foo) "foo"

More About Combinators �Three Types of Combinators ◦ Sequential (first a, then b) ◦

More About Combinators �Three Types of Combinators ◦ Sequential (first a, then b) ◦ Disjunctive (either a, or b) ◦ Literal (exactly foo) �Note: Our example uses a regular expression parser, but that is only a generalization of the literal parser

More About Combinators �Seldom created, often used �Good for problems which split into smaller

More About Combinators �Seldom created, often used �Good for problems which split into smaller sub-problems

March of the Monads �Monads are not scary

March of the Monads �Monads are not scary

March of the Monads �Monads are not scary �Monad explanations are scary

March of the Monads �Monads are not scary �Monad explanations are scary

March of the Monads �Monads are little containers for encapsulating something ◦ What the

March of the Monads �Monads are little containers for encapsulating something ◦ What the “something” is depends on the monad �An instance of a monad can be “bound” together with another instance of that monad �Most combinators are monads

March of the Monads �All monads have ◦ A type constructor class Option[A] {

March of the Monads �All monads have ◦ A type constructor class Option[A] { … } ◦ A single-argument constructor new Some("one to watch over me") ◦ A flat. Map method which behaves properly a flat. Map { v => v. next }

March of the Monads

March of the Monads

March of the Monads �Option ◦ This is what the Groovy folks really wanted

March of the Monads �Option ◦ This is what the Groovy folks really wanted when they designed the “Elvis Operator” �Parser ◦ Sequential parser is really two bound parsers ◦ Disjunctive parser uses an optional monadic function: or. Else ◦ Literal parser is the one-argument constructor �Function 1 (sort of) ◦ We could say that function composition is

Learn More �Read my blog! : -) ◦ http: //www. codecommit. com/blog �Some ◦

Learn More �Read my blog! : -) ◦ http: //www. codecommit. com/blog �Some ◦ ◦ �A better sources… http: //apocalisp. wordpress. com/ http: //michid. wordpress. com/ http: //joelneely. wordpress. com/ http: //scala-blogs. org really good paper… ◦ Monadic Parser Combinators (1996; Hutton, Meijer)