I just fork mori and add core.async to it

Jichao Ouyang
4 min readSep 7, 2015

--

mori is awesome when @swannodette exporting almost all awesome thing from ClojureScript to vanilla JavaScript.

But when I looking at the source code, it seems possible to port almost every function from ClojureScript to JavaScript by just using the macro mori-export. So I just give it a little try on the thing I always dream of that I can using in vanilla JavaScript — core.async.

so i just fork mori and give it a name conjs so i can npm install it in my own project(im not sure if EPL license allow me to rename it, but it’s just called conjs on npm, the library is still named mori,never intended to take over credit).

take 1

All I need to do is really simple as exporting all methods of Channel.

(mori-export async.chan async/chan)
(mori-export async.take$ async/take!)
(mori-export async.put$ async/put!)
(mori-export async.doAlts async/do-alts)
(mori-export async.timeout async/timeout)

these are some of most useful methods, and then it just works 😱 although just callback for now.

    it('take and put from channel',function(done) {
var c = async.chan()
async.take$(c ,function(x){
expect(x).toBe('something in channel')
done()
})
async.put$(c, 'something in channel')
})

event alts works

    it('race channel', function(done) {
var c1 = async.chan()
var c2 = async.chan()

async.doAlts(function(v) {
expect(mori.get(v, 0)).toBe('c1')
expect(mori.equals(c1, v.a(1))).toBe(true)
done()
},[c1,c2])
async.put$(c1, 'c1')
async.put$(c2, 'c2')
})

take 2

now Channel work but in a ugly callback way, what can I make it look better and nicer is to making it as Promise.

(defn ^:export put [chan val]
(goog/Promise. (fn [resolve, reject] (async/put! chan val (fn [res] (resolve res))))))
(defn ^:export take [chan]
(goog/Promise. (fn [resolve, reject] (async/take! chan (fn [res] (resolve res))))))
(defn ^:export alts [chans]
(goog/Promise. (fn [resolve, reject] (async/do-alts (fn [res] (resolve res)) chans))))

what im doing is just return a goog.Promise, in the promise, once the put/take callback is called, the promise will be resolved.

now we got a slightly better looking core.async channel

async.take(c)
.then(function(x){
expect(x).toBe(‘something in channel’)
})
.then(done)

but still far away from how it should look like in core.async’s go block.

you know the go block is a macro who generator proper state machine for corresponding body inside. there is no way to port a macro to JavaScript since macros expand at compile time, not runtime.

So I guess this is the best looking core.async that I can port to vanilla JavaScript, unless you’re using Babel and writing ES7 code.

Async Function

In ES7 there is propose of async function, and with babel, you can use it right now. And the nice thing is it’s compatible with Promise.

var a = mori.async;
(async function(){
var v = await a.atls([c1,c2])
expect(v.get(0)).toBe('c1')
expect(mori.equals(c1, v.get(1))).toBe(true)
})()

(async function(){
await a.put(c1, 'c1')
console.log('put c1 into c1')
})()

I guess my project is using babel so I’m totally happy with the core.async port from ClojureScript, event if without babel, a promise is still good enough for JavaScript style of CSP though.

And… One More Thing…

If you programming JavaScript alot, you will probably agree that Immutable.js have better JS style then mori.

mori.get(vector, 1)
// no, ^^^ it's clojure flavor
// this vvvvv is javascript
vector.get(1)

But I like mori’s performance. So I guess one thing I can do is make mori’s datastructure more like normal JavaScript’s datastructure api

so i wrote a simple macro to export all methods on datastructure’s prototype

(defmacro property-export [exportp corep]
`(js/goog.exportSymbol
~(str “mori.” exportp)
(fn [& args#] (apply ~corep (cons (~’js* “this”) args#)))))

so, when I export vector’s get mothod

(property-export “Vector.prototype.get” cljs.core/get)

I’ll be able to use as get as a method instead a private uglified method

using this macro, I exported all methods

describe(‘Vector’, function() {
var vec = m.vector(1,2,3,4)
it(‘expose methods’,function() {
expect(vec.get(1)).toBe(2);
expect(vec.conj(5,6).toString()).toBe(‘[1 2 3 4 5 6]’)
expect(vec.empty().toString()).toBe(‘[]’)
expect(vec.equiv(m.vector(1,2,3,4))).toBe(true)
expect(vec.count()).toBe(4)
expect(vec.assoc(3, 6).toString()).toBe(‘[1 2 3 6]’)
expect(vec.reduce(function(acc,x){return acc+x},0)).toBe(10)
expect(vec.kvReduce(function(acc, k, v){return acc+k+v},0)).toBe(16)
})
})

fin

I’m still having fun and using it in my side project, so it’s pretty experimental and WIP, have fun with it if you like taking all this ClojureScript advantages and using them in JavaScript natively.

feel free to fork it and pull request is welcome.

availble in npm

npm install con.js

and rawgit cdn for browser

https://rawgit.com/jcouyang/conjs/master/mori.js

have fun 😹

--

--