Functional Programming for OO Programmers (part 2)

Post on 14-Jul-2015

899 views 1 download

Transcript of Functional Programming for OO Programmers (part 2)

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

THANK YOU

@calvinchengx

calvin.cheng.lc

calvinchengx

calvinx.com

CRAFTSMANSHIPMaster your craft - Procedural, OO, Functional