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

The Way to Fantasyland

672ba495b3eadfdb0daee60ed354bdff?s=47 wuct
November 05, 2017

The Way to Fantasyland

This is the slide from my talk about functional programming and Fantasyland in JavaScript Developer Conference (JSDC) 2017 in Taiwan.

672ba495b3eadfdb0daee60ed354bdff?s=128

wuct

November 05, 2017
Tweet

Transcript

  1. The Way to Fantasyland CT Wu JSDC 2017 wu_ct wuct

  2. None
  3. Divide and Conquer

  4. A good abstraction should be intuitive and universal.

  5. Mathematic

  6. (1 + 2) + 3 = 1 + (2 +

    3) (1 * 2) * 3 = 1 * (2 * 3)
  7. (a ∨ b) ∨ c = a ∨ (b ∨

    c) (a ∧ b) ∧ c = a ∧ (b ∧ c)
  8. max(max(1, 2), 3) = max(1, max(2, 3)) min(min(1, 2), 3)

    = min(1, min(2, 4))
  9. gcd(gcd(2, 4), 6) = gcd(2, gcd(4, 6)) lcm(lcm(2, 4), 6)

    = lcm(2, lcm(4, 6))
  10. The associativity is universal.

  11. ([1].concat([2])).concat([3]) = [1].concat([2].concat([3])) ("1".concat("2")).concat("3") = "1".concat("2".concat("3")) Even in Programming Languages

  12. Can we define this kind of abstractions in programming languages?

  13. It is functional programming.

  14. The name of the abstraction is Semigroup (1 + 2)

    + 3 = 1 + (2 + 3) (1 * 2) * 3 = 1 * (2 * 3) max(max(2, 4), 6) = max(2, max(4, 6)) min(min(2, 4), 6) = min(2, min(4, 6)) gcd(gcd(2, 4), 6) = gcd(2, gcd(4, 6)) lcm(lcm(2, 4), 6) = lcm(2, lcm(4, 6)) (a ∨ b) ∨ c = a ∨ (b ∨ c) (a ∧ b) ∧ c = a ∧ (b ∧ c) Value Operation
  15. Fantasyland Land Specification “Algebraic JavaScript Specification” An algebra is a

    set of values, a set of operators that it is closed under and some laws it must obey.
  16. The Genealogy of Fantasyland

  17. None
  18. #:: “is a member of” "fantasy" #:: String New types

    can be created via type constructors ["fantasy"] #:: Array String #[["fantasy"], ["land"#]] #:: Array (Array String) Type Signature Notation in Fantasyland
  19. #-> is an infix type constructor String #-> Number A

    function with multiple #-> is a curried function String #-> String #-> Number Type Signature Notation in Fantasyland
  20. Lowercase letters stand for type variables a #-> b #~>

    is an infix type constructor of a method a #~> b () stands for no argument () #-> a #=> expresses constraints on type variables Semigroup a #=> a #-> a Type Signature Notation in Fantasyland
  21. concat #:: Semigroup a #=> a #~> a #-> a

    Let’s Define Semigroup x.concat(y).concat(z) = x.concat(y.concat(z)) concat should satisfy the associative law…
  22. [1].concat([2]).concat([3]) "1".concat("2").concat("3") Array and String are already Semigroup by default

    In fact, we still need to add “fantasy-land/“ prefix to methods name, but it’s an implementation detail.
  23. const daggy = require('daggy') const Sum = daggy.tagged('Sum', ['value']) Sum(1)

    #// = { value: 1 } Sum(1).toString() #// = "Sum(1)" Daggy is an utility for creating constructors with named properties. Daggy The Utility
  24. const Sum = daggy.tagged('Sum', ['value']) Sum.prototype.concat = function(that) { return

    Sum(this.value + that.value) } Sum(1).concat(Sum(2)).concat(Sum(3)) #// = Sum(6) Sum as an Instance of Semigroup
  25. const All = daggy.tagged('All', [‘value']) All.prototype.concat = function(that) { return

    All(this.value #&& that.value) } All(false).concat(All(false)).concat(All(true)) #// = All(false) Boolean as an Instance of Semigroup (All)
  26. const Any = daggy.tagged('Any', [‘value']) Any.prototype.concat = function(that) { return

    Any(this.value #|| that.value) } Any(false).concat(Any(false)).concat(Any(true)) #// = Any(true) Boolean as an Instance of Semigroup (Any)
  27. const List = daggy.taggedSum('List', { Cons: ['head', 'tail'], #// Cons

    #:: a #-> List a #-> List a Nil: [] #// Nil #:: List a }) List.prototype.concat = function(that) { return this.cata({ Cons: (head, tail) #=> List.Cons(head, tail.concat(that)), Nil: () #=> that }) } List.Cons(1, List.Nil).concat(List.Cons(2, List.Nil)) #// = List.Cons(1, List.Cons(2, List.Nil)) Linked List as an Instance of Semigroup
  28. const First = daggy.tagged('First', ['value']) First.prototype.concat = function() { return

    this } First("A").concat(First("B").concat("C") #// = First("A") const Last = daggy.tagged('Last', ['value']) Last.prototype.concat = function(that) { return that } Last("A").concat(Last("B")).concat(Last("C")) #// = Last("B") More Semigroup Examples
  29. How can these things be useful?

  30. Let’s See a Real World Usage Example • You have

    a website providing several ways for users to sign up, e.g. email/password and social login. • An user creating two accounts accidentally wants to merge them now. • An user account can be an instance of Semigroup!
  31. const User = daggy.tagged('User', ['name', 'emails', 'isVerified', 'loginTimes']) User.prototype.concat =

    function(that) { return User( this.name.concat(that.name), this.emails.concat(that.emails), this.isVerified.concat(that.isVerified), this.loginTimes.concat(that.loginTimes) ) } User(First('CT'), ['ct@foo.com'], Any(false), Sum(6)) .concat( User(First('CT Wu'), ['ct@bar.com'], Any(true), Sum(13)) ) #// = User(First("CT"), ["ct@foo.com", "ct@bar.com"], Any(true), Sum(19)) User as an Instance of Semigroup
  32. How about parallel computing?

  33. We can code logic into types! • Types can be

    checked by compilers, so there’s no need for unit tests. • First, Any, Sum and other types we used here can be provided by libraries. • Functional programming languages, like PureScript, already providing these common types in the language.
  34. Can a set be an instance of Semigroup?

  35. (A ∪ B) ∪ C = A ∪ (B ∪

    C) (A ∩ B) ∩ C = A ∩ (B ∩ C)
  36. Set as an instance of Semigroup • Union and intersection

    are associative operations. • However, we need to be capable of comparing two elements are equal or not. • Setoid is the algebra!
  37. None
  38. Setoid equals #:: Setoid a #=> a #~> a #->

    Boolean equals should satisfy following laws: Reflexivity: a.equals(a) ##=== true Commutativity: a.equals(b) ##=== b.equals(a) Transitivity: if a.equals(b) and b.equals(c) then a.equals(c)
  39. None
  40. Monoid empty #:: Monoid m #=> () #-> m …

    and the laws: Right Identity: m.concat(M.empty()) is equivalent to m Left Identity: M.empty().concat(m) is equivalent to m
  41. Some Monoid implementation… String.empty = () #=> "" Sum.empty =

    () #=> Sum(0) Product.empty = () #=> Product(1) All.empty = () #=> All(true) Any.empty = () #=> Any(false) Max.empty = () #=> Max(-Infinity) Min.empty = () #=> Min(Infinity) Array.empty = () #=> [] List.empty = () #=> List.Nil First.empty = () #=> #// ? Last.empty = () #=> #// ?
  42. Reduce Data by the Monoid operation foldArray #:: Monoid m

    #=> Array m #-> m foldArray = array #=> array.reduce((a, b) #=> a.concat(b), []) foldArray(["A", "B", "C"]) #// = "ABC" foldArray([Sum(1), Sum(2)]) #// = Sum(3) In this sense, we could fold other types by…
 foldList #:: Monoid m #=> List m #-> m foldTree #:: Monoid m #=> Tree m #-> m if they can be reduced.
  43. None
  44. Foldable reduce #:: Foldable f #=> f a #~> ((b,

    a) #-> b, b) #-> b … and there is no law for Foldable!
  45. List.prototype.reduce = function(f, initial) { return this.cata({ Cons: (head, tail)

    #=> tail.reduce(f, f(initial, head)), Nil: () #=> initial, }) } List.Cons(1, List.Cons(2, List.Cons(3, List.Nil))) .reduce((a, b) #=> a + b, 0) #// = 6 Linked List as an Instance of Foldable
  46. fold #:: Foldable f #=> Monoid m #=> f m

    #-> m fold = M #=> foldable #=> foldable.reduce((acc, x) #=> acc.concat(x), M.empty()) fold(Sum)(List.Cons(Sum(1), List.Cons(Sum(2), List.Nil))) #// = Sum(3) fold(All)([All(true), All(false)]) #// = All(false) Fold Universally
  47. The force of abstraction.

  48. Let’s see another abstraction const inc = x #=> x

    + 1 [1].map(inc) #// = [2] Promise.resolve(1).then(inc) #// = Promise.resolve(2) Observable.of(1).map(inc) #// = Observable.of(2) They are similar … as usual, there is an abstraction over them already!
  49. None
  50. Functor map #:: Functor f #=> f a #~> (a

    #-> b) #-> f b … and the laws Identity: u.map(x #=> x) ##=== u Composition: u.map(f).map(g) ##=== u.map(x #=> g(f(x)))
  51. The Intuition of Functor [1] #:: Array Int Promise.resolve(1) #::

    Promise Int Observable.of(1) #:: Observable Int 1 2 [2] #:: Array Int Promise.resolve(2) #:: Promise Int Observable.of(2) #:: Observable Int map(x #=> x + 1) Notice that the types of Promise and Observable are overly simplified here. We should also consider error handling in their types.
  52. Can Set be an instance of Functor?

  53. No, because every element in a set should be a

    Setoid. map #:: Functor f #=> f a #~> (a #-> b) #-> f b b might be not a Setoid.
  54. Have You Seen This? if (a #!= undefined) { if

    (b #!= undefined) { if (c #!= undefined) { } } } Or these… TypeError: Cannot read property ‘…’ of undefined TypeError: … is not a function
  55. Another Useful Functor, Maybe const Maybe = daggy.taggedSum('Maybe', { Just:

    ['value'], Nothing: [] }) Maybe.prototype.map = function(f) { return this.cata({ Just: value #=> Maybe.Just(f(value)), Nothing: () #=> Maybe.Nothing }) } Maybe.Nothing.map(x #=> x.length) #// = Maybe.Nothing Maybe.Just([1, 2, 3]).map(x #=> x.length) #// = Maybe.Just(3)
  56. Function as an Instance of Functor Functor is an abstraction

    over unary type constructors… Functor f #=> f a #~> (a #-> b) #-> f b We know (#->) is a binary type constructor, 
 so (#->) r is an unary type constructor… ((#->) r a) #~> (a #-> b) #-> ((#->) r b) (r #-> a) #~> (a #-> b) #-> (r #-> b)
  57. Function as an Instance of Functor Function.prototype.map = function(f) {

    return x #=>f(this(x)) } const f = (x #=> x * 10).map(x #=> x + 7) f(8) #// = ?
  58. Compose is Just Functor Map map #:: Functor f #=>

    (a #-> b) #-> f a #-> f b map #:: (a #-> b) #-> (r #-> a) #-> (r #-> b) map = (f, g) #=> x #=> f(g(x)) #// = compose
  59. Learn once, apply everywhere.

  60. Question? wu_ct wuct