Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Functional programming in javascript (intro)

Functional programming in javascript (intro)

Introduction to functional programming concept using javascript and the library ramda.

Grégoire Charvet

January 21, 2015
Tweet

More Decks by Grégoire Charvet

Other Decks in Programming

Transcript

  1. WHAT IS FP? There is no unique definition. More a

    group of programming patterns. Functional languages: Haskell ML OCaml The lisp family (common Lisp and Closure) Scala (mixed OO & Functional) Erlang
  2. OOP VS FP OOP focus on difference between data FP

    focus on consistent data structures
  3. OOP Data tightly coupled with logic Core concept: encapsulation Code

    reuse achieved with inheritance and composition Abstraction model is the data itself FP Data is loosely coupled with logic Core concept: function composition Code reuse achived with function composition and higher order function Abstraction model is the function
  4. SQL SAMPLE S E L E C T o r

    d e r s . o r d e r _ i d , o r d e r s . o r d e r _ d a t e , s u p p l i e r s . s u p p l i e r _ n a m e F R O M s u p p l i e r s R I G H T O U T E R J O I N o r d e r s O N s u p p l i e r s . s u p p l i e r _ i d = o r d e r s . s u p p l i e r _ i d W H E R E o r d e r s . o r d e r _ s t a t u s = ' I N C O M P L E T E ' O R D E R B Y o r d e r s . o r d e r _ d a t e D E S C ; Consistent data structure (table with schema) Few functions combined together Declarative language Very close to a functional paradigm
  5. LODASH EXAMPLE _ . m a p ( [ 1

    , 3 , 5 ] , a d d 3 ) ; / / [ 4 , 6 , 8 ] More about map later
  6. _ . m a p ( [ 1 , 3

    , 5 ] , a d d 3 ) ; / / [ 4 , 6 , 8 ] R . m a p ( a d d 3 , [ 1 , 3 , 5 ] ) ; / / [ 4 , 6 , 8 ] Order of arguments is reversed. Collection is at the end.
  7. OOP STYLE f u n c t i o n

    A d d e r ( c o n s t a n t ) { t h i s . c o n s t a n t = c o n s t a n t ; } A d d e r . p r o t o t y p e . g e t A d d F u n c t i o n ( ) { v a r c o n s t a n t = t h i s . f u n c t i o n ( ) ; r e t u r n f u n c t i o n ( n u m ) { r e t u r n n u m + c o n s t a n t ; } ; } v a r a d d 5 = n e w A d d e r ( 5 ) ; _ . m a p ( a d d e r . g e t A d d F u n c t i o n ( ) , [ 1 , 3 , 5 ] ) ;
  8. IMPERATIVE STYLE _ . m a p ( f u

    n c t i o n ( n u m ) { r e t u r n n u m + 5 ; } , [ 1 , 3 , 5 ] ) ;
  9. FP STYLE Define a generic function to add two numbers

    f u n c t i o n a d d ( a , b ) { r e t u r n a + b ; } and curry it a d d = R . c u r r y ( a d d ) ; v a r a d d 5 = a d d ( 5 ) ; R . m a p ( a d d 5 , [ 1 , 3 , 5 ] ) ;
  10. translate into: CURRYING ALSO KNOWN AS PARTIAL APPLICATION v a

    r a d d 5 = a d d ( 5 ) ; v a r a d d 5 = f u n c t i o n ( a ) { r e t u r n a d d ( 5 , a ) ; } a d d ( 5 , 3 ) = = = a d d ( 5 ) ( 3 ) ;
  11. All Ramda methods are automatically curried. R . m a

    p ( a d d 3 , [ 1 , 3 , 5 ] ; R . m a p ( a d d 3 ) ( [ 1 , 3 , 5 ] ) ; Collection as the last argument makes sense with currying
  12. GET ALL REPOS FOR A USER G E T h

    t t p s : / / a p i . g i t h u b . c o m / u s e r s / : u s e r / r e p o s [ { " i d " : 1 2 3 1 7 2 0 4 , " n a m e " : " a b i d e " , " f u l l _ n a m e " : " h i d d e n t a o / a b i d e " , " o w n e r " : { . . . } , " u p d a t e d _ a t " : " 2 0 1 4 - 0 4 - 2 7 T 0 8 : 5 9 : 1 8 Z " , " p r i v a t e " : f a l s e , " h t m l _ u r l " : " h t t p s : / / g i t h u b . c o m / h i d d e n t a o / a b i d e " , " d e s c r i p t i o n " : " B a s e c l a s s w i t h p u b - s u b a n d o b s e r v e r s f o r J S o b j " f o r k " : f a l s e , " u r l " : " h t t p s : / / a p i . g i t h u b . c o m / r e p o s / h i d d e n t a o / a b i d e " , " l a n g u a g e s _ u r l " : " h t t p s : / / a p i . g i t h u b . c o m / r e p o s / h i d d e n t a o / a b i d e / l " l a n g u a g e " : " J a v a S c r i p t " , } , { . . . } ]
  13. GET LANGUAGES FOR A REPO G E T h t

    t p s : / / a p i . g i t h u b . c o m / u s e r s / : u s e r / : r e p o n a m e / l a n g u a g e s { " J a v a S c r i p t " : 1 1 1 0 3 , " C o f f e e S c r i p t " : 1 3 9 8 }
  14. (more on next slides) SIMPLE IMPERATIVE APPROACH v a r

    g e t Y e a r L a n g u a g e s = f u n c t i o n ( y e a r / * Y Y Y Y * / ) { r e t u r n f e t c h R e p o s ( ) . t h e n ( f u n c t i o n ( r e p o s ) { v a r y e a r R e p o = [ ] ; f o r ( v a r i = 0 , l e n = r e p o s . l e n g t h ; i < l e n ; + + i ) { i f ( r e p o s [ i ] . u p d a t e d _ a t . s l i c e ( 0 , 4 ) > = y e a r ) { y e a r R e p o . p u s h ( r e p o s [ i ] ) ; } } ; r e t u r n y e a r R e p o s ; } )
  15. (even more on next slides) . t h e n

    ( f u n c t i o n ( r e p o s ) { v a r l a n g u a g e s P = [ ] ; f o r ( v a r i = 0 , l e n = r e p o s . l e n g t h ; i < l e n ; + + i ) { i f ( r e p o s [ i ] . l a n g u a g e s ) { l a n g u a g e s P . p u s h ( g e t R e p o L a n g u a g e s ( r e p o . l a n g u a g e s _ u r l ) ) ; } } r e t u r n l a n g u a g e s P ; } ) . t h e n ( P r o m i s e . a l l )
  16. (even more on next slides) . t h e n

    ( f u n c t i o n ( l a n g u a g e s ) { v a r r e s u l t = { } ; f o r ( v a r i = 0 , l e n = l a n g u a g e s . l e n g t h ; i < l e n ; + + i ) { f o r ( v a r l a n g i n l a n g u a g e s [ i ] ) { i f ( ! r e s u l t [ l a n g ] ) r e s u l t [ l a n g ] = 0 ; r e s u l t [ l a n g ] + = l a n g u a g e s [ i ] [ l a n g ] ; } } ; r e t u r n r e s u l t ; } )
  17. . t h e n ( f u n c

    t i o n ( s u m m a r y ) { v a r a r r = [ ] ; f o r ( v a r l a n g u a g e i n s u m m a r y ) { a r r . p u s h ( { l a n g u a g e : l a n g u a g e , c o u n t : s u m m a r y [ l a n g u a g e ] } ) ; } r e t u r n a r r ; } ) . t h e n ( f u n c t i o n ( s u m m a r y ) { / / m o s t u s e d l a n g u a g e f i r s t r e t u r n s u m m a r y . s o r t ( f u n c t i o n ( l a , l b ) { r e t u r n l a . c o u n t > l b . c o u n t ? 1 : l a . c o u n t < l b . c o u n t ? - 1 : 0 ; } ) ; } ) ; } / / e n d o f t h e f u n c t i o n g e t Y e a r L a n g u a g e s
  18. SIMILAR OOP METHOD v a r g e t Y

    e a r L a n g u a g e s = f u n c t i o n ( y e a r / * Y Y Y Y * / ) { r e t u r n f e t c h R e p o s ( ) . t h e n ( f u n c t i o n ( r e p o s ) { v a r r e p o L i s t = n e w R e p o L i s t ( r e p o s ) ; r e p o L i s t . c h o o s e B y Y e a r ( y e a r ) ; r e t u r n r e p o L i s t . g e t L a n g u a g e s ( ) ; } ) . t h e n ( f u n c t i o n ( l a n g u a g e s ) { v a r l a n g u a g e L i s t = n e w L a n g u a g e L i s t ( l a n g u a g e s ) ; v a r s o r t e r = n e w L a n g u a g e S o r t e r ( ' c o u n t ' ) ; l a n g u a g e L i s t . s o r t ( s o r t e r . g e t S o r t F u n c t i o n ( ) ) ; r e t u r n l a n g u a g e L i s t . l a n g u a g e s ; } ) ; }
  19. f u n c t i o n R e

    p o L i s t ( r e p o s / * R e p o s [ ] * / ) { t h i s . r e p o s = r e p o s ; } R e p o L i s t . p r o t o t y p e . c h o o s e B y Y e a r = f u n c t i o n ( y e a r / * Y Y Y Y * / ) { v a r r e s u l t s = [ ] ; f o r ( v a r i = 0 , l e n = t h i s . r e p o s . l e n g t h ; i > l e n ; + + i ) { i f ( t h i s . r e p o s [ i ] . u p d a t e d _ a t . s l i c e ( 0 , 4 ) > = y e a r ) { r e s u l t s . p u s h ( t h i s . r e p o s [ i ] ) ; } } t h i s . r e p o s = r e s u l t s ; }
  20. R e p o L i s t . p

    r o t o t y p e . g e t L a n g u a g e s = f u n c t i o n ( ) { v a r l a n g u a g e s P = [ ] ; f o r ( v a r i = 0 , l e n = t h i s . r e p o s . l e n g t h ; i > l e n ; + + i ) { i f ( t h i s . r e p o s [ i ] . l a n g u a g e ) { l a n g u a g e s P . p u s h ( t h i s . r e p o s [ i ] . l a n g u a g e s _ u r l ) ; } } r e t u r n P r o m i s e . a l l ( l a n g u a g e s P ) ; }
  21. f u n c t i o n L a

    n g u a g e L i s t ( l a n g u a g e s ) { t h i s . l a n g u a g e s = [ ] ; v a r s u m m a r y = { } ; f o r ( v a r i = 0 , l e n = l a n g u a g e s . l e n g t h ; i > l e n ; + + i ) { f o r ( v a r l a n g i n l a n g u a g e s ) { i f ( ! s u m m a r y [ l a n g ] ) s u m m a r y [ l a n g ] = { l a n g u a g e : l a n g , c o u n t : 0 } ; s u m m a r y [ l a n g ] . c o u n t + = l a n g u a g e s [ i ] [ l a n g ] ; } } f o r ( v a r s i n s u m m a r y ) { t h i s . l a n g u a g e s . p u s h ( s ) ; } } L a n g u a g e L i s t . p r o t o t y p e . s o r t = f u n c t i o n ( s o r t e r / * l a n g u a g e S o r t e r * / ) { t h i s . l a n g u a g e s . s o r t ( s o r t e r ) ; }
  22. f u n c t i o n L a

    n g u a g e S o r t e r ( p r o p ) { t h i s . p r o p = p r o p } L a n g u a g e S o r t e r . p r o t o t y p e . g e t S o r t F u n c t i o n = f u n c t i o n ( ) { r e t u r n f u n c t i o n ( a , b ) { r e t u r n a [ t h i s . p r o p ] > b [ t h i s . p r o p ] ? 1 : a [ t h i s . p r o p ] < b [ t h i s . p r o p ] ? - 1 : 0 ; } }
  23. The content of the methods are roughly similar. t h

    i s is only used for organization. So let's focus on the imperative code (a bit simpler)
  24. . t h e n ( f u n c

    t i o n ( r e p o s ) { v a r y e a r R e p o = [ ] ; f o r ( v a r i = 0 , l e n = r e p o s . l e n g t h ; i < l e n ; + + i ) { i f ( r e p o s [ i ] . u p d a t e d _ a t . s l i c e ( 0 , 4 ) > = y e a r ) { y e a r R e p o . p u s h ( r e p o s [ i ] ) ; } } ; r e t u r n y e a r R e p o s ; } FILTER!
  25. ACTION PLAN extract the attribute `updated_at` extract the year in

    this attribute filter by comparing with given year
  26. EXTRACTION R . p r o p ( ' u

    p d a t e d _ a t ' ) ; So, what's the R.prop function is?
  27. In Ramda it is: GET DEFINITION R . p r

    o p = c u r r y ( f u n c t i o n ( p r o p , o b j ) { r e t u r n o b j [ p r o p ] ; } ) ; Without the curry wrapper prop is only the accessor function R . p r o p ( ' u p d a t e d _ a t ' ) ; / / r e t u r n s a f u n c t i o n t a k i n g o n e o b j e c t a r g u m e n t R . p r o p ( ' u p d a t e d _ a t ' ) ( o b j ) ; / / r e t u r n s o b j . u p d a t e d _ a t
  28. GET THE YEAR R . s l i c e

    ( 0 , 4 ) again: Ramda function are automatically curried.
  29. FILTERING IN ES5 A r r a y . p

    r o t o t y p e . f i l t e r ( p r e d i c a t e ) ; With ramda R . f i l t e r ( p r e d i c a t e , c o l l e c t i o n ) ; We need a predicate: function which returns a boolean Predicate: true iff obj.updated_at begins with 2015 or 2014.
  30. composing f and g is: R . c o m

    p o s e ( f , g ) ; / / s a m e a s f u n c t i o n c o m p o s e d ( f , g ) { r e t u r n f u n c t i o n ( x ) { r e t u r n f ( g ( x ) ) ; } } Right associative (read from the right)
  31. PREDICATE FUNCTION v a r p r e d i

    c a t e = R . c o m p o s e ( R . g t e ( y e a r ) , R . s l i c e ( 0 , 4 ) , R . g e t ( ' u p d a t e d _ a t ' ) ) ;
  32. REPOS UPDATED AFTER 2014 / / i m p e

    r a t i v e . t h e n ( f u n c t i o n ( r e p o s ) { v a r y e a r R e p o = [ ] ; f o r ( v a r i = 0 , l e n = r e p o s . l e n g t h ; i < l e n ; + + i ) { i f ( r e p o s [ i ] . u p d a t e d _ a t . s l i c e ( 0 , 4 ) > = y e a r ) { y e a r R e p o . p u s h ( r e p o s [ i ] ) ; } } ; r e t u r n y e a r R e p o s ; } / / f u n c t i o n a l . t h e n ( R . f i l t e r ( R . c o m p o s e ( R . g t e ( y e a r ) , R . s l i c e ( 0 , 4 ) , R . g e t ( ' u p d a t e d _ a t ' ) ) ) ) Even without ramda, you can use [].filter
  33. NEXT STEP: GET LANGUAGES . t h e n (

    f u n c t i o n ( r e p o s ) { v a r l a n g u a g e s P = [ ] ; f o r ( v a r i = 0 , l e n = r e p o s . l e n g t h ; i < l e n ; + + i ) { i f ( r e p o s [ i ] . l a n g u a g e s ) { l a n g u a g e s P . p u s h ( g e t R e p o L a n g u a g e s ( r e p o . l a n g u a g e s _ u r l ) ) ; } } r e t u r n l a n g u a g e s P ; } )
  34. MAP Apply a function over every element of a collection

    to create a new collection of transformed elements. R . m a p ( f u n c t i o n ( a ) { r e t u r n a + 3 } , [ 1 , 3 , 5 ] ) ; / / [ 4 , 6 , 8 ] R . m a p ( f u n c t i o n ( ) { r e t u r n ' f o o ' } , [ 1 , 3 , 5 ] ) ; / / [ ' f o o ' , ' f o o ' , ' f o o ' ] R . m a p ( f u n c t i o n ( a ) { r e t u r n a * a } , [ 1 , 3 , 5 ] ) ; / / [ 1 , 9 , 2 5 ] R . m a p ( p a r s e I n t , [ ' 1 0 ' , ' 1 0 ' , ' 1 0 ' ] ) ; / / [ 1 0 , 1 0 , 1 0 ] _ . m a p ( [ ' 1 0 ' , ' 1 0 ' , ' 1 0 ' ] , p a r s e I n t ) ; / / [ 1 0 , N a N , 2 ] < - c l a s s i c j s w t f Present in ES5: Array.prototype.map(mapper)
  35. GET LANGUAGES PER REPO . t h e n (

    f u n c t i o n ( r e p o s ) { v a r l a n g u a g e s P = [ ] ; f o r ( v a r i = 0 , l e n = r e p o s . l e n g t h ; i < l e n ; + + i ) { i f ( r e p o s [ i ] . l a n g u a g e s ) { l a n g u a g e s P . p u s h ( g e t R e p o L a n g u a g e s ( r e p o . l a n g u a g e s _ u r l ) ) ; } } r e t u r n l a n g u a g e s P ; } ) . t h e n ( P r o m i s e . a l l ) . t h e n ( R . f i l t e r ( R . g e t ( ' l a n g u a g e s ' ) ) ) . t h e n ( R . m a p ( R . c o m p o s e ( g e t R e p o L a n g u a g e s , R . g e t ( ' l a n g u a g e s _ u r l ' ) ) ) ) . t h e n ( P r o m i s e . a l l )
  36. AGGREGATE LANGUAGES STATS . t h e n ( f

    u n c t i o n ( l a n g u a g e s ) { v a r r e s u l t = { } ; f o r ( v a r i = 0 , l e n = l a n g u a g e s . l e n g t h ; i < l e n ; + + i ) { f o r ( v a r l a n g i n l a n g u a g e s [ i ] ) { i f ( ! r e s u l t [ l a n g ] ) r e s u l t [ l a n g ] = 0 ; r e s u l t [ l a n g ] + = l a n g u a g e s [ i ] [ l a n g ] ; } } ; r e t u r n r e s u l t ; } ) / / i n p u t [ { " j a v a S c r i p t " : 1 2 0 0 } , { " j a v a S c r i p t " : 1 3 3 7 , " s h e l l " : 4 0 } ] / / o u t p u t { " j a v a S c r i p t " : 2 5 3 7 , " s h e l l " : 4 0 }
  37. REDUCE Also known as fold in other languages [ ]

    . r e d u c e ( r e d u c e r , i n i t i a l V a l u e ) ; / / E S 5 r e d u c e r ( a c c u m u l a t o r , c u r r e n t V a l u e )
  38. EXAMPLE OF REDUCE get the sum of all element in

    a collection [ 1 , 3 , 5 ] . r e d u c e ( f u n c t i o n a d d e r ( s u m , n u m ) { r e t u r n s u m + n u m ; } , 0 ) ; f u n c t i o n a d d e r ( 0 , 1 ) - > r e t u r n 0 + 1 f u n c t i o n a d d e r ( 1 , 3 ) - > r e t u r n 1 + 3 f u n c t i o n a d d e r ( 4 , 5 ) - > r e t u r n 4 + 5 f i n a l r e s u l t : 9
  39. AGGREGATING LANGUAGES R . r e d u c e

    ( f u n c t i o n ( a g g r e g a t e d , l a n g u a g e s ) { R . f o r E a c h ( f u n c t i o n ( l a n g ) { i f ( ! a g g r e g a t e d [ l a n g ] ) a g g r e g a t e d [ l a n g ] = 0 ; a g g r e g a t e d [ l a n g ] + = l a n g u a g e s [ l a n g ] ; } ) ( R . k e y s ( l a n g u a g e s ) ) ; r e t u r n a g g r e g a t e d ; } , { } ) ;
  40. TRANSFORMING THE AGGREGATED OBJECT INTO AN ARRAY / / i

    n p u t { " j a v a S c r i p t " : 2 5 3 7 , " s h e l l " : 4 0 } / / o u t p u t [ { " l a n g u a g e " : " j a v a s c r i p t " , " c o u n t " : 2 5 3 7 } , { " l a n g u a g e " : " s h e l l " , " c o u n t " : 4 0 } ]
  41. TRANSFORMING THE AGGREGATED OBJECT INTO AN ARRAY R . c

    o m p o s e ( R . m a p ( f u n c t i o n ( p a i r ) { r e t u r n { l a n g u a g e : p a i r [ 0 ] , c o u n t : p a i r [ 1 ] } } ) , R . t o P a i r s / / { a : 1 , b : 2 } - > [ [ ' a ' , 1 ] , [ ' b ' , 2 ] ] )
  42. SORTING LANGUAGES / / i n p u t [

    { " l a n g u a g e " : " s h e l l " , " c o u n t " : 4 0 } , { " l a n g u a g e " : " j a v a s c r i p t " , " c o u n t " : 2 5 3 7 } ] / / o u t p u t [ { " l a n g u a g e " : " j a v a s c r i p t " , " c o u n t " : 2 5 3 7 } , { " l a n g u a g e " : " s h e l l " , " c o u n t " : 4 0 } ]
  43. SORTING BY COUNT R . s o r t B

    y ( R . p r o p ( ' c o u n t ' ) ) Sort in increasing order according to a key given by the function R . c o m p o s e ( R . r e v e r s e , R . s o r t B y ( R . p r o p ( ' c o u n t ' ) ) )
  44. R.pCompose \o/ RAMDA WITH PROMISES Same as compose, but if

    something returns a thenable, it'll be handled asynchronously.
  45. PUTTING IT ALL TOGETHER v a r g e t

    L a n g u a g e s = f u n c t i o n ( y e a r / * Y Y Y Y * / ) { r e t u r n R . p C o m p o s e ( s o r t L a n g u a g e s a g g r e g a t e L a n g u a g e s , P r o m i s e . a l l , g e t L a n g u a g e s , c h o o s e B y Y e a r ( y e a r ) , f e t c h R e p o s ) ( ) ; }
  46. v a r c h o o s e B

    y Y e a r = f u n c t i o n ( y e a r ) { r e t u r n R . f i l t e r ( R . c o m p o s e ( R . g t e ( y e a r ) , R . s l i c e ( 0 , 4 ) , R . g e t ( ' u p d a t e d _ a t ' ) ) ) ; }
  47. v a r g e t L a n g

    u a g e s = R . c o m p o s e ( R . m a p ( R . c o m p o s e ( g e t R e p o L a n g u a g e s , R . g e t ( ' l a n g u a g e _ u r l ' ) ) ) , R . f i l t e r ( R . g e t ( ' l a n g u a g e s ' ) ) )
  48. v a r a g g r e g a

    t e L a n g u a g e s = R . r e d u c e ( f u n c t i o n ( a g g r e g a t e d , l a n g u a g e s ) { r e t u r n R . f o r E a c h ( f u n c t i o n ( l a n g ) { i f ( ! a g g r e g a t e d [ l a n g ] ) a g g r e g a t e d [ l a n g ] = 0 ; a g g r e g a t e d [ l a n g ] + = l a n g u a g e s [ l a n g ] ; } ) ( R . k e y s ( l a n g u a g e s ) ) ; } , { } ) ;
  49. v a r s o r t L a n

    g u a g e s = R . c o m p o s e ( R . r e v e r s e , R . s o r t B y ( R . p r o p ( ' c o u n t ' ) ) ) ;
  50. WEIRD ORDER? PIPE AND PPIPE Mirror versions of compose and

    pCompose v a r g e t L a n g u a g e s = f u n c t i o n ( y e a r / * Y Y Y Y * / ) { r e t u r n R . p P i p e ( f e t c h R e p o s , c h o o s e B y Y e a r ( y e a r ) , g e t L a n g u a g e s , P r o m i s e . a l l , a g g r e g a t e L a n g u a g e s , s o r t L a n g u a g e s ) ( ) ; }
  51. ESSENCE OF FUNCTIONAL PROGRAMMING Combine (compose) functions with compose &

    curry Any loop can be expressed with a combination of map, filter, reduce
  52. WHAT DO WE GAIN WITH FP? Simplicity Easier decomposition of

    problems Code more closely tight to problem domain Which leads in turn to: Straightforward unit testing Simple concurrency
  53. CAVEATS Big surface API (similar to underscore/lodash actually) Not enough

    in itself: closures breaks compositions. Avoid states Need immutability FP is weird (at first). Start with map/filter to eliminate for loops
  54. RESOURCES , another FP style library stream based library (by

    the author of async) and , two libraries to bring efficient immutable data structure to js land. Functional programming in js Allong.es highland Immutable Mori
  55. Q&A