OKC-FP 2016 - Introduction to Functional Programming
-
Upload
jordan-parmer -
Category
Software
-
view
129 -
download
5
Transcript of OKC-FP 2016 - Introduction to Functional Programming
Welcome to OKC-FP
Organizers
Jordan ParmerJessica
CampbellJeff Smith
Alex AdriaanseMike Hayes
Where to Follow Us?meetup.com okcfp.comTwitter @fp_okcSlack #okc-fp
User Group Topics
Functional Programming ConceptsLanguages
Libraries, Frameworks, Projects, Tools
What is Functional Programming?
hello!Jordan Parmer
@ProfParmer
A Very Short Story
“
“We are builders first; technologists second
“The better builders we are, the more value we provide.
1.What is Functional
Programming?
What Does It Mean to be Imperative?
What Are The Most Common Problems In
Imperative OOP Code?
Null Pointer ExceptionsConcurrency Errors With Shared State
Broad Context of ReasoningDeep Inheritance Chains
Mutable State
What is the emphasis with imperative OOP
code?
Inheritance over composition
Abstraction by Inheritance
Internal class state and encapsulation
Imperative state mutations
Can We Do Better?
Return to Roots
“A functional programming language is a language that
emphasises programming with pure functions and immutable
data
Functional ProgrammingDeclarativeDescribe what, not howExpressions over statements
IdempotentImmutabilityFunctions are without side-effectsResults deterministic
Referentially TransparentAny expression can be replaced with the results of its value
AlgebraicTypes, equality, expressionsCategory theory
Functionally ComposableSeparate behavior from dataSmall functions composed together to make small building blocks
ParallelizableIdempotent code by nature enables local and distributed parallelization
Myths✘Requires a math degree to understand
✘Targets only math/science problems
✘Only relevant in academic circles
✘Not for “Line of Business” or “Form Over Data” apps
LIES!
Shocking Adjustments✘No Null✘Recursion over looping✘Avoidance of if-statements✘Map/filter/fold over iterating✘Monadic I/O✘Lots of foreign mathematical nomenclature✘No debuggers
“OOP makes code understandable by encapsulating moving parts.
FP makes code understandable by minimizing moving parts.
✘Michael Feathers
We Want These
You Don’t Need a Functional Language To
Be Functional
But It Does Help
Some [Subjective] ExamplesPure-ish
✘ Haskell✘ Erlang✘ Elm✘ Hope✘ Joy✘ Mercury✘ Scheme
Hybrids✘ Scala✘ F#✘ Kotlin✘ Swift✘ ML✘ LISP✘ Rust✘ Groovy✘ R✘ Dart✘ Ruby
Progressively Incorporating✘ Java 8✘ C#✘ Python✘ C++ 11
...and many more
Popular Languages
Beware Bias Ahead
JVM LanguagesScala
✘ Statically strong typed✘ FP with OOP good parts✘ Higher-order functions ✘ Insanely advanced type system✘ Currying, lazy-evaluation, pattern matching, etc.✘ Large commercial adoption
Twitter, Spotify, LinkedIn, Apache Spark, Kafka, etc.
✘ Backed by Lightbend + Scala Centre
Clojure✘ Dynamically typed✘ First-order functions✘ S-expressions✘ LISP-dialect
“Modern LISP that is symbiotic with Java”
.NET Languages
F#✘ Statically typed✘ Multi-paradigm: FP, imperative, OOP✘ .NET Framework✘ Visual Studio Tooling✘ Backed by Microsoft
Apple Languages
Swift✘ Scala...brought to you by Apple
I kid! I kid!
✘ FP alternative to Objective C✘ Borrowed some constructs from C & Objective
C✘ Optional types✘ Categories (i.e. type-classes)
Erlang Languages
Erlang✘ Ideal for distributed systems✘ Fault-tolerant, real-time✘ Highly-available systems✘ BEAM virtual machine
Elixir✘ Higher abstractions on top of Erlang
Reduces boilerplate✘ Runs on BEAM✘ New language but closely related to Erlang
Purist Languages
Haskell✘ Purely functional✘ Named after Haskell Curry✘ Strong type system✘ Introduced type-classes✘ Strict immutability
Elm✘ FP for the web✘ Graphical layout without destructive updates✘ Interops with (not compiles to)
JavaScript/CSS/HTML
...And So Many More
But What Does That All Mean?
Let’s Talk About Some FP Concepts
The SuspectsPure Functions
ExpressionsType Systems
Function ChainingMap, Filter, Fold
Functors, Monoids, Monads
Side-Effects Are The Source of Many Woes
Consider Algebra
f(x) = 3x2-7x+5Given the above
Find f(-4)
f(x) = 3x2-7x+5Given the above
Find f(-4)81
Function Has Side-Effects If It...✘Modifies a variable✘Modifies a data structure in place
e.g. append to linked list✘Throws an exception✘Prints to the console✘Writes to a file✘Updates a database✘Draws to the screen
Function Has Side-Effects If It...✘Modifies a variable✘Modifies a data structure in place
e.g. append to linked list✘Throws an exception✘Prints to the console✘Writes to a file✘Draws to the screen
Have you ever passed a reference of a list to
something else?
Single InputSingle OutputNo side effectsReferentially transparent Func
A B
Function Is Pure If It...
Functional VS Imperative OOP
Pure functions are easy to test
Local reasoning
In => OutVS
Objects are hard to test
Encapsulated state
Context
Global reasoning
Functional VS Imperative OOP
Pure functions are easy to re-use
Low risk
Little-to-no dependencies
VSWe are terrible at making truly reusable objects
Challenges of tight coupling
Hard to abstract at correct level
Functional VS Imperative OOP
Pure functions are easy to parallelize
State in isolation
Just lego piecesVS
Sharing is cool in life...not in threads
How many concurrency patterns do we need to deal with shared state?
Functional VS Imperative OOP
Pure functions reduce need for most OOP design patterns VS
‘Cause who doesn’t want to know about your MVVMVMVMMVCCC singleton factory observer?
Function Composition
Single Input Single Output
Func
A B
Functions Of This Sort Can Be Things
Func
A B
Functions Of This Sort Can Be Things
Func
A B
Called Types
Two Functions
Func
Func
Compose Them Together
Func
Func
Compose Them Together
Func
Func
f(g(x))or
F o G x
New Function!
Func
Function Composition Is The New Old Injection
// Goal: Allow brewing coffee with different techniques and beans// Types// Beans => Grind// Grind => Coffee
trait Beanstrait Grindtrait Coffee
case class ColumbianBeans() extends Beans
def brew(grind: Beans => Grind, brew: Grind => Coffee): Beans => Coffee = { brew compose grind}
def burrGrind(beans: Beans): Grind = ???def blendGrind(beans: Beans): Grind = ???
def pourOver(grind: Grind): Coffee = ???def drip(grind: Grind): Coffee = ???def frenchPress(grind: Grind): Coffee = ???
val pourOverWithBurrGrind = brew(burrGrind, pourOver)val myFavoriteBeans = ColumbianBeans()val myFavoriteCoffee = pourOverWithBurrGrind(myFavoriteBeans)
// TIME TO WAKE UP!!!
// Goal: Allow brewing coffee with different techniques and beans// Types// Beans => Grind// Grind => Coffee
trait Beanstrait Grindtrait Coffee
case class ColumbianBeans() extends Beans
def brew(grind: Beans => Grind, brew: Grind => Coffee): Beans => Coffee = { brew compose grind}
def burrGrind(beans: Beans): Grind = ???def blendGrind(beans: Beans): Grind = ???
def pourOver(grind: Grind): Coffee = ???def drip(grind: Grind): Coffee = ???def frenchPress(grind: Grind): Coffee = ???
val pourOverWithBurrGrind = brew(burrGrind, pourOver)val myFavoriteBeans = ColumbianBeans()val myFavoriteCoffee = pourOverWithBurrGrind(myFavoriteBeans)
// TIME TO WAKE UP!!!
brew is a higher order function
Expressions
// Which right triangle that has integers for all sides and all sides equal to// or smaller than 10 has a perimeter of 24?
def rightTriangle = for { c <- 1 to 10 b <- 1 to c a <- 1 to b if ((Math.pow(a,2) + Math.pow(b,2) == Math.pow(c,2)) && (a + b + c == 24)) } yield (a, b, c)
rightTriangle.foreach(println(_))
Answer: [6, 8, 10]
Type Constraints
f(x) = 3/xInt => Double
f(x) = 3/x1 => 3/1 => 3.02 => 3/2 => 1.50 => 3/0 => ???
f(x) = 3/x1 => 3/1 => 3.02 => 3/2 => 1.50 => 3/0 => ???
AH, PICKLES!
f(x) = 3/x1 => 3/1 => 3.02 => 3/2 => 1.50 => 3/0 => ???
THROW AN EXCEPTION?
f(x) = 3/xInt => Double
0 => 3/0 => ???
THROW AN EXCEPTION?
THEN THIS IS A LIE!
f(x) = 3/xInt => Double
0 => 3/0 => ???
THROW AN EXCEPTION?
THEN THIS IS A LIE!
Are You A Liar?
But Functions Are Types!
Types Don’t Have to Be Open to All
Circumstances
f(x) = 3/xNonZeroInt => Double
Just Became Constrained And Self-Documented
f(x) = 3/x Zero Not Allowed BY NonZeroInt
-1 => 3/-1 => -31 => 3/1 => 3
2 => 3/2 => 1.5
Alternatively Extend Output to Be Monadic
f(x) = 3/xInt => Option[Double]
2 => 3/2 => Some(1.5)
0 => 3/0 => None
Static Types Just Became Enforced
Domain Documentation
Function Types Just Became Interfaces
We Sometimes Refer to Our Programs as an
“Algebra”
If It Compiles, It Probably Works
FP LOVES Collections
The Functional TrinityMap
Filter Fold
Mapval list = List(1, 2, 3, 4, 5)list.map(x => x + 100)
[101, 102, 103, 104, 105]
Map transforms an A to a B
Filterval list = List(1, 2, 3, 4, 5)list.filter(x => x > 3)
[4, 5]
Filter applies a predicate to each member in the set
Foldval list = List(1, 2, 3, 4, 5)list.fold(0)((x, y) => x + y)
15
Equivalent to…(((((0 + 1) + 2 )+ 3) + 4) + 5)
Fold applies a binary operation with each member and the result of the previous application
Function Chaining
def foo(): Thing = { val x = DoAThing() if (x != null) { val y = DoAnotherThing(x) if (y != null) { val z = DoYetAnotherThing(y) if (z != null) { return z } else { return null } } else { return null } } else { return null }}
def foo(): Thing = { val x = DoAThing() if (x != null) { val y = DoAnotherThing(x) if (y != null) { val z = DoYetAnotherThing(y) if (z != null) { return z } else { return null } } else { return null } } else { return null }}
Yeah, I know this is bad.
But you know you’ve
seen this before.
Hang with me.
def foo(): Thing = { val x = DoAThing() if (x != null) { val y = DoAnotherThing(x) if (y != null) { val z = DoYetAnotherThing(y) if (z != null) { return z } else { return null } } else { return null } } else { return null }}
Pyramid of Doom
def foo(): Thing = { val x = DoAThing() if (x != null) { val y = DoAnotherThing(x) if (y != null) { val z = DoYetAnotherThing(y) if (z != null) { return z } else { return null } } else { return null } } else { return null }}
Pyramid of Doom
The Only Lines Doing
Work
That’s only 20%!
def foo(): Thing = { val x = DoAThing() if (x != null) { val y = DoAnotherThing(x) if (y != null) { val z = DoYetAnotherThing(y) if (z != null) { return z } else { return null } } else { return null } } else { return null }}
Pyramid of Doom
Look at all those nulls!
Half this code is null checking.
Nulls are a serious code
smell!
def foo(): Thing = { val x = DoAThing() if (x != null) { val y = DoAnotherThing(x) if (y != null) { val z = DoYetAnotherThing(y) if (z != null) { return z } else { return null } } else { return null } } else { return null }}
Pyramid of Doom
Look at all those nulls!
Half this code is null checking.
Nulls are a serious code
smell!
Null Pointer Exceptions Waiting to Happen
def foo(): Thing = { val x = DoAThing() if (x != null) { val y = DoAnotherThing(x) if (y != null) { val z = DoYetAnotherThing(y) if (z != null) { return z } else { return null } } else { return null } } else { return null }}
Pyramid of Doom
Throw in async operations, we can
create the same pyramid with
nested callbacks.
FP to the Rescue!
def foo(): Option[Int] = { for { x <- DoAThing() y <- DoAnotherThing(x) z <- DoYetAnotherThing(y) } yield z}
def foo(): Option[Int] = { for { x <- DoAThing() y <- DoAnotherThing(x) z <- DoYetAnotherThing(y) } yield z}
Every line is significant
def foo(): Option[Int] = { for { x <- DoAThing() y <- DoAnotherThing(x) z <- DoYetAnotherThing(y) } yield z} Boom!
Every line is significant
Success or Failure?
def foo(): Option[Int] = { for { x <- DoAThing() y <- DoAnotherThing(x) z <- DoYetAnotherThing(y) } yield z}
foo().map { fooResult => // do stuff with result of foo}
foo() match { case Some(x) => // result of foo case None => // problem case}OR
def foo(): Option[Int] = { for { x <- DoAThing() y <- DoAnotherThing(x) z <- DoYetAnotherThing(y) } yield z}
foo().map { fooResult => // do stuff with result of foo}
foo() match { case Some(x) => // result of foo case None => // problem case}OR
Map function will only apply when there is
something in the result
A Some[T]
This is not the same as a conditional. Compiler will warn if missing algebraic type checking.
def foo(): Option[Int] = { for { x <- DoAThing() y <- DoAnotherThing(x) z <- DoYetAnotherThing(y) } yield z}
foo() match { case Some(x) => // result of foo case None => // problem case}
Instead of None, we could use our own custom types to represent error states. Remember, types are enforced documentation!
sealed trait OperationResult[+T]final case class OperationSuccess(result: T) extends OperationResult[T]final case class OperationFailure(error: Throwable) extends OperationResult[Nothing]
def foo(): OperationResult[Int] = { for { x <- DoAThing() y <- DoAnotherThing(x) z <- DoYetAnotherThing(y) } yield z}
foo() match { case OperationSuccess(v) => // do something with v case OperationFailure(e) => // do something with error}
Compiler will enforce this as an algebraic proof.
We’ve encoded rich information in our API.
Chained workflow.
Application Becomes a Collection of Pure
Functions
Just Fold the Collection
ParallelizationFold All the Pieces!
MonadsThe Key to a Lot of Awesome
And confusion
Functors, Monoids, and Bears
Functor
A => B
Monoid
Type AIdentity Function
Binary Combinator
Monad
Type with a Functor + FlatMap
State
State
State stays in box
State
The box is a “monad”
State
Think of monad as
collection of at most one
item
State
You can do collection things to monads
.map(...)
.flatMap(...)
.fold(...)
.filter(...)
.collect(...)
.head
State
If Monad is “empty”, applied functions are ignored
.map(...)
.flatMap(...)
.fold(...)
.filter(...)
.collect(...)
.head
def foo(): Option[Int] = Some(100)
val myFoo = foo()
val result = myFoo .map(i => i + 100) .map(i => i.toString) .map(i => i + " is a bigger number") .getOrElse("didn't do anything")
// result = "200 is a bigger number"
def foo(): Option[Int] = None
val myFoo = foo()
val result = myFoo .map(i => i + 100) .map(i => i.toString) .map(i => i + " is a bigger number") .getOrElse("didn't do anything")
// result = "didn’t do anything"
Executed but not applied
Monads are so much more but let’s keep it
simple for now…
...another talk to come
2.
Common Applications
Trends in Functional Programming
Data Processing Streaming Parallelizatio
n
Machine Learning
Heavy Computation Attracting
Talent
3.
Disadvantages?
4.
Final Thoughts
Natural way to migrate To Functional
Programming?
thanks!Any questions?
You can find me at@ProfParmer