functional programming for oo programmers (part 2)
TRANSCRIPT
FUNCTIONAL PROGRAMMINGfor OO programmers (Part 2)
WHAT
• Pure versus Impure (exercise)
• Currying (exercise)
• Map, Filter, Reduce (exercise)
• Functors, Applicative Functors, Monads (exercise)
are we diving into?
PURE OR IMPURErecognize pure functions
RECOGNIZE
• Functions that don’t change anything out-of-scope and don’t depend on anything out-of-scope are called “pure”
• A pure function always gives the same result given the same parameters; independent of program/system state
pure functions
RECOGNIZEpure function
var values = { a: 1 };
function pureFunction (a) {
var b = 1;
a = a * b + 2;
return a;
}
RECOGNIZEimpure function
var values = { a: 1 };
function impureFunction (items) {
var b = 1;
items.a = items.a * b + 2;
return items;
}
LET’S PLAYpure or impure?
PURE OR IMPURE?function getQueryVariable(variable) {
var query = window.location.search.substring(1);
var vars = query.split('&');
for (var i = 0; i < vars.length; i++) {
var pair = vars[i].split('=');
if (decodeURIComponent(pair[0]) === variable) {
return decodeURIComponent(pair[1]);
}
}
}
PURE OR IMPURE?function random(mZ, mW) {
mZ = 36969 * (mZ & 65535) + (mZ >> 16);
mW = 18000 * (mW & 65535) + (mW >> 16);
return (mZ >> 16) + mW;
}
PURE OR IMPURE?function addAndShow(a, b, console) {
var c = a + b;
console.log(a, ' + ', b, ' = ', c);
return c;
};
PURE OR IMPURE?var number = 1;
var increment = function() {
return number += 1;
};
increment();
PURE OR IMPURE?var number = 1;
var incrementAlt = function(n) {
return n + 1;
};
incrementAlt(number);
CURRYINGfirst-order function and higher-order function capabilities
CURRYINGvar people = [
{ name: 'Calvin' },
{ name: 'John' },
{ name: 'Thomas' }
];
// function with hardcoded key 'name' to retrieve list of names
function getPersonName(obj) {
return obj['name'];
}
// mapping to the hardcoded function
var names = people.map(getPersonName);
console.log(names);
JavaScript
CURRYING// Specify the key on-‐the-‐fly by using a generic function.
function getByKey(key, obj) {
return function(obj) {
return obj[key];
};
}
JavaScript
CURRYINGfunction getByKey(key, obj) {
return function(obj) {
return obj[key];
};
}
var getByKeyPartial = getByKey('name');
console.log(getByKeyPartial);
var names2 = people.map(getByKeyPartial);
console.log(names2);
[Function]
curried function or partial function
JavaScript
CURRYING
function getByKey(key, obj) {
return function(obj) {
return obj[key];
};
}
var names3 = people.map(getByKey('name'));
console.log(names3);
[Function]curried function or partial functionequivalent to:getByKey(‘name’)(obj) in this context
JavaScript
CURRYINGmodule Main where
f :: Integer
f = max 4 5
fPartial :: (Ord a, Num a) => a -‐> a
fPartial = max 4
main :: IO ()
main = do
print f
let g = fPartial 10
print g
Haskell
CURRYINGpeople :: [(String, String)]
people = [("name", "Calvin")
,("wat", "John")
,("name", "Thomas")
]
Haskell
CURRYING{-‐ generic filterByKey function that accepts a string as
keyname and the data structure shown previously -‐}
filterByKey :: Eq a => a -‐> [(a, t)] -‐> [t]
filterByKey _ [] = []
filterByKey p ((k, v):xs)
| p == k = v : filterByKey p xs
| otherwise = filterByKey p xs
filterByName :: [(String, t)] -‐> [t]
filterByName = filterByKey "name"
Haskell
[Function]curried function or partial function
CURRYINGmain :: IO ()
main = do
let names2 = filterByKey "name" people
print names2
let names3 = filterByName people
print names3
Haskell
[Function]curried function or partial function
MAP, FILTER, REDUCELook ma, no loops
MAP, FILTER, REDUCE// map, reduce and filter are built-‐in as methods of the
// Array class in JS
var aList = [0, 1, 2];
var newList = aList.map(function (i) {
return i + 1;
});
console.log(newList);
console.log(aList);
JavaScript map
MAP, FILTER, REDUCEaList :: [Integer]
aList = [0, 1, 2]
addOne :: [Integer]
addOne = map (+1) aList
Haskell map
MAP, FILTER, REDUCE// map, reduce and filter are built-‐in as methods of the
// Array class in JS
var aList = [0, 1, 2];
var lessThanTwo = aList.filter(function (i) {
return i < 2;
});
console.log(lessThanTwo);
console.log(aList);
JavaScript filter
MAP, FILTER, REDUCEaList :: [Integer]
aList = [0, 1, 2]
lessThanTwo :: [Integer]
lessThanTwo = filter (<2) aList
Haskell filter
MAP, FILTER, REDUCE// map, reduce and filter are built-‐in as methods of the
// Array class in JS
var aList = [0, 1, 2];
var reduceToSum = aList.reduce(function (a, b) {
return a + b;
});
console.log(reduceToSum);
console.log(aList);
reduce
MAP, FILTER, REDUCEaList :: [Integer]
aList = [0, 1, 2]
reduceToSum :: Integer
reduceToSum = foldl (+) 0 aList
Haskell reduce (foldl and foldr)
FUNCTORSa function that, given a value and a function, does the right thing
FUNCTORSfunction addOne(value) {
return value + 1;
}
console.log(addOne(10)); // 11
function addTwo(value) {
return value + 2;
}
console.log(addTwo(10)); // 12
JavaScript
FUNCTORS// A not-‐quite-‐there Functor
function aFunctor(value, fn) {
return fn(value);
}
console.log(aFunctor(10, addOne)); // 11, works as expected
console.log(aFunctor([1, 2, 3], addOne)); // '1,2,31' is
returned, which is not what we want
JavaScript
FUNCTORSfunction betterFunctor(value, fn) {
if (typeof value === 'number') {
return fn(value);
} else {
var map = Array.prototype.map;
return map.call(value, fn);
}
}
JavaScript
FUNCTORSconsole.log(betterFunctor([1, 2, 3], addOne)); // [2, 3,
4] is what we expected
console.log(betterFunctor(10, addOne)); // 11 is
what we expected
JavaScript
FUNCTORS// JavaScript's Array's map method is a functor! :-‐)
var map = Array.prototype.map;
console.log(map.call([1, 2, 3], addOne)); // [2, 3,
4] is what we expected.
console.log([].map.call([1, 2, 3], addOne)); // This
works too
JavaScript
FUNCTORSaddOne :: Num a => a -‐> a
addOne a = a + 1
addTwo :: Num a => a -‐> a
addTwo a = a + 2
result :: Num b => [b] -‐> [b]
result xs = map addOne xs
Haskell
COMPARISONFunctors vs Applicative Functors vs Monads
Functors Applicatives Monads
example (+3) [2]== [5]
Just(+3) <*>
Just 2== Just 5
Just 4>>=
makeHalf== Just 2
examples functions map, fmap, <*> <*>, <$>, liftA2 >>=, liftM
brings function operator in
execute operationgeneralizedmap
does both <*> & <$>
apply fn to wrapped value
apply wrapped fn to wrapped value
apply fn that returns a wrapped value
to a wrapped value
MAYBE FUNCTORSan often-used functor; and more about functors
MAYBE FUNCTORS• Captures null check
• Value inside may or may not be there
• Maybe has two subclasses - ‘Just’ or ‘Nothing’ (Haskell)
• Also referred to as Option with subclasses ‘Some’ or ‘None’ (Scala)
• Also available in Swift, e.g. Optional, enum Either<NSError, User>
MAYBE FUNCTORSvar aList = [1, 2, 3];
function compose(f, g) {
return function (x) {
return f(g(x));
};
}
function addOne(value) {
return value + 1;
}
function addTwo(value) {
return value + 2;
}
JavaScript
MAYBE FUNCTORSconsole.log(aList.map(compose(addOne, addTwo)));
console.log(aList.map(addTwo).map(addOne));
function mayBe(value, fn) {
return value === null || value === undefined ? value :
fn(value);
}
JavaScript
MAYBE FUNCTORSconsole.log(mayBe(undefined, compose(addOne, addTwo)));
// returns expected result undefined
console.log(mayBe(mayBe(undefined, addTwo), addOne));
// returns expected result undefined
console.log(mayBe(1, compose(addOne, addTwo)));
// returns expected result 4
console.log(mayBe(mayBe(1, addTwo), addOne));
// returns expected result 4
JavaScript
MAYBE FUNCTORSaddOne :: Num a => a -‐> a
addOne a = a + 1
addTwo :: Num a => a -‐> a
addTwo a = a + 2
composedFn :: Integer -‐> Integer
composedFn = addOne . addTwo
res :: [Integer]
res = map composedFn aList
Haskell
MAYBE FUNCTORSmain :: IO ()
main = do
print res
let res2 = fmap composedFn Nothing
print res2
let res3 = fmap composedFn (Just 1)
print res3
Haskell
APPLICATIVESapply wrapped function to wrapped value
APPLICATIVESvar none = {
map: function() {
return none;
},
bind: function() {
return none;
},
toString: function() {
return 'none';
}
};
JavaScript
APPLICATIVESfunction some(value) {
return {
map: function(func) {
return some(func(value));
},
bind: function(func) {
return func(value);
},
toString: function() {
return "some(" + value + ")";
}
};
}
JavaScript
APPLICATIVESvar functor = {
map: function(func, option) {
return option.map(func);
},
unit: some,
applyFunctor: function(funcOption, argOption) {
return funcOption.bind(function(func) {
return argOption.map(func);
});
}
};
JavaScript
APPLICATIVESfunction curry(func, numberOfArguments) {
return function(value) {
if (numberOfArguments === 1) {
return func(value);
} else {
return curry(func.bind(null, value), numberOfArguments -‐ 1);
}
};
}
JavaScript
APPLICATIVES// Usage
var four = some(4);
var six = some(6);
console.log(four.toString());
console.log(six.toString());
function add(a, b) {
return a + b;
}
var result = functor.applyFunctor(functor.map(curry(add, 2), four), six);
console.log(result.toString()); // some(10)
JavaScript
APPLICATIVESresult = functor.applyFunctor(functor.map(curry(add, 2),
none), six);
console.log(result.toString());
result = functor.applyFunctor(functor.map(curry(add, 2),
four), none);
console.log(result.toString());
JavaScript
APPLICATIVES// A cleaner API for our applicative functor operations
functor.applyFunctorUncurried = function(func) {
var args = Array.prototype.slice.call(arguments, 1);
return args.reduce(
functor.applyFunctor,
functor.unit(curry(func, args.length))
);
};
JavaScript
APPLICATIVESvar result2 = functor.applyFunctorUncurried(add, four, six);
console.log(result2.toString());
result2 = functor.applyFunctorUncurried(add, none, six);
console.log(result2.toString());
result2 = functor.applyFunctorUncurried(add, four, none);
console.log(result2.toString());
JavaScript
APPLICATIVESimport Control.Applicative
ans :: Maybe Integer
ans = (+) <$> Just 4 <*> Just 6
main :: IO ()
main = print ans
Haskell
MONADSapply function that returns wrapped value to a wrapped value
MONAD“A monad is just a monoid in the category of endofunctors, what's the problem?”
- James Iry (Brief, Incomplete and Mostly Wrong History of Programming Languages)
MONADLet’s
• understand the why,
• then, illustrate the how
• and, we will know what a monad is.
MONADWhy?
avoid mutable state while chaining a group of functions together (or executing a sequence of logic)
MONADmutation:
functions in imperative languages change the state of values as logic is executed
MONADSo what’s the big deal?
When data is mutable, parallel thread execution of your code on multiple CPUs leads to race conditions
Can’t do parallel thread execution on your multi-core machine ⇒
idle resource
If we insist on parallel thread execution without locks ⇒
unpredictable results, i.e. errors
MONAD
MONAD f (x)
x = g(x)
x = h(x)
x = i(x)
return x
MONAD f (x)
x = g(x)
x = h(x)
x = i(x)
return x
x = 3
x = 5
x = 10
x = -3 x = -3
f (x)
x = g(x)
x = h(x)
x = i(x)
return x
f (x)
x = g(x)
x = h(x)
x = i(x)
return x
MONAD x = 3
x = 5
x = 17
x = 4 x = -9
x = 7
x = 12
CPU #1 CPU #2
x = 4 x = -9
Shared Memory, x
MONADAs you can see, the same function results in indeterminate results depending on the whims of the parallel OS-controlled POSIX threads (“pthreads”)
We expect that when we write the code (“computation logic”) for f(x), when given x = 3, f(x) will always result in -3 but because x is mutable in traditional languages, this is not the case if we try to make our program run on multiple CPUs.
MONADImperative languages that we are familiar with solve this problem with
• locks (semaphores) on POSIX threads; or
• green threads (concurrent threads) and asynchronous I/O
MONADHaskell has everything + more performant approaches:
• sparks (super-duper lightweight green threads)
• green threads
• semaphores
• immutable variables by default, “side effects” (mutability) achieved via monads
f (x)
x1 = g(x)
x2 = h(x1)
x3 = i(x2)
return x3
f (x)
x1 = g(x)
x2 = h(x1)
x3 = i(x2)
return x3
MONAD x = 3
x1 = 5
x2 = 10
x3 = -3 x3 = -3
x1 = 5
x2 = 10
CPU #1 CPU #2
x3 = -3 x3 = -3
MONADHaskell
f :: Num a => a -> a
f x = let
x1 = x + 2
x2 = x1 + 5
x3 = x2 - 13
in x3
f :: Num a => a -> a
f x = let
x = x + 2
x = x + 5
x = x - 13
in x
error :variables in haskell
are immutable values!no side effects!
MONADSo, now, we know the Why?
avoid mutable state while chaining a group of functions together (or executing a sequence of logic)
So, how can a monad achieve the magic illustrated in the previous 2 slides?
MONADHow?
• type container
• return
• bind
MONADvar Maybe = function(value) { // container
this.value = value;
};
Maybe.prototype.ret = function() { // return
return this.value;
};
Maybe.prototype.bind = function(func) { // bind
if (this.value !== null) {
return func(this.value);
}
return this.value;
};
JavaScript
MONAD// lift: takes in a function that returns a normal value and changes it in a monad
Maybe.lift = function(func) {
return function(value) {
return new Maybe(func(value));
};
};
// Usage
var addOne = function(value) {
return value + 1;
};
// we can use this with bind
var maybeAddOne = Maybe.lift(addOne);
JavaScript
MONAD// lift2 use closures to get values from the two monads
// before running it through function, handling the undefined cases
Maybe.lift2 = function(func) {
return function(M1, M2) {
return new Maybe(M1.bind(function(value1){
return M2.bind(function(value2) {
return func(value1, value2);
});
}));
};
};
JavaScript
MONADvar add = function(a, b) {return a + b;};
var m1 = new Maybe(1);
var m2 = new Maybe(2);
var m3 = new Maybe(undefined);
var liftM2Add = Maybe.lift2(add);
liftM2Add(m1, m2).ret(); //3
liftM2Add(m3, m2).ret(); //undefined
liftM2Add(m1, m3).ret(); //undefined
JavaScript
MONADa :: Maybe Integer
a = Just 1
f :: Integer -‐> Maybe Integer
f = \x -‐> Just (x + 1)
main :: IO ()
main = do
let ans = a >>= f
print ans {-‐ we expect to get Just 2 -‐}
Haskell
MONAD• type container
• return
• bind
• We pass in function(s) to operate values inside it
• and get a returned value
MONADThe code
• http://github.com/calvinchengx/learnhaskell
CRAFTSMANSHIPMaster your craft - Procedural, OO, Functional