Recently, David Nolen of Cognitect held a webinar about “Designing Front End Applications Using core.async”. I’m a huge fan of David’s teaching style (see his tutorials on Enlive, LightTable+ClojureScript, Om Basic and Om Intermediate) so I cleared my calendar. I’m very excited about Clojure’s front-end stack, with ClojureScript, core.async, and Transit, and after this webinar, I have a much clearer idea of how core.async fits in.
UPDATE: The recording is now live: http://go.cognitect.com/core_async_webinar_recording
Here are my notes
- EXAMPLES – https://github.com/cognitect/async-webinar
- Problems with Asynchronous programming
- nesting callbacks is hard to maintain, read
- JS is single-threaded
- JS promises
- better looking code, somewhat easier to read, but still a lot of callbacks. Chaining, not as nested
- JS ES6 generators – extremely low level – core.async delivers the same benefits, but with a much higher level abstraction
- ClojureScript
- Cognitect – write the same types of apps, but simpler. Embrace and enhance the host
- Immutability
- ClojureScript – good interop with JS runtimes + libraries, with Clojure language platform
- Why ClojureScript?
- ClojureScript – good interop with JS runtimes + libraries, with Clojure language platform
- Leverage Google Closure Compiler/Library
- JS has many methods of packaging and delivery; Cljs only uses Closure
- Superior minification + compression
- dead code elimintation – include whatever you want, with no code bloat
- Battle-tested standard library – support back to IE6
- Core.Async
- based on CSP (communicating sequential processes) – naturally captures one-off async tasks AND async streams/queues (e.g. mouse movements, etc)
- Go blocks are a source transform that give the illusion of blocking, even in single-threaded context (i.e. JS hosts)
- Terminology
- Channels – conduit between different processes – put values in, take values out
- Buffers – channels use them to configure backpressure, handle bursts of events; you can customize semantics
- Transducers (new!) – efficient way to control how values enter and exit channel, e.g. map/filter
- Go blocks – async blocks of execution with the illusion of blocking operations
- Channels – conduit between different processes – put values in, take values out
- Core Operations
- putting values onto a channel
- taking values off a channel
- selecting over multiple channels (puts & takes)
- 10 short examples
- good enough first principles
- repo available – https://github.com/cognitect/async-webinar – build, launch, follow along in browser
- helpers
- includes examples of JS interop
- events→chan – listen for events, put them into the channel
- use core.async to deal with events, instead of callbacks
- mouse-loc→vec – turn events into vector
- Example1
- create a channel of clicks, block, and add a note
- <! – blocking read inside of go blocks
- Example 2 – same, but wait for 2 clicks
- Example 3 – 2 channels, wait for one from each channel
- Example 4 – shows how writing to a channel blocks that program until some other go block takes from the channel
- Example 5 – separate go block to consume the channel
- go blocks are like forking a process
- looks like 2 separate processes running together and communicating over channels
- Example 6 – stream processing
- use transducer to convert events before they come into the channel
- looks like an infinite loop – but getting a message off the click channel ends it
- alts! – select over multiple channels – non-deterministically read from whichever channel has data first
- line 140 – pattern matching on the channel
- transducers let you work at a higher level of abstraction and data like you like it
- Example 7 – more transducers
- discard events unless the y is divisible by 5
- loops can use the loop var bindings to record state
- Example 8 – use loop vars to store state
- Example 9 – more like real web
- next/prev buttons to loop through a list
- listen on prev/next channels to change vals, enable/disable prev/next
- Example 10 – bigger example, broken out into helpers
- more transducers – take all key presses, map the keyCode, filter by the set #{37 39}, and map them to :prev/:next
- efficient because all those transformations happen in one function, inside the channel
- click start, construct the keys channel
- async/merge – take any number of channels, merge them into one channel (e.g. prev, next, keys) → a single channel that spits out :prev/:next
- you can add more channels (e.g. left/right swipe on a tablet) and convert them to the same channels
- more transducers – take all key presses, map the keyCode, filter by the set #{37 39}, and map them to :prev/:next
- Code is easier to manage and reason about new sources of events
- Resources & Next Steps
- original announcement – http://clojure.com/blog/2013/06/28/clojure-core-async-channels.html
- Tim Baldrige Clojure/conj 2013 core.async talk (40 min vid) – http://www.youtube.com/watch?v=enwIIGzhahw
- server-side work
- portability between Clojure and ClojureScript
- Facebook React – http://facebook.github.io/react
- Works well with immutable data structures for UI
- ClojureScript libs – Om, Reagent, Quiescent, Reacl
- Q&A
- How do you compose small pieces into a large front-end application?
- Think about the abstract event types and handling, not concrete event sources
- Core.async lets you write little distributed elements inside your UI
- Components are independent and share a message language for coordination
- Chan function – up to 3 args
- int that sets buffer size or properties
- more useful in server context than UI
- on front end, best is to use size 1 and a transducer to transform the stream
- int that sets buffer size or properties
- Complex data?
- All pre-built React components can work with any ClojureScript React library
- Buffer size is 1 event – with data of arbitrary value
- If you’re already running JVM, it makes more sense to use Nashorn JS on JVM lib instead of Node
- You cannot monitor channels – they are purposely opaque
- How do you compose small pieces into a large front-end application?
Leave a Reply
You must be logged in to post a comment.