770 likes | 940 Views
Practically Functional. Daniel Spiewak. whoami. Author of Scala for Java Refugees and other articles on Scala and FP Former editor Javalobby / EclipseZone Engaged in academic research involving Scala DSLs and text parsing ( ScalaBison , GLL Combinators , ScalaQL ). Agenda.
E N D
Practically Functional Daniel Spiewak
whoami • Author of Scala for Java Refugees and other articles on Scala and FP • Former editor Javalobby / EclipseZone • Engaged in academic research involving Scala DSLs and text parsing (ScalaBison, GLL Combinators, ScalaQL)
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 stuff
Definitions • Q: What is “functional programming”?
Definitions • Q: What is “functional programming”? • A: Nobody knows!
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) • Absolutely no side-effects println("Hello, World!")
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 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) • Is Scala a “functional language”?
Functional Trademarks • Higher-order functions defforeach(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 • 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 these count! • Singly-linked immutable lists (cons cells) val names = "Chris"::"Joe"::Nil val names2 = "Daniel":: names
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 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 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 at a distance” • …
What does this buy you? public class Company { private List<Person> employees; public List<Person> getEmployees() { return employees; } public void addEmployee(Person p) { if (p.isAlive()) { employees.add(p); } } }
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 { val prop = forAll { (i: Int, e: Int) => i>=0==> { (vector(0) = e)(0) mustEqual e } } prop must pass } "implement length"in { val prop = forAll { list: List[Int] => valvec = Vector(list:_*) vec.lengthmustEquallist.length } prop must pass } }
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 within methods deffactorial(n: Int) = { var back = 1 for (i <- 1to n) { back *=i } back }
Functional Idioms • Recursion instead of loops • Scala helps with this by allowing methods within methods deffactorial(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 within methods deffactorial(n: Int) = { defloop(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 within methods • Higher-order functions instead of recursion
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 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 within those files. # daniel.properties name.first = Daniel name.last = Spiewak age = 21 # tim.properties name.first = Timothy name.last = Olmsted age = 22
Example #1 • Using loops
deftoInt(s: String) = try { s.toInt } catch { case _ => null } // uninteresting and ugly defreadFile(file: String): Map[String, String] = { importcollection.jcl.Hashtable try { val is = newBufferedInputStream(new FileInputStream(file)) val p = new Properties p.load(is) is.close() newHashtable(p).asInstanceOf[Hashtable[String, String]] } catch { case _ => null } }
importcollection.mutable.ListBuffer defreadPeople(files: List[String]): List[Person] = { val back = newListBuffer[Person] for (file <- files) { val props = readFile(file) if (props !=null) { if (props.contains("name.first") && props.contains("name.last") && props.contains("age")) { val age = toInt(props("age")) if (age !=null) back +=new Person(props("name.first"), props("name.last"), age) } } } back.toList }
Example #1 • Using loops • Recursive
defreadPeople(files: List[String]): List[Person] = files match { case file :: tail => { val props = readFile(file) val back = if (props !=null) { if (props.contains("name.first") && props.contains("name.last") && props.contains("age")) { val age = toInt(props("age")) if (age !=null) new Person(props("name.first"), props("name.last"), age) else null } else null } else null if (back !=null) back :: readPeople(tail) else readPeople(tail) } caseNil => Nil }
Example #1 • Loops • Recursion • Higher-order functions
defreadPeople(files: List[String]): List[Person] = { files.foldRight(List[String]()) { (file, people) => val props = readFile(file) val back = if (props !=null) { if (props.contains("name.first") && props.contains("name.last") && props.contains("age")) { val age = toInt(props("age")) if (age !=null) new Person(props("name.first"), props("name.last"), age) else null } else null } else null if (back !=null) back :: people else people } }
Example #1 • Loops • Recursion • Higher-order functions • … • Monads!
deftoInt(s: String) = try { Some(s.toInt) } catch { case _ => None } // uninteresting and ugly defreadFile(file: String): Option[Map[String, String]] = { importcollection.jcl.Hashtable try { val is = newBufferedInputStream(newFileInputStream(file)) val p = new Properties p.load(is) is.close() Some(newHashtable(p).asInstanceOf[Hashtable[String, String]]) } catch { case _ => None } }
defreadPeople(files: List[String]): List[Person] = { for { file <- files props <- readFile(file) firstName <- props get"name.first" lastName <- props get"name.last" ageString <- props get"age" age <- toInt(ageString) } yield new Person(firstName, lastName, age) }
Example #1 • Loops • Recursion • Higher-order functions • Combinators • Monads!
defreadPeople(files: List[String]) = { importFunction._ files flatMapreadFileflatMap { props => valfNames = props get"name.first" val names = fNamesflatMap { (_, props get"name.last") } val data = names flatMap { case (fn, ln) => (fn, ln, props get"age"maptoInt) } data maptupled(new Person _) } }
What did we just see? • foldLeft / foldRight • 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? • foldLeft / foldRight defsum(nums: List[Int]) = { nums.foldLeft(0) { (x, y) => x + y } }
What did we just see? • foldLeft / foldRight defsum(nums: List[Int]) = { nums.foldLeft(0) { _ + _ } }
What did we just see? • foldLeft / foldRight defsum(nums: List[Int]) = { (0/:nums) { _ + _ } }
What did we just see? • foldLeft / foldRight • 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? • foldLeft / foldRight • map valnums = List("1", "2", "3", "4", "5") numsmap { str => str.toInt }
What did we just see? • foldLeft / foldRight • map valnums = List("1", "2", "3", "4", "5") numsmap { _.toInt }