λ 2 Years of Real World FP at REA @KenScambler Scala Developer at
Me 14 years 5 years 5 years when possible when bored when forced
- 3 teams @ - 17 codebases - 43K LOC Jul 13 Jan 14 Jul 14 Jan 15
Why Functional Programming?
Compelling, tangible software engineering benefits
Modularity Abstraction Composability
Modular, abstract, composable programs are simple programs
Modularity
Can you reason about something in isolation?
Or do you need to fit everything in your head at once?
A B C K Is the cost of replacing B with K just writing K ?
A K B C Is the cost of replacing B with K just writing K ?
A K glue K B C K glue …or rewriting half the program?
A pure function is just input output; no side effects You can tell what it does without Looking at surrounding context.
Consider: Let’s parse a string like “Richmond, VIC 3121” def def parseLocation(str: String): Location = { val val parts = str.split( “,” ) val val secondStr = parts(1) val val parts2 = secondStr.split( “ “ ) Location( parts(0), parts2(0), parts(1).toInt) }
Possible errors: 1 def def parseLocation(str: String): Location = { val val parts = str.split( “,” ) val val secondStr = parts(1) val val parts2 = secondStr.split( “ “ ) Location( parts(0), parts2(0), parts(1).toInt) } Could be null
Possible errors: 2 def def parseLocation(str: String): Location = { val val parts = str.split( “,” ) val val secondStr = parts(1) val val parts2 = secondStr.split( “ “ ) Location( parts(0), parts2(0), parts(1).toInt) } Might not have enough elements
Possible errors: 3 def def parseLocation(str: String): Location = { val val parts = str.split( “,” ) val val secondStr = parts(1) val val parts2 = secondStr.split( “ “ ) Location( parts(0), parts2(0), parts(1).toInt) } Might not have enough elements
Possible errors: 4 def def parseLocation(str: String): Location = { val val parts = str.split( “,” ) val val secondStr = parts(1) val val parts2 = secondStr.split( “ “ ) Location( parts(0), parts2(0), parts(1).toInt) } Might not have enough elements
Possible errors: 5 def def parseLocation(str: String): Location = { val val parts = str.split( “,” ) val val secondStr = parts(1) val val parts2 = secondStr.split( “ “ ) Location( parts(0), parts2(0), parts(1).toInt) } Might not have enough elements
Possible errors: 6 def def parseLocation(str: String): Location = { val val parts = str.split( “,” ) val val secondStr = parts(1) val val parts2 = secondStr.split( “ “ ) Location( parts(0), parts2(0), parts(1).toInt) } Might not be an int
Possible errors: 6 + 3 = 9 def ef doSomethingElse(): Unit = { 3 // // . ...Do ..Do o oth ther s er stu tuff ff 6 parseLocation (“Melbourne, VIC 3000”) }
Possible errors: 9 + 4 = 13 def ef anotherThing(): Unit = { 4 // // . ...Do ..Do e eve ven mo n more re s stuff tuff 9 doSomethingElse() }
Code inherits all the errors and side-effects of code it calls. Local reasoning becomes impossible; modularity is lost.
Possible errors: 0 def parseLocation(str: String): def Option[Location] = { val val parts = str.split( ”," ) for for { locality <- parts.optGet(0) theRestStr <- parts.optGet(1) theRest = theRestStr.split(" ") subdivision <- theRest.optGet(0) postcodeStr <- theRest.optGet(1) postcode <- postcodeStr.optToInt } yield yield Location(locality, subdivision, postcode) }
All possibilities have been elevated into the type system Local reasoning is possible! Local reasoning is possible about things that call it, etc…
Abstraction
Know as little as you need
Know as little as you need Produce as little as you can
def def sumInts( list: List[Int]): Int = { var var result = 0 for for (x <- list) { result = result + x } return return result }
def def flatten[A]( list: List[List[A]]): List[A] = { var var result = List() for for (x <- list) { result = result ++ x } return return result }
def def all[A]( list: List[Boolean]): Boolean = { var var result = tru rue for for (x <- list) { result = result && x } return return result }
def def sumInts( list: List[Int]): Int = { var var result = 0 for for (x <- list) { result = result + x } return return result }
def def flatten[A]( list: List[List[A]]): List[A] = { var var result = List() for for (x <- list) { result = result ++ x } return return result }
def def all[A]( list: List[Boolean]): Boolean = { var var result = tru rue for for (x <- list) { result = result && x } return return result }
Extract the essence! trait trait Monoid[M] { def zero: M def append(m1: M, m2: M): M }
def def fold[M](list: List[M]) (im impl plic icit it m: Monoid[M]): M = { var result = m.z var .zer ero for for (x <- list) { result = m.append(result,x) } result }
def def fold[M](list: List[M]) (im impl plic icit it m: Monoid[M]): M = list.foldLeft(m.zero)(m.append)
fold(List(1, 2, 3, 4)) 10 fold(List(List("a", "b"), List("c"))) List("a", "b", "c") fold(List(true, true, false, true)) false
Abstraction is always a good thing! Less repetition More reuse Less decay, because code can’t grow tumours around unnecessary detail
Composability
Functions compose. A => B A => B B => C B => C C => D C => D D => E D => E
Functions compose. A => E A => E
Sideways too. A => E A => E X => Y X => Y
Sideways too. (A,X) => (E,Y) (A,X) => (E,Y)
This works in the large, as well as the small! Entire systems can be composable like functions…. without side effects
Truly composable systems can accrue more and more stuff without getting more complex!
Simplicity • Modularity – Reason locally • Abstraction – say only what you need, hide everything you don’t • Composability – scale without accruing complexity
The human process
Software GSD Coffee Tea Quiet time development is a human process Ponies Reconsider! Technical excellence isn’t enough!
Xi’an offshore team Team Team Team Team
Xi’an offshore team • Many teams are partly based in Xi’an. • They’re very good, but… • Communication is hard! • It works, but requires great investment of time and money
Bottom-up tech decisions
POST {"partyTime": "5:00"} GET /buzz/5/ GET /foo/bar PUT {"mode": "banana"}
Architect Mountain
Architect Mountain
Architect Mountain
NO Just needs more Agile No no no no no no no no no no no no no no Architect no no no no no no no Mountain no no no no no no no no no no no not like this. Wake up it’s a school day Don’t forget More your meetings, velocity but littler
Bottom-up tech decisions You have to win
Bottom-up tech decisions You have to win
Bottom-up tech decisions You have to win
Software Paleontology Everyone’s got a history…
Scriptozoic era 1995 – 2010 Mostly Perl
Monolithocene epoch 2010 – 2012 Ruby, Java Scriptozoic era 1995 – 2010 Mostly Perl
Microservices AWS 2012 – Ruby, Scala, JS Monolithocene epoch 2010 – 2012 Ruby, Java Scriptozoic era 1995 – 2010 Mostly Perl
Adoption
June, 2013
λ
λ λ
λ λ λ
λ λ λ
λ λ
λ λ
λ
Language choice Functional Object Oriented Powerful static types JVM
Functional Object Oriented Powerful static types JVM
Functional JVM
Functional JVM
Functional JVM
Functional JVM
Whatever works for you!
The journey
Recommend
More recommend