2. Intro
Extreme Programming (XP) was the precursor movement to Agile, it's focus was
primarily on simplicity of design, refactoring and maximising the amount of work
NOT done. This talk will introduce Functional Programming (FP) concepts and
compare them with Object/Class Oriented Programming (OOP). I will argue FP is
perfectly aligned to XP, while OOP typically has some shortcomings. I will even
argue that the entropy and complexity seen in most large scale projects is caused by
OOP rather than mitigated by it.
3. Preamble
- Please interrupt! Please disagree - helps me know what slides to stay on
- This talk is packed with a lot of concepts, you don’t need to follow it all
- Apologies for the lack of examples
6. “OOP”?
Why do Functional Programmers home school their kids?
Because they hate Classes!
A Class is a scope where variables can be tied to functions and has a runtime
lifecycle.
8. Extreme Programming Recap
Perfection is achieved, not when there is nothing more to add, but when there is nothing left to take
away. —Antoine de Saint-Exupéry
Any intelligent fool can make things bigger, more complex and more violent. It takes a touch of
genius and a lot of courage to move in the opposite direction. —Albert Einstein.
When writing code, agile developers often stop to ask themselves, "What is the
simplest thing that could possibly work?" They seem to be obsessed with simplicity.
Rather than anticipating changes and providing extensibility hooks and plug-in points,
they create a simple design that anticipates as little as possible, as cleanly as
possible. Unintuitively, this results in designs that are ready for any change,
anticipated or not. -James Shore
https://www.jamesshore.com/Agile-Book/simple_design.html
9. Extreme Programming in 15 Chars
YAGNI
KISS
DRY
TDD
See also https://martinfowler.com/articles/designDead.html
10. Definition - Functional Programming
1. Things are functions <--- Mainly syntactic sugar
2. Functions are things <--- Mainly syntactic sugar
3. Functions are functions (huh?) <--- MOST IMPORTANT!!
4. Functions have no Free Variables
11. Things are Functions
You can apply most things in Scala as if they were a function, e.g.
scala> val mySet = Set(1, 3, 5, 6)
mySet: scala.collection.immutable.Set[Int] = Set(1, 3, 5, 6)
scala> mySet(4)
res0: Boolean = false
scala> mySet(5)
res2: Boolean = true
So mySet is a function from Int to Boolean, can you guess what it is?
12. Functions are Things
We can declare and pass functions around like things. E.g.
scala> val addOne = (i: Int) => i + 1
addOne: Int => Int = <function1>
scala> val list = List(1, 2, 2, 5, 5, 6)
list: List[Int] = List(1, 2, 2, 5, 5, 6)
scala> list.map(addOne)
res6: List[Int] = List(2, 3, 3, 6, 6, 7)
13. So Much Sugar
- Call objects as functions
- Pass functions around
-
- Currying
- Lamda (anonymous function) syntax
- Pattern matching - used for logic, and also dispatch (over poly’ based dispatch)
- Can use both postfix & infix (so can introduce operators as methods)
14. Functions are really Functions! AKA Pure
Functions
● That is they are functions in the formal mathematical sense
● They only take some parameters and return a result
I.e.
1. They do NOT change anything
2. They can NOT depend on change
(NOTE: Style and practice - not forced by the language.)
15. Breaks 1
scala> var iCanChange = 0
scala> def notReallyAFunction(bar: Int): Int = {
iCanChange = iCanChange + 10
bar
}
scala> notReallyAFunction(5)
res16: Int = 5
scala> iCanChange
res17: Int = 10
This is called a "side effect"
16. Breaks 2
scala> var iCanChange = 0
iCanChange: Int = 0
scala> def notReallyAFunction(bar: Int): Int = iCanChange + bar
notReallyAFunction: (bar: Int)Int
scala> notReallyAFunction(5)
res9: Int = 5
scala> iCanChange = iCanChange + 3
iCanChange: Int = 3
scala> notReallyAFunction(5)
res10: Int = 8
17. Pure Functions Are Important
A system's complexity is determined by the number of moving parts, the more
complex a system the harder it is to understand and consequently the more
mistakes will be made. Therefore having a system with no moving parts eliminates
almost all complexity and thus almost all mistakes.
NOTE: You can do functional programming in any language even if it doesn't
support the usual syntactic sugar.
18. Functions have no Free Variables
// In a class
public void BuildSQLConfig()
{
var sqlConfig = SqlConfigReader.ReadSQLConfig();
_config.SqlConnectionString = sqlConfig.ConnectionString;
}
After some refactoring
// In a (“static”) namespace
public BuildSQLConfig(config: Config, sqlConfigFile: String): Config
{
var sqlConfig = SqlConfigReader.ReadSQLConfig(sqlConfigFile);
config.copy(SqlConnectionString = sqlConfig.ConnectionString);
}
19. Point of OOP
1. Encapsulating Mutable State
○ No longer necessary (memory & cpu is cheap), can just “copy” rather
than mutate
2. Dynamic Dispatch via Inheritance Polymorphism
○ In FP we use instead:
i. Type-classes
ii. Dependencies that would normally need dynamic dispatch (e.g. calls to a DB, FS, etc)
are removed from the business logic and deferred via Monads & ADTs
○ Dynamic Dispatch via OOP still useful for external dependencies since (i) Type-classes can
leak type parameters, and (ii) deferred execution isn’t always an option
20. OOP (COP) vs FP - Principles
“OOP” employs SOLID, which are a set of mostly vague subjective principles to
prevent tight coupling. SRP can lead to Refuctoring (see video).
FP employs KISS, YAGNI, DRY and mathematical objectively defined concepts (e.g.
Monad, Monoid, etc)
KISS, YAGNI, DRY can be defined formally in terms of Complexity Theory, see
Kolmogorov Complexity, Agda, Idris, Code gen, etc
22. Origins Of Coupling - Two Big Leaks!
1. Tying Data & Functions in a single scope.
This complicates scopes and means many methods have Free Variables, these Free
Variables tie methods together in such a way that is over and above the signature of
the method. This is a lexical LEAK.
2. Furthermore state couples methods together. This is a logical LEAK.
Without these leaks expressions become fully transparent - they do the exact same
thing no matter where they are in the codebase
Recommend reading First Order Logic and Complexity Theory (especially Kolmogorov), e.g. Mathematical Introduction to Logic & Introduction to
Mathematical Logic by Enderton & Mendelson resp
https://en.wikipedia.org/wiki/Free_variables_and_bound_variables
23. FP - No Tight Coupling
- Avoid classes (so avoid Free Variables in methods). Only use functions in static
namespaces and pass around structs (aka data containers, case classes)
- Avoid mutation
Now we observe:
- A function cannot couple via state
- A function cannot couple via scope, all dependencies of the function must be
passed in via the parameters
- Every function is a Single Responsibility (even if in the same file)
- Functions can be easily moved around and refactored
- Function can be easily tested (no need to new up a class with a billion
dependencies just to test 1 function in the class, and no `private`)
24. … FP - No Tight Coupling
- No need for complex inheritance hierarchies
- Barely ever even a need for interfaces
- Most applications can be built using a very simple set of languages features
(again think Lisp)
25. Connascence - Taxonomy of Coupling
Coupling of Meaning - (types help us here)
Coupling of Algorithm - (again, types)
Couple of Position - (named params helps, types help)
Coupling of Value - (DRY)
Coupling of Execution Order (can solve with FP, no state)
https://www.slideshare.net/carlosraffellini/connascence-136561891 and http://connascence.io/
26. Design Patterns (DP)
You don’t need Design Patterns either, yet another load of crap you don’t need to learn. E.g.
Please avoid Design Pattern terminology in Functional Languages
https://blog.jooq.org/2016/07/04/how-functional-programming-will-finally-do-away-with-the-gof-patterns/
https://stackoverflow.com/questions/327955/does-functional-programming-replace-gof-design-patterns
Factory Pattern Currying and returning functions
Decorators on streams Lazily evaluated iterators
Visitor pattern Pattern matching
Command pattern Functions as first class
Various ways to do dynamic dispatch Pattern matching
27. End of Tight Coupling - Types FTW!
SOLID simpler: FP & Types
Some forms of coupling will remain, but all these forms can be solved with good
Type design.
Most Functional languages provide very rich type features (see Dotty, Haskell,
Agda, Idris)
Type aliases mean we can even avoid coupling on type (where necessary!)
28. Microservices (MS)
There are two main motivations for Microservices
Handling Complexity in Large Applications
- “OOP” Monolithes are hard to understand, maintain, develop and test.
- Complexity in “OOP” Monolithes tends to grow quadratically in the number of features (every feature
ends up impacting development of every other feature)
- FP Monolithes grow in complexity linearly, so Micro Services provide no benefit, in fact they just add
engineering complexity
Concurrency & Deployment Granularity
- FP got here first (again!), see Erlang, Elixir, Akka, etc
In essence all Microservices do is force modules to only interact via messages/structs and to not leak state,
which is exactly what FP is. Finally, most devs do Microservices really badly (distributed monolith,
multi-repos, build hell, etc)
29. Common Counters / Misconceptions
A perfect “OOP” developer won’t
introduce coupling and hard to reverse
design smells
BUT!
- ~50% of devs have less than 5 years experience
- OOP, SOLID, Design Patterns, Micro Services etc
require training from considerably more senior devs
- Why make things complicated if a simpler alternative
exists?
- “Perfect” cannot be defined since SOLID, DP, MS etc is
subjectively defined
- Giving devs a huge list of “do’s” is much harder than a
very short list of “do nots”
FP results in long parameter lists Wrap params in more structs
FP means passing Contexts around over and over.
OOP has DI frameworks.
Scala offers implicit parameters for this. Some
people prefer explicit passing of contexts anyway
as there is no Hocus Pocus.
30. YAGNI
- SOLID
- Access modifiers (most of the time no state to protect)
- Design Patterns
- Micro Services (and probably not Azure Functions / AWS Lambda either)
- Multirepos (Monorepos FTW!)
- Dependency Injection Frameworks
- As many unit tests
- As many developers
- Fear of monolithes and large projects
31. FP is the most XP
Little to no up front design necessary, no SRP or SOLID.
Source of all complexity is the Two Big Leaks: Free Variables & State.
“OOP” encourages premature abstraction (unnecessary interfaces & hierarchies).
Premature abstractions are always wrong and leaky.
Simply write all code in functions and pass in all dependencies in the signature.
Use tests (TDD) & DRY to motivate splitting functions not SRP
33. Scala Bonus - Inlining and Named Params
We often see
val fred = … some code …
val bob = … some code …
someFunction(fred, bob)
Please do instead
someFunction(
fred = … some code ...,
bob = … some code ...
)