1.4k

# The Way to Fantasyland

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

## wuct

November 05, 2017

## Transcript

2. None

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))

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 Speciﬁcation “Algebraic JavaScript Speciﬁcation” An algebra is a

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

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 inﬁx 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 inﬁx 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 Deﬁne Semigroup x.concat(y).concat(z) = x.concat(y.concat(z)) concat should satisfy the associative law…
22. ### .concat().concat() "1".concat("2").concat("3") Array and String are already Semigroup by default

In fact, we still need to add “fantasy-land/“ preﬁx 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

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

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.

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: Reﬂexivity: 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

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

+ 1 .map(inc) #// =  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  #:: Array Int Promise.resolve(1) #::

Promise Int Observable.of(1) #:: Observable Int 1 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 simpliﬁed here. We should also consider error handling in their types.

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 undeﬁned 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