
Clojure core.async and Go: A Code Comparison - drewolson
http://blog.drewolson.org/blog/2013/07/04/clojure-core-dot-async-and-go-a-code-comparison/
======
nickik
This is very cool and intressting. I have to think about something to do with
core.async.

This is also a good example why macros are just awesome. Go is a language
design to work well with goroutins and channels, but the clojure code looks
just as good and readable. You could simply not have such idiomatic use of
these concepts without macros.

Or am I wrong, can a flexible language like scala or python be extended to
look as good for that usecase? I dont know enougth of the tricks scala people
use, I cant juge if it is possible.

~~~
voidlogic
>but the clojure code looks just as good and readable.

But don't mistake this for having the same runtime characteristics- For what
its worth the Computer Language Benchmarks Game shows Go as generally being
faster, using much less memory and less code.

[http://benchmarksgame.alioth.debian.org/u64q/benchmark.php?t...](http://benchmarksgame.alioth.debian.org/u64q/benchmark.php?test=all&lang=go&lang2=clojure&data=u64q)

~~~
nickik
That is true. I have not looked at the much of the clojure code and non of the
go code but both probebly are written in a very unidiomitic style that one
does normally not use.

Also with clojure, the actual timeconsuming calculations can be made with java
and that should be at least as fast as go (with a bit more memory).

So all in all I value the architectural things much more then pure speed. Go
simply has a diffrent target then Clojure.

~~~
igouy
> I have not looked at the much of the clojure code and non of the go code

:-)

> can be made with java

Would that be idiomatic Clojure? :-)

~~~
nickik
> Would that be idiomatic Clojure? :-)

Yes it would. Clojure is design with that idea in mind, make it connect really
closly to java so that you can interop with no overhead.

------
kinofcain
Are the first two examples equivalent? In the go version, an anonymous
function is being declared and then called with the value of the outer 'i'. In
the clojure version, it appears that the value of 'i' is part of the closure
for that function.

The go version does what it does to avoid a race condition, because the
goroutines are being spun up in the background and it's highly likely that it
will take longer to spin up at least one of them than it will to finish the
loop, so without it you'd likely get output of all nines.

If the clojure version doesn't have that problem, then I think it's a somewhat
telling indication of how it's actually working.

TL/DR: The Go version has to work around the race condition because the
runtime is doing things "right", is core.async?

~~~
hueyp
The i is scoped differently in clojure than in golang so it doesn't have the
same potential pitfall. I think golang people consider their scoping here a
mistake (and have tooling to check it?).

~~~
frou_dh
C# had the exact same pitfall and it was recognised years ago. It's
unfortunate that it made it in to Go.

~~~
shakesbeard
Did they introduce a language solution for it in C#?

~~~
jared314
C# 5.0 is changing the behavior of the loop variable to be logically inside
the loop [0].

[0]
[https://news.ycombinator.com/item?id=3477629](https://news.ycombinator.com/item?id=3477629)

------
hueyp
Are the clojure threads actually lightweight? Thread/sleep is blocking so
you'd need to occupy 10 threads right? If you wanted to sleep without blocking
a real thread would you use an executor service to write to a channel after
delay and block on that?

~~~
drewolson
Author here.

From what I've read/seen, the go blocks are lightweight thread-like processes
multiplexed onto a thread pool. You may be correct about using Thread/sleep,
ideally I would have used (timeout ...) and then pulled off the channel.
However, I didn't want to introduce the concept of channels too early in the
post, so I felt Thread/sleep worked as a compromise.

~~~
MBlume
Yeah, Thread/sleep isn't quite what you want here:

(time (let [c (chan), n 1000] (dotimes [i n] (go (Thread/sleep 50) (>! c i)))
(dotimes [i n] (<!! c))))

"Elapsed time: 8412.25733 msecs"

(defmacro gosleep [millis] `(<! (timeout ~millis)))

(time (let [c (chan), n 1000] (dotimes [i n] (go (gosleep 50) (>! c i)))
(dotimes [i n] (<!! c))))

"Elapsed time: 91.278469 msecs"

ETA: for comparison, here's what happens if you actually make 1000 system
threads:

(time (let [c (chan) n 1000] (dotimes [i n] (thread (Thread/sleep 50) (>!! c
i))) (dotimes [i n] (<!! c))))

"Elapsed time: 4183.669835 msecs"

~~~
drewolson
Thanks (and thanks to pron). I've updated the post to include a warning
against using Thread/sleep in go blocks for "real" code.

~~~
MBlume
I feel silly though that this is the one thing I've commented on, so:

This is an awesome post, thanks for it ^_^

------
simpleglider
Small bug irrelevant to the main point of the article: The very last golang
example has a leak. If the timeout does occur, main will exit after a timeout
and the spawned go routine will hang on the channel push.

I think the easiest fix is to make the channel buffered. "make(chan string)"
=> "make(chan string, 1)" Not sure if there is a more idiomatic golang way to
accomplish this.

~~~
kisielk
Once main exits the program does as well, so all other goroutines will be
terminated.

~~~
simpleglider
True. Leaks don't matter for programs that exit immediately. I was pointing it
out because it is often relevant when using the timeout pattern.

------
aphyr
By the way, you can use (dotimes [i 100]) in place of (doseq [i (range 100)]).

------
mjw
Quick question for anyone here familiar with core.async:

Would it be possible (and if so what would be the simplest way) to implement
something like Python's generators and `yield` statement in Clojure using
core.async? I'm thinking something like:

    
    
        (defn range [n]
          (generator
            (loop [i 0]
              (yield i)
              (when (< i n)
                (recur (inc i)))
    
        (let [generator (range 5)]
          (generator) ;; => 0
          (generator) ;; => 1
          ;; etc
          )

~~~
JulianMorrison
I dunno, but I can do it in go :-P

    
    
      func generator(values ...interface{}) func() interface{} {
        c := make(chan interface{}, len(values))
        for _, v := range values {
          c <- v
        }
        return func() interface{} { return <-c }
      }

~~~
sambeau
"The problem with using a goroutine as a generator is that if you abandon it,
it will not get garbage collected."

It's also slow.

[https://groups.google.com/forum/#!topic/golang-
nuts/v6m86sTR...](https://groups.google.com/forum/#!topic/golang-
nuts/v6m86sTRbqA)

~~~
JulianMorrison
My design relies on pre-loading the channel's buffer and doesn't use a
goroutine.

------
SeanDav
I have never used Go or Clojure, but this is the first time I have heard of Go
as being verbose - or is that just in comparison to Clojure?

~~~
JulianMorrison
It's the difference between "programmable programming languages" (which the
lisps are, as are haskell and ruby) versus "languages for programming in"
(exemplified by go and java).

Go code is very terse compared to the general run of "languages for
programming in". And if you know the language, you can _just read it_.

On the other hand, you've got some serious digging to do if you want to
understand a clojure macro. Metaprogramming like that is seriously brain
twisty.

~~~
gruseom
_On the other hand, you 've got some serious digging to do if you want to
understand a clojure macro. Metaprogramming like that is seriously brain
twisty_

I wish this meme would stop. Your "if you know the language, you can just read
it" applies just as much here as it does there.

~~~
rsanders
I think you can be pretty proficient in writing regular Clojure and still find
macros "twisty". Most Clojurians would agree that "The first rule of Macro
Club is Dont Write Macros".

~~~
gruseom
Well, I wish that meme would stop too. It makes macros sound all freakazoid,
when really they're just another technique. The obvious and natural and
historically standard guidance is "Don't write a macro when a function will
do."

------
codygman
This is pretty awesome. I'm waiting on a clojure book in the mail, but this
article makes me want to dive in sooner!

Being a Go fan, this definitely draws me to clojure as a language since the
other language I've been messing with was racket (a scheme lisp).

------
bjeanes
Thanks for the write up. Definitely good to see core.async and goroutines side
by side...

------
knodi
Totally off topic, when i see ")))))))" it makes me want to run the other way.

~~~
tel
I'll add the classic response of "once you let your editor just handle it for
you you forget about it entirely". Because, really, truly, when you code in
lisp you're just traversing a very simple syntax tree. If you use
emacs/paredit you literally stop typing and start using keyboard commands to
"descend down the left branch" or "prune upward 4 times" or "delete this
entire branch".

~~~
josteink
Seconding emacs and pardedit.

The power of such a simple approach to editing is very hard to grasp (even
more so believe) until you've actually tried it yourself.

------
mortdeus
-> is not a valid operator in go.

------
charlieflowers
FYI, there's a typo: "it’s on yet available" -> "it’s NOT yet available"

------
egeozcan
In Go, am I supposed to seed the rand manually?
[http://i.imgur.com/Rw5afx9.png](http://i.imgur.com/Rw5afx9.png)

~~~
shakesbeard
Yes. The default seed is 1. You can set your own using something like this:

    
    
        rand.Seed(time.Now().UnixNano())

~~~
f2f
or, better yet, use crypto/rand to seed it.

