JSDC 2014 - functional java script, why or why not
-
Upload
chenghui-weng -
Category
Presentations & Public Speaking
-
view
105 -
download
2
Transcript of JSDC 2014 - functional java script, why or why not
Functional JavaScript, Why or Why Not?
JSDC 2014
bit.ly/jsdc2014-funjs
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?
It would bring you
Re-thinking about programming
Patterns efficiently make your works done
Fun -citons
Features of Functional Programmings
First-class function | Closure
High-order functions | Function composition
Purity | Managed side-effects | Laziness
Recursion | Tail-recursion optimization | (Type)
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...
function() {} is everything
Or () => { } if you're a lucky bastard
Use Firefox to embrace () => 'the power of ES6!'(Fat Arrow Functions)
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)
About the signature
Array Number → (Number → String) → Array String
[a] → (a → b) → [b]
"return value"function as argumentargument
a, b: type variables
var arr = [1, 2, 3]arr.push(4)
But what about...
=== return a completely new Array: arr → arr'=== Array Number → Number → Array Number
Immutable Data StructureBut in JavaScript we can 'pretend' we have it when we're playing with FP. Would discuss it later
Immutable Data StructureBut in JavaScript we can 'pretend' we have it when we're playing with FP. Would discuss it later
Facebook has an 'immutable-js' libraryhttps://github.com/facebook/immutable-js
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
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
var result = [];for (var i = 0; i < selectors.length; i++) { var selector = selectors[i]; result.push(document.querySelector(selector));}
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...
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...
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
People know what are you doing when they saw the 'map' and 'reduce'
If there is no or only few side-effects
urls.map((url) => Http.get(url) .filter((response) => response.status !== 404 ) .map((response) => Parser.comment(response)) .map((comment) => UI.renderComment(comment)) .execute()
urls.map((url) => Http.get(url) // map (URL -> IO) to [ URL ] .filter((response) => response.status !== 404 ) .map((response) => Parser.comment(response)) .map((comment) => UI.renderComment(comment)) .execute()
// If we have lazy IO & async mixed Monad // Transformer...will discuss it later
urls.map((url) => Http.get(url) // map (URL -> IO) to [ URL ] .filter((response) => response.status !== 404 ) .map((response) => Parser.comment(response)) .map((comment) => UI.renderComment(comment)) .execute()
// If we have lazy IO & async mixed Monad // Transformer...will discuss it later
[URL] → [IO a] → [Parsed b] → [DOM c]
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
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
var map = (xs, fn) => { return xs.reduce((acc, x) => { return acc.concat([ fn(x) ]); }, []); };
Recommends to use and get understand the 'transducer.js'
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
map (not.odd.read) ["1","2","3"]
[1,2,3].map(compose(not, odd, read))
Recommends to use lo-dash library to acquire the compose functions
m a → (a → b) → m bIO a → (a → b) → IO b[] a → (a → b) → [] b
['1','2','3'].map( $(not, odd, read) )[ ] Number
m a → (a → b) → m bIO a → (a → b) → IO b[] a → (a → b) → [] b
HTTP a → (a → b) → HTTP b
HTTPServer.map( $(not, odd, read) )HTTP Request
m a → (a → b) → m bIO a → (a → b) → IO b[] a → (a → b) → [] b
HTTP a → (a → b) → HTTP bMaybe a → (a → b) → Maybe b
maybeRead.map( $(not, odd, read) )Maybe a
m a → (a → b) → m bIO a → (a → b) → IO b[] a → (a → b) → [] b
HTTP a → (a → b) → HTTP bMaybe a → (a → b) → Maybe b
Now we have a same map & functionfor the different contexts
m a → (a → b) → m bIO a → (a → b) → IO b[] a → (a → b) → [] b
HTTP a → (a → b) → HTTP bMaybe a → (a → b) → Maybe b
Now we have a same map & functionfor the different contexts
In fact this general map called 'fmap'
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
$(not, odd, read)
List
a
List
b( a → b)
List#fmapapply the function on every element of the list
fmap
$(not, odd, read)
HTTP
a
HTTP
b( a → b)
HTTP#fmapreceive the request 'a' and response 'b' to client
fmap
List
Request
List
Response( Request → Response)
Needn't know how to apply on every element (iterate) within the list
Contexts could even be stockpiled to do complex computations
Although stockpiling is beyond Functor. See Appendix: Monad Transformer
HTTP
Request
HTTP
Response
List List
( Request →Response)Needn't know how to do IO,
networking, etc.Needn't know how to map
it to all requests.
HTTP
Request
HTTP
Response
List List
( Request →Response)Needn't know how to do IO,
networking, etc.Needn't know how to map
it to all requests.
[HTTP Request] (buffering?)
HTTP [Request] (SPDY?)
List
Request
List
Response
HTTP HTTP
( Request →Response)Needn't know how to do IO,
networking, etc.Needn't know how to map
it to all requests.
HTTP
Request
HTTP::200
Response( Request →Response)
( a → b )
The 'fmap' only allow (a → b), not (a → m b)So we can't control which sub-context should be instanced
HTTP
Request
HTTP::404
Response( Request →Response)
( a → b )
The 'fmap' only allow (a → b), not (a → m b)So we can't control which sub-context should be instanced
HTTPServer>>= (\req -> login)>>= (\authReq -> case (doAuth authReq) of
True -> content False -> authError))
HTTPServer>>= (\req -> login)>>= (\authReq -> case (doAuth authReq) of
True -> content False -> authError))
the 'bind' function, infix
HTTP::403
HTTP::200
let content response = HTTP200 responselet authError response = HTTP403 response(\authReq -> case (doAuth authReq) of
True -> content False -> authError)
( a → m b )
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)
(\req -> login) (\authReq -> doAuth...)>>= (......)>>=
Map to all elements
Map to all elements
List Monad
a → m c
a -> m b b -> m c>>=
actionFoo = actionA >>= actionBactionBar = actionFoo >>= actionC
(and so on...)
getStringforkIO
openFile readTVar
+, -, *...compress
tailpar
writeArray
a → m bwhile m /IO, HTTP, UI, ...
a → b
getStringforkIO
openFile readTVar
+, -, *...compress
tailpar
writeArray
a → m bwhile m /IO, HTTP, UI, ...
a → b
Embed them rationallywith the 'bind' function
from Gaia/System/notifications.js (v2.0)
line: 290 ~ 490 (200 lines)
- Create notification
- Detecting gesture
- Append notification
- Play sound
- Color one container
- Scroll container to top
...
from Gaia/System/notifications.js (v2.0)
line: 290 ~ 490 (200 lines)
There are so many
requests
from so many different
contexts
from Gaia/System/notifications.js (v2.0)
line: 290 ~ 490 (200 lines)
It's possible to use FP ideas,
to response these requests
with individual logic units is
trivial & reasonable
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
...
There is no way allowa Context-ed value to escape
Yes, I know Comonad and unsafe- can do that, but...
HTTPServer>>= (\req -> login)>>= (\authReq -> case (doAuth authReq) of
True -> content False -> authError))
Can only access the value when you're in the context
HTTPServer>>= (\req -> login)>>= (\authReq -> case (doAuth authReq) of
True -> content False -> authError))
let HTTPValue =
doSomething HTTPValue ...No way to do that!
extract
Promise(() => {...}) .then((a) => {...}) .then((b) => {...}) .then((c) => {...}) .then((d) => {...}).extract();
var promisedValue =
No way to do that!doSomething(promisedValue); ...
Promise(() => {...}) .then((a) => {...}) .then((b) => {...}) .then((c) => {...}) .then((d) => {...});
Context: Ensure the following step executes only after the previous one get done.
$('some-selector') .each(...) .animate(...) .append(...)
Context: Select, manipulate and check the DOM element(s)
_.chain(someValue) .filter(...) .groupBy(...) .map(...) .reduce(...)
Context: Guarantee the value would be transformed by lo-dash functions
ensure() .frame() .element(...) .actions() .pulls(0, 400) .perform() .must(...)
Context: Ensure the integration test only do what user can do, instead of magic manipulations
Promise(() => {...}) .then((a) => {...}) .then((b) => {...}) .then((c) => {...}) .then((d) => {...});
$('some-selector') .each(...) .animate(...) .append(...)
_.chain(someValue) .filter(...) .groupBy(...) .map(...) .reduce(...)
DOM _Async
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
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
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
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
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
...?
But things become crazy when the 'Monad' need to mix with Promise
(to support async operations natively)
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)); });
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)); });
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
But the most important things isit works!
When I see a bird that walks like a Monad and swimslike a Monad and quacks like a Monad, I call that bird a Monad
But how much we gain depends on how much we paid
In my experience the 'simple' fluent interfacewould be not enough soon
The missing part: how to stockpile different Contexts?
List
Request
List
Response
HTTP HTTP
( Request →Response)
The missing part: how to stockpile different Contexts?
List
Request
List
Response
HTTP HTTP
( Request →Response)
(See Appendix. Yes, we have a 200+ slides...)
Behavior trigger event
Data changed
View redraw
Let's think about what is an GUI program...
It's so simple, right?
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
And Flux looks like...
Yampa - a Functional Reactive Programming framework in Haskell
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
- 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
- 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
How to contribute to FirefoxOS
a -> m n b
CSE230 Wi14 - Monad Transformers
When you have many Monads
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
When you have many Monads
HTTP
Request
HTTP
Response
Database Database
( Request →Response)
We want to process HTTP as usual
Meanwhile we may perform somedatabase R/W at the same time
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 everystep's error and handle it
Monad Transformer
HTTP
Request
HTTP
Response
m m
>>=
Perform m first (ex: Logger)Then perform the target Monad (HTTP)
It's somewhat of the AOP concept
HTTPTransformer
HTTPLogger m
The transformer only know the inner one is the target Monad it can handle withex: (MaybeT → Maybe, HTTPT → HTTP)
Request
HTTPT (Transformer)
>>=
How Monad Transformer Works
HTTPLogger m
The transformer only know the inner one is the target Monad it can handle withex: (MaybeT → Maybe, HTTPT → HTTP)
Request
HTTPT ( "T"ransformer ) would not care what the outer monad is
HTTPT (Transformer)
>>=
How Monad Transformer Works
HTTPLogger 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
HTTPLogger 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
HTTPLogger 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
HTTPLogger 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_monad_transformer:_MaybeT
( a → m n b )
How Monad Transformer Works
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)
PromiseMaybeTNow it can stockpile arbitrary PromiseMonad on one PromiseMaybe monadic action
https://github.com/snowmantw/warmfuzzything.js/blob/master/maybet.js
PromiseMaybeTNow 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