Download - Functional programming ii
Functional Programming II
ExperTalks 2015-09-25Prashant Kalkar, Equal Experts
Christian Hujer, Nelkinda Software Craft
Little more on side effects
Consider this example
val hitMonster = monster.hitByShooting(gunShot)
// reduces the power of gunShot
val hitPlayer = player.hitByShooting(gunShot)
Changes to the gunshot are happening on the slide, hence they are called as Side Effects.
Not referentially transparent.
Functional way
val (hitMonster, usedGunShot) =
monster.hitByShooting(gunShot)
val hitPlayer = player.hitByShooting(usedGunShot)
Side effect is now visible in the return type.
No more side effect, just effect of the function.
New function is referentially transparent.
Recursion
Recursion
recursion/riˈkərZHən/nounSee recursion.
Recursion
recursion/riˈkərZHən/nounSee recursion. See also tail recursion.
Recursion
recursion/riˈkərZHən/nounSee recursion. See also tail recursion.
tail recursion/tāl riˈkərZHən/nounIf you aren’t sick of it already, see tail recursion.
Recursion
Recursion
Recursion
“If you already know what recursion is, just remember the answer.Otherwise, find someone who is standing closer to Douglas Hofstadter than you are;then ask him or her what recursion is.”
‒ Andrew Plotkin
A recursive law:Hofstadter’s Law
“It always takes longer than you expect, even when you take into account Hofstadter’s Law.”
‒ Douglas Hofstadter,Gödel, Escher, Bach:
An Eternal Golden Braid
Recursive Acronyms
GNUGNU's Not Unix
WINEWINE Is Not an Emulator
PHPPHP Hypertext Preprocessor
Recursion in Nature
Recursion in Arts
Recursion in Arts
Recursion Use Cases
● Being Awesome● Traversing Data Structures
○ Lists○ Trees
● Formulas● Algorithms
● Often less error-prone than iteration!
Recursion Example: Factorial
unsigned long factorial(unsigned long n) {
return
n == 0 ? 1
: n * factorial(n - 1);
}Base Case
Generic Case
Reduction
Recursion Example: Echo
Case 1: Empty Listecho([]) :- /* Base Case */
nl.
Case 2: Single Wordecho([N]) :- /* Special 1-Element Case */
write(N), echo([]).
Case 3: Multiple Wordsecho([H|T]) :- /* Generic Case */
write(H), write(' '), echo([T]).
Recursion Example: Echo
Case 1: Empty Listecho([]) :- /* Base Case */
nl.
Case 2: Single Wordecho([N]) :- /* Special 1-Element Case */
write(N), echo([]).
Case 3: Multiple Wordsecho([H|T]) :- /* Generic Case */
write(H), write(' '), echo([T]).
Recursion Example: Lsimport java.io.*;
public class Ls { public static void main(final String... args) { if (args.length == 0) ls(new File(".")); for (final String pathname : args)
ls (new File(pathname)); } public static void ls(final File path) { System.out.println(path); if (path.isDirectory()) for (final File child : path.listFiles()) ls(child); }}
Recursive Max Function in Haskell
maximum' :: (Ord a) => [a] -> a
maximum' [] = error "maximum of empty list"
maximum' [x] = x
maximum' (x:xs)
| x > maxTail = x
| otherwise = maxTail
where maxTail = maximum' xs
Recursive Max Function in Haskell
maximum' :: (Ord a) => [a] -> a
maximum' [] = error "maximum of empty list"
maximum' [x] = x
maximum' (x:xs) = max x (maximum' xs)
Quicksort in Haskell
qsort :: (Ord a) => [a] -> [a]
qsort [] = []
qsort (x:xs) =
qsort [a | a <- xs, a <= x]
++ [x] ++
qsort [a | a <- xs, a > x]
Disclaimer: This is not a true Quicksort (it’s not in-place), and not the fastest possible.
But behold its beauty!
How to DevelopLinear Recursive Algorithms
1. Define Base caseaka Case 0 or Case 1
2. Define special cases, if any3. Define generic case
aka Case n → n-1 or Case n+1 → n
Recursion in C
unsigned long factorial(unsigned long n) {
return
n == 0 ? 1
: n * factorial(n - 1);
}
Disassembly (gcc -O1)factorial:.LFB0:
.cfi_startprocmovl $1, %eaxtestq %rdi, %rdije .L6pushq %rbx.cfi_def_cfa_offset 16.cfi_offset 3, -16movq %rdi, %rbxleaq -1(%rdi), %rdicall factorialimulq %rbx, %raxpopq %rbx.cfi_restore 3.cfi_def_cfa_offset 8
.L6:rep ret.cfi_endproc
Disassembly (clang -O1)factorial: # @factorial
.cfi_startproc# BB#0:
movl $1, %eaxtestq %rdi, %rdije .LBB0_2.align 16, 0x90
.LBB0_1: # %tailrecurse # =>This Inner Loop Header: Depth=1
imulq %rdi, %raxdecq %rdijne .LBB0_1
.LBB0_2: # %tailrecurse._crit_edgeretq.cfi_endproc
Disassembly (gcc -O2)factorial:.LFB0:
.cfi_startproctestq %rdi, %rdimovl $1, %eaxje .L4.p2align 4,,10.p2align 3
.L3:imulq %rdi, %raxsubq $1, %rdijne .L3rep ret
.L4:rep ret.cfi_endproc
Conclusion
There are cases in which modern compilers● convert recursion into tail recursion● compile a tail recursion into a loop
Note: javac is stupid!Oracle, are you listening?!
Fibonacci
unsigned long fibo(unsigned long n){ return
n < 2 ? 1 : fibo(n - 1) + fibo(n - 2);}
⇒ Too tough for most compilers!
ConvertingRecursion → Tail Recursion
typedef unsigned long u64;
u64 fiboT(u64 a, u64 b, u64 n){ return n < 2 ? a : fiboT(b, a + b, n - 1);}
u64 fibo(u64 n){ return fiboT(1, 2, n);}
Why are Functional Languagesbetter at Recursion?
● Pure functions● Memoization● (Why do impure functions not allow
memoization?)● Lazy Evaluation
Algebraic Data Types
Algebraic data type
Composite Type
Defined by one more more data constructors.
data List a = Nil | Cons x (List xs)
● ADT algebra○ Sum of data constructors - Disjoint unions○ Each constructor is Product of its arguments
ADT Example
List definition in Scala
sealed trait List[+A]
case object Nil extends List[Nothing]
case class Cons[+A](head: A, tail: List[A])
extends List[A]
In OO terms these constructors can be mapped to subtypes.
ADT Example
ADTs have finite number of constructors or subtypes.
For list we have two constructors or subtypesEmpty list Non empty list with head and tail.
These constructions are exhaustive and no new subtype will ever be added.
More ADT Examples
OptionSome | None
EitherLeft | Right
TrySuccess | Failure
FutureSuccess | Failure
Switch cases good or bad
OO programmers consider Switch cases bad. Conditional code. Hard to maintainHunt down existing cases to add new cases. Program will misbehave if you miss to update one.
FP enhanced switch cases to pattern matching and promote them.
What’s the Truth??
Types and Operations
● Type against OperationsTy
pe
Operations
Infinite Type, Finite Operations
Finite Type, Infinite Operations
Infinite Type, Finite OperationsImplemented as Polymorphism (class hierarchy).
Easy to add new Type by Subtyping.
Difficult to add new Operations.
Finite Type, Infinite OperationsRepresented by Algebraic data types (ADTs)
Easy to add new operations using Pattern matching.
Difficult to add new Types.
Pattern Matching
Check for given pattern in the input.
Constant Pattern
def describe(x: Any) = x match {
case 5 => "five"
case "hello" => "hi!"
case () => "Unit" // => everything is a value
}
Switch cases.. Enhanced
Pattern Types
● Constructor Pattern
sealed class Expr
case class Number(num: Double) extends Expr
case class UnOp(operator: String, arg: Expr)
extends Expr
expr match {
case UnOp("-", UnOp("-", Number(a))) =>
println("Double Negation for" + a)
case _ => // return type unit ().
}
Sequence Pattern
expr match {
case List(0, _, _) =>
println("3 element, starting with 0")
case List(0, _*) => println("starting with 0")
case List(_, _, *) => println("at least two")
case e => println("something else" + e)
// variable pattern
}
Variable pattern is catch all pattern.
Type Pattern
def generalSize(x: Any) = x match {
case s: String => s.length
// typecast to string
case m: Map[_, _] => m.size
case _ => -1
}
Type pattern
def isIntIntMap(x: Any) = x match {
case m: Map[Int, Int] => true
case _ => false
}
<console>:11: warning: non-variable type argument
Int in type pattern Map[Int,Int] is unchecked since
it is eliminated by erasure
case m: Map[Int, Int] => true
^
Type erasers
Generic types in java and scala are only available at compile time.
Compiler use this information for type checking and then erase it.
Java and Scala arrays are exception to this rule.
def isStringArray(x: Any) = x match {
case a: Array[String] => "yes" // this works
case _ => "no"
}
Pattern matching with ADTs
def printIfExists(a: Option[Int]) = {
a match {
case Some(x) =>
println("value provided " + x)
}
}
<console>:10: warning: match may not be exhaustive.It would fail on the following input: None
This check is possible because of ADTs
Pattern match as Higher order function
def doubleTheOdds(numbers: List[Int]) = {
numbers.map {
case x if x%2 != 0 => x * 2
case y => y
}
}
The match is happening on the input of the map function.
If condition in pattern matching is called as Guard Condition.
Partial functions with Pattern Matching
def printIfExists:
PartialFunction[Option[Int], Unit] = {
case Some(x) =>
println("value provided " + x)
}
This will compile without any warnings.
Also no match required (similar to HOF pattern matching)
Pattern matching in variable declaration
Tuple values
val fullName = ("Jack", "Sparrow")
val (firstName, lastName) = fullName
Case class objects
val exp = BinOp("*", Number(5), Number(1))
val BinOp(op, left, right) = exp
Pattern matching as value
val number = 10
val numberDescription = number match {
case 5 => "five"
case 10 => "Ten"
case _ => "something else"
}
Everything is value in function programming.
Also said as everything is an expression (not statement).
Pattern Matching final Example
val numbers = List(1, 2, 3, 4, 5, 6)numbers.collect { case x if x%2 == 0 => x * 2 }output => List(4, 8, 12)
This is equivalent to
val partial: PartialFunction[Int, Int] = { case x if x % 2 == 0 => x * 2
}
numbers.filter(partial.isDefinedAt).map(partial)
I’ve seen the future.
● It’s logical.
● It has pattern matching……on the right side, which is left!
Pattern matching beyond FP?
main(Argv) :- echo(Argv).
echo([]) :-
nl.
echo([Last]) :-
write(Last), echo([]).
echo([H|T]) :-
write(H), write(' '), echo(T).
Echo in Prolog
Expression Problem
Problem
What can be done when neither Types nor Operations are limited.
Is there a way to Add a new type easilyAdd a new operation easily
This problem is well known as Expression Problem.
Philip Wadler 1998
American Computer Scientist
He coined the term Expression Problem
Expression Problem
Definition
Expression Problem:The goal is to define a datatype by cases, where one can add new cases to the datatype and new functions over the datatype, without recompiling existing code, and while retaining static type safety (e.g., no casts)
Used in discussing strengths and weaknesses (expressive power) of various programming paradigms and programming languages.
The problem manifest itself while working with language expressions.
interface Exp { void print(); }
class Lit implements Exp {
protected int value;
Lit(int value) { this.value = value; }
public void print() { System.out.print(value); }
}
class Add implements Exp {
protected Exp left, right;
Add(Exp l, Exp r) { left = l; right = r; }
public void print() {
return left.print(); Syso.print(“+”); right.print();
}
}
interface EvalExp extends Exp {
int eval();
}
class EvalAdd extends Add implements EvalExp {
public int eval() {
return left.eval() + right.eval();
}
}
// Ouch! This will not compile.
Generics to the Rescue
We can solve the compilation problem by somehow limiting the left and right to EvalExp
We can do that by introducing a type parameter for EvalAdd class.
class EvalAdd<A extends EvalExp> extends Add implements EvalExp
But this does not change the type of the left or right.
We will have to parameterize Add too.
Simple Solution with Generics
class Add<A extends Exp> implements Exp { protected A left, right;
Add(A l, A r) { left = l; right = r; } public void print() { … }
}
class EvalAdd<A extends EvalExp> extends Add<A> implements EvalExp {
public int eval() {
return left.eval() + right.eval();
// Now this will work.
}
}
Analysing the solution
The solution is possible because of Generics which came from FP languages into Java.
But we do not like this solution.
The base interface Exp does not enforce type parameter on derived classes.
This version was simplified version of actual proposed solution.
Type classes
Expression problem can be solved by using type classes
Haskell was the first language to introduce type classes.
They are supported by Scala with the help of implicits.
Example
sealed trait Exp
case class Lit(value: Int) extends Exp
case class Add(left: Exp, right: Exp) extends Exp
object ExpEvaluator {
def eval(exp : Exp): Int = exp match {
case Lit(value) => value
case Add(left, right) =>
eval(left) + eval(right)
}
}
We would like to convert Expression to Json.
object JsonWriter {
def write(value: JsonValue): String = // ...
def write(value: Exp) = // ...
}
But, we should be able to use JsonWriter for any object not just Expressions.
How about this
trait JsonConvertible {
def toJson: JsValue
}
sealed trait Exp extends JsonConvertible
object JsonWriter {
def write(value: JsonValue): String = // ...
def write(value: JsonConvertible) =
write(value.toJson)
}
This works.
JsonWriter.write(Add(Lit(2), Lit(3)))
But wait! .. Why the Expression class needs to know anything about Json.
We would like to remove the Expression dependency on JsonCovertible.
This implementation is also called as Subtype Polymorphism
We can solve the coupling problem by introducing a Type class as follows
// type class
trait JsonConverter[A] {
def convertToJson(value: A): JsonValue
}
object JsonWriter {
def write(value: JsonValue): String = // ...
def write[A](value: A, conv: JsonConverter[A]) =
write(conv.convertToJson(value))
}
We need to implement a convertor.
def expJsonConverter = new JsonConverter[Exp] {
def convertToJson(exp: Exp): JsonValue = { ... }
}
JsonWriter.write(Add(Lit(2), Lit(3)),
expJsonConverter)
This removed the coupling between Exp and Json.
But we have to pass in a parameter, which can quickly become complex for Composite type.
Scala implicits can help here.
implicit def expJsonConverter = // ...
def write[A](value: A)(implicit conv: JsonConverter[A]) =
write(conv.convertToJson(value))
JsonWriter.write(Add(Lit(2), Lit(3)))
Implicits parameters are passed in by the Compiler.
The call is now exactly like when JsonCovertible was used.
More about Type classes
Type classes provide something called as Ad-hoc polymorphism.
Type classes allow you to add new functionality to the existing Types, without changing them.Note, they are different than extension methods.
Like extension method, they do not attach themself to the type they are working on.
More about Type classes
Type classes provide polymorphism on their type parameters.
Interfaces in OO, provide polymorphism over the calling object.
Type classes can accept more than one type parameters and hence can provide polymorphism on two different type parameters. This is not possible for interfaces.
More about Type classes
Compiler picks-up the right type classes on the type parameter.
So unlike interfaces, implementation is chosen at compile time
Type classes depend on their type parameters and not with the object so they are more like Generic Static Method.
Solving Expression Problem with Type classes
trait Exp
case class Lit(value: Int) extends Exp
case class Add[A <: Exp, B <: Exp](left: A, right:
B) extends Exp
[A <: Exp] is same as <A extends Exp> in Java
Define type class and implicit implementations
// type classtrait Eval[E] {
def eval(e: E): Int
}
implicit def litEval = new Eval[Lit] {
def eval(l: Lit) = l.value
}
Implementation for Add is little complex.
implicit def addEval[A <: Exp, B <: Exp]
(implicit e1: Eval[A], e2: Eval[B]) = {
new Eval[Add[A, B]] {
def eval(a: Add[A, B]) =
e1.eval(a.left) + e2.eval(a.right)
}
}
The method takes two implicit arguments to evaluate left and right exp.
In, Add[A, B], A and B are type of Exp which can be Add or Lit
Evaluating the Expressions
def expressionEvaluator[A <: Exp]
(exp: A)(implicit e : Eval[A]) = {
e.eval(exp)
}
Lets use it to evaluate something// (1 + 2) + 3val exp1 = Add(Add(Lit(1), Lit(2)), Lit(3))
println("" + expressionEvaluator(exp1))
// output => 6
Let add a new type
case class Mult[A <: Exp, B <: Exp]
(left: A, right: B) extends Exp
Define an Implicit evaluator for Mult (same as Add)
implicit def mulEval[A <: Exp, B <: Exp]
(implicit e1: Eval[A], e2: Eval[B]) = {
new Eval[Mult[A, B]] {
def eval(m : Mult[A, B]) =
e1.eval(m.left) * e2.eval(m.right)
}
}
This will allow us to evaluate Expression with Mult
// (3 + 4) * 7val exp2 = Mult(Add(Lit(3), Lit(4)), Lit(7))
println("" + expressionEvaluator(exp2))
// output => 49
Lets try to add new operation like Print.
Type class for Print operation
// type classtrait Print[P] {
def print(p: P): Unit
}
Implicit implementation for Lit type
implicit def litPrint = new Print[Lit] {
def print(l: Lit) = Console.print(l.value)
}
Similarly implicit implementation for Add and Mult
implicit def addPrint[A <: Exp, B <: Exp]
(implicit p1: Print[A], p2 : Print[B]) = {
new Print[Add[A, B]] {
def print(a : Add[A, B]) = {
p1.print(a.left);
Console.print(" + ");
p2.print(a.right);
}
}
Implementation for Mult is exactly same.
Define a method to print the expression
def printExpressions[A <: Exp]
(exp : A)(implicit p : Print[A]) = {
p.print(exp)
}
Use it to print the expressions.// (3 + 4) * 7val exp2 = Mult(Add(Lit(3), Lit(4)), Lit(7))
printExpressions(exp2)
// output => 3 + 4 * 7
TestingFunctional Programming
● In General: Easier than OO (without FP)○ Pure Functions are almost trivial to test.○ No Side-effects ⇒ No additional states○ Higher Order Functions can be mocked easily.
● But what about TDD? How do you approach TDD for Functional Programming?
Testing Functional Programming
Transformation Priority Premise
● What is a Transformation?
● Let’s talk about Refactoring.
“Uncle Bob”, My Hero Robert C. Martin →
Refactoring
“Refactoring is changing the structure of code without significantly changing its behavior.”
‒ Robert C. Martin
Transformation
“Transformation is changing the behavior of code without significantly changing its structure.”
‒ Robert C. Martin
Transformation Priority Premise({} → nil) no code at all -> code that employs nil(nil->constant)
(constant → constant+) a simple constant to a more complex constant(constant → scalar) replacing a constant with a variable or an argument(statement → statements) adding more unconditional statements.(unconditional → if) splitting the execution path(scalar → array)
(array → container)
(statement → recursion)
(if → while)
(expression → function)replacing an expression with a function or algorithm
(variable → assignment) replacing the value of a variable
Beyond FP
● FP and OO are NOT mutually exclusive● FOOP - Functional Object Oriented
Programming● Logic Programming
Thank you!Questions?
Backup Slides
TODO Testing
● Testing Pure First-Order Functions● Testing Pure Higher-Order Functions
○ Mocking in FP● Transformation Priority Premise for FP
○ Example how to develop from Base Case to N-Case
List Implementation - optional
package mylist
sealed trait List[A] {
def ::(e: A): List[A] = mylist.::(e, this)
def :::(l: List[A]): List[A] = ???
}
case object Nil extends List[Nothing]
case class ::[A](h: A, tail: List[A]) extends List[A]
Topic List[P] Side Effects Continued[C] Recursion[P] Algebraic Datatypes[P] Pattern Matching[P] Expression Problem[C] Streams in Java (creation, consumption, filtering, map, reduce etc.)[P] Streams in Scala (creation, consumption, filtering, map, reduce etc.)[C] Testing in Functional Programming (TDD for FP, BDD, TPP for FP)MonadsMonoidsFunctorsApplicativesF-Bounded Types
Recursion TODO● Replacing Assignments with Calls● Replacing Loops with Recursion● Developing Recursive Algorithms
○ Base Case / Most Degenerate Case○ N-Case○ List-Variants: Empty, 1 Element, Multiple○ Head-Tail-Recursion vs Partition Recursion
● Tail-Recursion○ Why○ How to convert normal recursion into tail recursion (example: factorial) by introducing a
result parameter○ Examples in C with Disassemblies by GCC, clang, armcc, c251○ Example in Prolog○ Example in Scala○ Example in Clojure○ Limitations in Java