A talk in JSDC.tw 2014. I introduce the advantage and disadvantage to write JavaScript in functional style. It covers simple Functional Programming concepts, how JavaScript becomes more functional, and all the difficulties people may encounter.
14. This talk is about
Concepts
Features
Trade-Off
Libraries
Frameworks
15. This talk is NOT about
20 mins to write a blog (you had enough of it, right?)
42 Tools for your toolbox (although I do recommend to use some libraries)
Hammer on your hand and everything looks like a nail
Brainwash believe whatever you want; so, hacking, not blaming
Lambda Calculus ...wait, or should we put it in slides?
22. It would bring you
Re-thinking about programming
Patterns efficiently make your works done
Fun -citons
23. Features of Functional Programmings
First-class function | Closure
High-order functions | Function composition
Purity | Managed side-effects | Laziness
Recursion | Tail-recursion optimization | (Type)
24. Features of Functional Programmings
First-class function | Closure
High-order functions | Function composition
Purity | Managed side-effects | Laziness
Recursion | Tail-recursion optimization | (Type)
JavaScript Ready
Need some hard works
Impossible if runtime
doesn't support it (well)
Discuss it later...
26. function() {}
is everything
Or () => { } if you're
a lucky bastard
Use Firefox to embrace () => 'the power of ES6!'
(Fat Arrow Functions)
27. 65535 -- Number (*yes, it's a function)
65535 + 1 -- Number β Number β Number
[1] -- Array Number
[1].push(2) -- Array Number β Number β
Array Number
[1, 2, 3].length -- Array Number β Number
[1, 2, 3].map((x) => `${ x }`) -- Array Number β (Number β String)
β Array String
Use FirefoxNightly to embrace `the ${power} of ES6!`
(Quasi-Literals)
28. About the signature
Array Number β (Number β String) β Array String
[a] β (a β b) β [b]
"return value"function as argumentargument
a, b: type variables
29. var arr = [1, 2, 3]
arr.push(4)
But what about...
30. var arr = [1, 2, 3]
arr.push(4)
But what about...
=== return a completely new Array: arr β arr'
=== Array Number β Number β
Array Number
32. Immutable Data Structure
But in JavaScript we can 'pretend' we have it
when we're playing with FP. Would discuss it later
33. Immutable Data Structure
But in JavaScript we can 'pretend' we have it
when we're playing with FP. Would discuss it later
Facebook has an 'immutable-js' library
https://github.com/facebook/immutable-js
35. 65535 -- Number (*yes, it's a function)
65535 + 1 -- Number β Number
[1] -- Array Number
[1].push(2) -- Array Number β Number β
Array Number
[1, 2, 3].length -- Array Number β Number
[1, 2, 3].map((x) => `${ x }`) -- Array Number β (Number β String)
β
Array String
So now we have the Transformation
42. High-Order Functions is useful
map:: [a] β (a β b) β [b]
reduce:: [a] β (a β b β b) β [a] β b
43. High-Order Functions is useful
map:: [a] β (a β b) β [b]
reduce:: [a] β (a β b β b) β [a] β b
-- Note: these are *NOT* correct signatures in Haskell
-- but in JavaScript, we can treat [1,2,3].map as map::[a]...
-- which makes the code matches the type better
48. for (var r = 0; r < records.length; r += 1) {
var record = records[r];
var typeStr = NfcUtils.toUTF8(record.type);
if (NfcUtils.equalArrays(record.type, NDEF.RTD_TEXT)) {
poster.text = poster.text || {};
var textData = NDEF.payload.decodeText(record.payload);
if (poster.text[textData.language]) {
// According to NFCForum-SmartPoster_RTD_1.0 3.3.2,
// there MUST NOT be two or more records with
// the same language identifier.
return null;
// to be continue...
51. return records.reduce((poster, record) => {
var typeStr = NfcUtils.toUTF8(record.type);
if (NfcUtils.equalArrays(record.type, NDEF.RTD_TEXT)) {
poster.text = poster.text || {};
var textData = NDEF.payload.decodeText(record.payload);
if (poster.text[textData.language]) {
// According to NFCForum-SmartPoster_RTD_1.0 3.3.2,
// there MUST NOT be two or more records with
// the same language identifier.
return null;
// to be continue...
52. return records.reduce((poster, record) => {
var typeStr = NfcUtils.toUTF8(record.type);
if (NfcUtils.equalArrays(record.type, NDEF.RTD_TEXT)) {
poster.text = poster.text || {};
var textData = NDEF.payload.decodeText(record.payload);
if (poster.text[textData.language]) {
// According to NFCForum-SmartPoster_RTD_1.0 3.3.2,
// there MUST NOT be two or more records with
// the same language identifier.
return null;
// to be continue...
A real case in Gaia project: Bug 1039245
53. People know what are you doing when
they saw the 'map' and 'reduce'
54. People know what are you doing when
they saw the 'map' and 'reduce'
If there is no or only few side-effects
55. It can be more powerful if you play
with the type
63. forEach:: [a] β (a β SideEffect; will discuss it later)
filter:: [a] β (a β Bool) β [a] * the type is similar with map
groupBy:: [a] β (a β a β Bool) β [[a]] * lo-dash has it
zipWith: [a] β[b] β (a β b β c) β [c] * worth to implement
64. forEach:: [a] β (a β SideEffect; will discuss it later)
filter:: [a] β (a β Bool) β [a] * the type is similar with map
groupBy:: [a] β (a β a β Bool) β [[a]] * lo-dash has it
zipWith: [a] β[b] β (a β b β c) β [c] * worth to implement
Recommends to use lo-dash library to acquire these
functions
66. var map = (xs, fn) => {
return xs.reduce((acc, x) => {
return acc.concat([ fn(x) ]);
}, []); };
Recommends to use and get understand the 'transducer.js'
81. Safety if small functions are safe, the larger one is safe, too
Reusability no need to create new functions
Flexibility compose anything you want at anytime
Composition is good for you
90. [a] β (a β b) β [b]
['1','2','3'].map( $(not, odd, read) )
91. m a β (a β b) β m b
['1','2','3'].map( $(not, odd, read) )
92. m a β (a β b) β m b
IO a β (a β b) β IO b
getString.map( $(not, odd, read) )
IO String
93. m a β (a β b) β m b
IO a β (a β b) β IO b
[] a β (a β b) β [] b
['1','2','3'].map( $(not, odd, read) )
[ ] Number
94. m a β (a β b) β m b
IO a β (a β b) β IO b
[] a β (a β b) β [] b
HTTP a β (a β b) β HTTP b
HTTPServer.map( $(not, odd, read) )
HTTP Request
95. m a β (a β b) β m b
IO a β (a β b) β IO b
[] a β (a β b) β [] b
HTTP a β (a β b) β HTTP b
Maybe a β (a β b) β Maybe b
maybeRead.map( $(not, odd, read) )
Maybe a
96. m a β (a β b) β m b
IO a β (a β b) β IO b
[] a β (a β b) β [] b
HTTP a β (a β b) β HTTP b
Maybe a β (a β b) β Maybe b
Now we have a same map & function
for the different contexts
97. m a β (a β b) β m b
IO a β (a β b) β IO b
[] a β (a β b) β [] b
HTTP a β (a β b) β HTTP b
Maybe a β (a β b) β Maybe b
Now we have a same map & function
for the different contexts
In fact this general map called 'fmap'
98. List apply the function to every element ([1,2,3])
HTTP receive request and response with the handled content
Maybe if previous one is Nothing, do not apply the function
Same fmap; different meaning
99. The fmap is a common interface
that all contexts are agreed with
142. (>>=):: m a β ( a β m b ) β m b
a β m bm a
>>=m a β a a β b; b β m b
unwrap wrap
143. Allow inject ( a β m b ) means allow
we control the way to wrap the value
144. HTTPServer
>>= (req -> login)
>>= (authReq -> case (doAuth authReq) of
True -> content
False -> authError))
145. HTTPServer
>>= (req -> login)
>>= (authReq -> case (doAuth authReq) of
True -> content
False -> authError))
the 'bind' function, infix
HTTP::403
HTTP::200
146. let content response = HTTP200 response
let authError response = HTTP403 response
(authReq -> case (doAuth authReq) of
True -> content
False -> authError)
( a β m b )
149. HTTPServer
>>= (req -> login)
>>= (authReq -> case (doAuth authReq) of
True -> content
False -> authError))
The bound function stil don't need know
how to open IO, networking, etc.
(a β m b)
150. We still keep the same interface,
different implementations feature
167. DOM
Create notification
Change container's style
UI
Append notification
Scroll container to top
Gesture
Detect gesture on notification
Sound
Play sound
Asynchronous
Manage asynchronous operations
Conditional Statements
If...else to do or not to do things
I/O
Get/write data and control device
...
Functor
Monad
Monad Transformer
High-order Function
Partial Application
Curry
...
178. HTTPServer
>>= (req -> login)
>>= (authReq -> case (doAuth authReq) of
True -> content
False -> authError))
Can only access the value when you're in the context
179. HTTPServer
>>= (req -> login)
>>= (authReq -> case (doAuth authReq) of
True -> content
False -> authError))
let HTTPValue =
doSomething HTTPValue ...
No way to do that!
extract
181. Promise(() => {...})
.then((a) => {...})
.then((b) => {...})
.then((c) => {...})
.then((d) => {...}).extract();
var promisedValue =
No way to do that!
doSomething(promisedValue); ...
182. In fact what we could learn from
Monad is not only the type & rules
183. But the idea to control different
computations in different Contexts
184. Promise(() => {...})
.then((a) => {...})
.then((b) => {...})
.then((c) => {...})
.then((d) => {...});
Context: Ensure the following
step executes only after the
previous one get done.
191. threeCoins = do
a <- randomSt
b <- randomSt
c <- randomSt
return (a,b,c)
main = do
a <- ask "Name?"
b <- ask "Age?"
return ()
IO
add mx my = do
x <- mx
y <- my
return (x + y)
MaybeState
192. threeCoins = do
a <- randomSt
b <- randomSt
c <- randomSt
return (a,b,c)
main = do
a <- ask "Name?"
b <- ask "Age?"
return ()
IO
add mx my = do
x <- mx
y <- my
return (x + y)
MaybeState
the 'do' notification
196. Lazy vs eager: sometimes it's reasonable to be lazy
Flow control for asynchronous operations is important
Type in some critical places we still need the information
Laws is it possible to follow the Monad Laws?
Requirements to get closer with real Monads
197. It's easy to make our 'Monad'
lazy with some type supporting
198. var action = (new Maybe()).Just(3)
.then((v) => {
return (new Maybe()).Just(v+99); })
.then((v) => {
return (new Maybe()).Just(v-12); })
.then((v) => {
return (new Maybe()).Nothing(); })
.then((v) => {
return (new Maybe()).Just(v+12); })
// Execute it with `action.done()`.
action = (Just 3)
>>= v -> return (v + 99)
>>= v -> return (v - 12)
>>= v -> Nothing
>>= v -> return (v + 12)
https://github.com/snowmantw/
warmfuzzything.js/blob/master/maybe.js
199. var action = (new Maybe()).Just(3)
.then((v) => {
return (new Maybe()).Just(v+99); })
.then((v) => {
return (new Maybe()).Just(v-12); })
.then((v) => {
return (new Maybe()).Nothing(); })
.then((v) => {
return (new Maybe()).Just(v+12); })
// Execute it with `action.done()`.
action = (Just 3)
>>= v -> return (v + 99)
>>= v -> return (v - 12)
>>= v -> Nothing
>>= v -> return (v + 12)
https://github.com/snowmantw/
warmfuzzything.js/blob/master/maybe.js
...?
200. But things become crazy when the
'Monad' need to mix with Promise
(to support async operations natively)
201. action = (Just 3)
>>= v -> return (v + 99)
>>= v -> return (v - 12)
>>= v -> Nothing
>>= v -> return (v + 12)
https://github.com/snowmantw/
warmfuzzything.js/blob/master/promise_maybe.js
var action = (new PromiseMaybe()).Just(3)
.then((mSelf, v) => {
mSelf.returns((new PromiseMaybe).Just(v+99)); })
.then((mSelf, v) => {
setTimeout(function() { // Only for test. Meaningless.
mSelf.returns((new PromiseMaybe).Just(v-12));
}, 3000); })
.then((mSelf, v) => {
mSelf.returns((new PromiseMaybe).Nothing()); })
.then((mSelf, v) => {
mSelf.returns((new PromiseMaybe).Just(v+12)); });
202. action = (Just 3)
>>= v -> return (v + 99)
>>= v -> return (v - 12)
>>= v -> Nothing
>>= v -> return (v + 12)
https://github.com/snowmantw/
warmfuzzything.js/blob/master/promise_maybe.js
var action = (new PromiseMaybe()).Just(3)
.then((mSelf, v) => {
mSelf.returns((new PromiseMaybe).Just(v+99)); })
.then((mSelf, v) => {
setTimeout(function() { // Only for test. Meaningless.
mSelf.returns((new PromiseMaybe).Just(v-12));
}, 3000); })
.then((mSelf, v) => {
mSelf.returns((new PromiseMaybe).Nothing()); })
.then((mSelf, v) => {
mSelf.returns((new PromiseMaybe).Just(v+12)); });
203. action = (Just 3)
>>= v -> return (v + 99)
>>= v -> return (v - 12)
>>= v -> Nothing
>>= v -> return (v + 12)
https://github.com/snowmantw/
warmfuzzything.js/blob/master/promise_maybe.js
var action = (new PromiseMaybe()).Just(3)
.then((mSelf, v) => {
mSelf.returns((new PromiseMaybe).Just(v+99)); })
.then((mSelf, v) => {
setTimeout(function() { // Only for test. Meaningless.
mSelf.returns((new PromiseMaybe).Just(v-12));
}, 3000); })
.then((mSelf, v) => {
mSelf.returns((new PromiseMaybe).Nothing()); })
.then((mSelf, v) => {
mSelf.returns((new PromiseMaybe).Just(v+12)); });
It can be better, but the real
problem is it's implementation
is very tricky
205. But the most important things is
it works!
When I see a bird that walks like a Monad and swims
like a Monad and quacks like a Monad, I call that bird a Monad
206. But the most important things is
it works!
(although it doesn't follow the laws)
216. The missing part: how to stockpile different Contexts?
List
Request
List
Response
HTTP HTTP
( Request βResponse)
217. The missing part: how to stockpile different Contexts?
List
Request
List
Response
HTTP HTTP
( Request βResponse)
(See Appendix. Yes, we have a 200+ slides...)
226. Behavior trigger event
Data changed
View redraw
Let's think about what is an GUI program...
Can be done purely, while
IO is relatively simple than drawing
Lots of side-effects and
trolling data anytime
237. It's great because we can build a full
Functional Programming stack on it
238.
239. It's great because we can build a full
Functional Programming stack on it
with Function Composition, Monad, Partial Application, Curry,
Monad Transformer, and other useful features in JavaScript
259. - lo-dash is your friend
- transducer in JavaScript is a good way to understand
reducing deeply
- immutable-js make your code purer
- React & Flux bring you a whole new FRP world
260. - jQuery is a Monad (no, not really, see the comments)
- Learn You a Haskell a good way to learn about Monad
- Typeclassopedia classical article about Typeclasses
- Foldr Foldl Foldl' detailed article about reducing
- CSP and transducers about Transducer in JavaScript
267. a -> m n b
CSE230 Wi14 - Monad Transformers
When you have many Monads
268. When you have many Monads
HTTP
Request
HTTP
Response
Logger Logger
( Request βResponse)
269. When you have many Monads
HTTP
Request
HTTP
Response
Logger Logger
( Request βResponse)
We want to process HTTP as usual
Meanwhile we want every request-response
been logged
270. When you have many Monads
HTTP
Request
HTTP
Response
Database Database
( Request βResponse)
We want to process HTTP as usual
Meanwhile we may perform some
database R/W at the same time
271. When you have many Monads
HTTP
Request
HTTP
Response
Error Error
( Request βResponse)
We want to process HTTP as usual
Meanwhile we want to capture every
step's error and handle it
274. HTTP
Logger m
The transformer only know the inner one
is the target Monad it can handle with
ex: (MaybeT β Maybe, HTTPT β HTTP)
Request
HTTPT (Transformer)
>>=
How Monad Transformer Works
275. HTTP
Logger m
The transformer only know the inner one
is the target Monad it can handle with
ex: (MaybeT β Maybe, HTTPT β HTTP)
Request
HTTPT ( "T"ransformer ) would not
care what the outer monad is
HTTPT (Transformer)
>>=
How Monad Transformer Works
276. HTTP
Logger m#bind
First transformer would call the outer
one's bind function to apply the rule on
the inner monadic value, and dig into the
second layer (inner monad)Request
apply m#bind on the
HTTP monadic value
HTTPT doesn't know
what the 'm' is
How Monad Transformer Works
277. HTTP
Logger m#bind
First transformer would call the outer
one's bind function to apply the rule on
the inner monadic value
Request
apply m#bind on the
HTTP monadic value
HTTPT doesn't know
what the 'm' is
How Monad Transformer Works
For this example, it means to perform the logging
278. HTTP
Logger m#bind
And then dig into the second layer
(inner monad)
Request
apply m#bind on the
HTTP monadic value
HTTPT doesn't know
what the 'm' is
How Monad Transformer Works
Now we're in the inner monad context
279. HTTP
Logger m
Then transformer apply the specific
Monad's binding rules on the inner
monadic value, including to call the
embedded function, just like what the
ordinary Monad does, but now we get
(m n b) rather than (m b)
Request Request
HTTP
Response
>>=
Logger m
http://en.wikibooks.
org/wiki/Haskell/Monad_transformers#A_simple_mon( a β m n b )
How Monad Transformer Works
280. Step by Step
Outer 'Bind' applied on the monadic value
m (n a) β m (n a)'
Inner 'Bind' applied on the monadic value
m (n a)' β m (n b)
Wrap It Back while it still doesn't know what the m is
m (n a)' β m (n b)
281. A not so successful try to implement
it in JavaScript
282. PromiseMaybeT
Now it can stockpile arbitrary PromiseMonad on one
PromiseMaybe monadic action
https://github.com/snowmantw/
warmfuzzything.js/blob/master/maybet.js
283. PromiseMaybeT
Now it can stockpile arbitrary PromiseMonad on one
PromiseMaybe monadic action
But since our basic 'Monad' is tricky, the transformer, is tricky, too
https://github.com/snowmantw/
warmfuzzything.js/blob/master/maybet.js