2 Years of Real World FP at REA
@KenScambler Scala Developer at
λ
2 Years of Real World FP at REA @KenScambler Scala Developer at - - PowerPoint PPT Presentation
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?
@KenScambler Scala Developer at
λ
14 years 5 years 5 years when possible when bored when forced
Jul 13 Jan 14 Jul 14 Jan 15
glue glue
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) }
Could be null
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) }
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: 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 be an int
Possible errors: 6
def ef doSomethingElse(): Unit = { // // . ...Do ..Do o
ther s er stu tuff ff parseLocation(“Melbourne, VIC 3000”) }
Possible errors: 6 + 3 = 9 6 3
def ef anotherThing(): Unit = { // // . ...Do ..Do e eve ven mo n more re s stuff tuff doSomethingElse() }
Possible errors: 9 + 4 = 13 4 9
Code inherits all the errors and side-effects of code it calls. Local reasoning becomes impossible; modularity is lost.
def def parseLocation(str: String): 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) }
Possible errors: 0
All possibilities have been elevated into the type system Local reasoning is possible! Local reasoning is possible about things that call it, etc…
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 }
trait trait Monoid[M] { def zero: M def append(m1: M, m2: M): M }
Extract the essence!
def def fold[M](list: List[M]) (im impl plic icit it m: Monoid[M]): M = { var var result = m.z .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
A => B A => B B => C B => C C => D C => D D => E D => E
A => E A => E
A => E A => E X => Y X => Y
(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!
everything you don’t
Coffee Tea Quiet time GSD Ponies Reconsider!
Team Team Team Team
money
GET /foo/bar PUT {"mode": "banana"} POST {"partyTime": "5:00"} GET /buzz/5/
Architect Mountain
Architect Mountain
Architect Mountain
Architect Mountain Just needs more Agile Don’t forget your velocity More meetings, but littler
No no no no no no no no no no no no no no no no no no no no no no no no no no no no no no no no not like
school day
Scriptozoic era 1995 – 2010 Mostly Perl
Monolithocene epoch 2010 – 2012 Ruby, Java Scriptozoic era 1995 – 2010 Mostly Perl
AWS
Monolithocene epoch 2010 – 2012 Ruby, Java Scriptozoic era 1995 – 2010 Mostly Perl Microservices 2012 – Ruby, Scala, JS
June, 2013
λ λ
λ λ
λ
λ λ
λ
λ λ
λ λ
λ
Object Oriented Powerful static types Functional JVM
Object Oriented Powerful static types Functional JVM
Functional JVM
Functional JVM
Functional JVM
Functional JVM
Jul 13 Jan 14
1
LOC Web JSON DB Dep Inj Play2 Play2 6K Squeryl / Play2 Constructors Type API
#1
LOC Web JSON DB Dep Inj Play2 Play2 6K Squeryl / Play2 Constructors Type API
Learning Investment
#1
LOC Web JSON DB Dep Inj Play2 Play2 6K Squeryl / Play2 Constructors Type API
Learning Investment Report card
Learning curve Technical result Productivity Sentiment Steep but ok OK; slight dip Great; but FWs too heavy
#1
Jul 13 Jan 14
1 2
Some infrastructure
LOC Web JSON DB Dep Inj Play2 Argonaut 3K Squeryl / Play2 Constructors Type API
#2
LOC Web JSON DB Dep Inj Play2 Argonaut 3K Squeryl / Play2 Constructors Type API
Learning Investment
#2
LOC Web JSON DB Dep Inj Play2 Argonaut 3K Squeryl / Play2 Constructors Type API
Learning Investment Report card
Learning curve Technical result Productivity Sentiment Learning? Meh Needed rework; OK in the end
#2
!!!
λ
Another team! λ
“We’ll have some of that!” λ
Jul 13 Jan 14
1 2 4 6
5
3
LOC Web JSON DB Dep Inj Unfinagled Argonaut 2K, 3K, 4K, 1K Slick Constructors Type Web app, libs, API x 2
Learning Investment Report card
Learning curve Technical result Productivity Sentiment Not bad Great Great
#3,4, 5,6
Jul 13 Jan 14
1 2 4 6
5 3
7
Jul 14
LOC Web JSON DB Dep Inj Unfinagled Argonaut 4K Slick Monad Transformers Type API
#7
LOC Web JSON DB Dep Inj Unfinagled Argonaut 4K Slick Monad Transformers Type API
Learning Investment
#7
LOC Web JSON DB Dep Inj Unfinagled Argonaut 4K Slick Monad Transformers Type API
Learning Investment Report card
Learning curve Technical result Productivity Sentiment Vertical Great Great
#7
Good technical benefits, but… Only 2 people could understand the code Experienced engineers felt totally helpless Learning curve way too steep
All-rounders Gurus
All-rounders Gurus
All-rounders Gurus
Gurus
All-rounders
Gurus
is utterly essential
rounders
behind
Familiar, but technically unsound concepts have limited value. However… if we can’t make a concept learnable, then we can’t use it.
λ A Ruby team considers its options…
λ
λ
Jul 13 Jan 14
1 2 4 6
5 3
7
8
Jul 14
LOC Web JSON DB Dep Inj Play2 Play2 2K Anorm Constructors Type Web app
#8
LOC Web JSON DB Dep Inj Play2 Play2 2K Anorm Constructors Type Web app
Learning Investment
#8
LOC Web JSON DB Dep Inj Play2 Play2 2K Anorm Constructors Type Web app
Learning Investment Report card
Learning curve Technical result Productivity Sentiment Steep Meh OK
#8
λ
Jul 13 Jan 14
1 2 4 6
5 3
7
8
Jul 14 Jan 15
17
Inheritance/mixins Static functions
Inheritance/mixins Static functions Partial functions Total functions
Inheritance/mixins Static functions Partial functions Total functions Exceptions Sum types
Inheritance/mixins Static functions Partial functions Total functions Strings/primitives Wrapper types Exceptions Sum types
Every Thursday, in work hours 7 – 12 people each week Reading, exercises, talks, live coding
LOC Web JSON DB Dep Inj Unfiltered Argonaut 3K Slick Free Monads Type API
#17
LOC Web JSON DB Dep Inj Unfiltered Argonaut 3K Slick Free Monads Type API
Learning Investment
#17
LOC Web JSON DB Dep Inj Unfiltered Argonaut 3K Slick Free Monads Type API
Learning Investment Report card
Learning curve Technical result Productivity Sentiment Smooth Great Brilliant
#17
Pure core
Routes Controllers Logic Script
Interpreter
Actual DB “Authenticate” “Use config” “Get from DB” “Update DB” “Log result” Web Framework Server
App runtime
Pure core
Routes Controllers Logic Script
Pure Interpreter
Pure tests
Input Output Assert “Authenticate” “Use config” “Get from DB” “Update DB” “Log result”
Pure core
Interpreter
Actual DB Web Framework Server
App runtime
Wafer thin E2E tests
Input Output Assert
SearchController troller { def def getList(uid: UserId): Script[Response] = { for for { searches <- getSearches(uid) cfg <- getConfig results <- addLinks(searches, cfg) } yield yield Ok ~> Response(result.toJson) } }
Most business software isn’t rocket science.
FP is a tall tree But there is so much low hanging fruit!