7 ms·
Clojure core.async and Go: A Code Comparison
- bjeanes 13y agoThanks for the write up. Definitely good to see core.async and goroutines side by side...
- kinofcain 13y agoAre 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 13y agoThe 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?).
- pcwalton 13y agoThe Go race detector will generally catch this mistake at runtime.
- frou_dh 13y agoC# had the exact same pitfall and it was recognised years ago. It's unfortunate that it made it in to Go.
- shakesbeard 13y agoDid they introduce a language solution for it in C#?
- deleted 13y ago[deleted]
- jared314 13y agoC# 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
- VMG 13y agoAlso see Javascript's IIFE: http://en.wikipedia.org/wiki/Immediately-invoked_function_expression#Evaluation_context http://en.wikipedia.org/wiki/Immediately-invoked_function_ex...
- Tuna-Fish 13y agoIt's a difference of the language. In the Go version, i is a mutable variable that can be read and written to. In Clojure, it's an immutable bound value. The i of one iteration is not in any way directly linked to the i of another iteration.
- frou_dh 13y ago"The Value of Values"
- deleted 13y ago[deleted]
- SeanDav 13y agoI 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?
- danenania 13y agoIt's a bit more verbose than popular dynamic languages like ruby, python, clojure, etc. but a lot less verbose than Java, C#, or C. To me it feels roughly on par with javascript LOC-wise, though it has much stronger constructs.
- chongli 13y agoClojure, being a Lisp, is as terse as you want it to be. Some of the examples on this page could be made even shorter with macros.
- pcwalton 13y agoDepends on what you compare it to. Go doesn't have macros and idiomatically prefers C-style for loops to higher-level abstractions (except for the built-in "range" construct) for iteration. So in general Lisps like Clojure offer more opportunities for terse code. (Which style is easier to understand is of course a never-ending debate.)
- tptacek 13y agoGolang is verbose, as modern languages go. In its idiom, it's actually a little more verbose than some modern, tight C (though less verbose than most C code, and much tighter than Java).
- NateDad 13y agoterseness is not a feature Go is striving for. That being said, it's still more terse than most other statically typed languages, simply due to type inference. You do have to write out some loops that other languages provide syntactic sugar for... people from those languages call that verbosity. Gophers call it clarity :)
- cgag 13y ago
- hueyp 13y agoAre 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 13y agoAuthor 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.
- hueyp 13y agoYah, for demo purposes of wanting to show something taking awhile thread/sleep is very convenient. I have no idea, time.sleep in golang might actually block a real thread too. I'm just curious if blocking in a go block could starve your thread pool or if there is some magic being done by the macro to even correct for that?
- MBlume 13y agoThere isn't. Thread/sleep will block the thread and potentially starve your thread pool. Be careful not to do it.
- pron 13y agoThread/sleep will block the entire thread. The thread pool could create more threads, but core.async uses a fixed thread-pool, so, yeah, don't use Thread/sleep in a go block. A timeout channel would be the way to go, or, you can use an alternate core.async implementation, which is a (small) part of the Pulsar project: https://groups.google.com/forum/#!topic/clojure/1xxxTti6Vi0 https://groups.google.com/forum/#!topic/clojure/1xxxTti6Vi0 (I'm the main author) We will have full API and semantic compatibility when version 0.2 is released next week. Pulsar also has cluster distribution and an Erlang-like actor framework. Because Pulsar uses instrumentation at the bytecode level, you have more freedom within Go blocks. You wouldn't use Thread/sleep in the Pulsar implementation, either, but Strand/sleep will do the job. It detects whether you're in a go block (implemented as a fiber in Pulsar), in which case it suspends the fiber but doesn't block the thread, or within a normal thread, in which case it will simply call Thread/sleep.
- simpleglider 13y agoSmall 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 13y agoOnce main exits the program does as well, so all other goroutines will be terminated.
- simpleglider 13y agoTrue. Leaks don't matter for programs that exit immediately. I was pointing it out because it is often relevant when using the timeout pattern.
- codygman 13y agoThis 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).
- mortdeus 13y ago-> is not a valid operator in go.
- nickik 13y agoThis 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.
- stuglaser 13y agoI've implemented Go-style channels in Python, and it turned out pretty well. The 'select' statement is a bit verbose, but still pretty readable. You can find the code here: http://github.com/stuglaser/pychan http://github.com/stuglaser/pychan And documentation here: https://chan.readthedocs.org/en/latest/ https://chan.readthedocs.org/en/latest/
- voidlogic 13y ago>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?test=all&lang=go&lang2=clojure&data=u64q http://benchmarksgame.alioth.debian.org/u64q/benchmark.php?t...
- nickik 13y agoThat 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 13y ago
- mjw 13y agoQuick 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 )
- tensor 13y agoYou could presumably do this with core.async, but the more idiomatic solution to generators would be lazy sequences.
- mjw 13y agoOh for sure, more as an exercise in curiosity than anything else. (Although I am also playing with generator- and iterator-like abstractions as part of a resource-scoped foldable stream abstraction to help process big files.) Looks like I managed to answer the 'is it possible?' part of the question anyway -- something like this: (defn range [n] (let [c (chan)] (go (loop [i 0] (>! c i) (if (< i n) (recur (inc i)) (close! c)))) (fn [] (<!! c))))
- pron 13y agoYes, but I wouldn't do that. Every time you pull an item out of the channel, you actually submit a task to execute in a separate thread pool (to produce the next number), block your own thread, and wait for the task to complete on its thread and then wake your thread up. That's a lot of task-switching going on just to generate the next consecutive number :) OTOH, you could use async's coroutine code (used to implement go blocks) to create generators, but because you have lazy sequences, that's not necessary either.
- mjw 13y agoYep I figured this would probably not be very efficient. About lazy sequences: sometimes you want to avoid the allocation of lots of intermediate cons cells when mapping/filtering/etc. The reducers framework for example manages to avoid this sort of cost when the data structure supports something faster than first/rest recursion. I'm interested in extending reducers to work nicely over large files and other sequences which don't fit in memory, and some kind of iterable or generator-like abstraction could play a useful role in this. (Actual coroutine-based generators might not be necessary, but would allow for a neat outward-facing API)
- egeozcan 13y agoIn Go, am I supposed to seed the rand manually? http://i.imgur.com/Rw5afx9.png http://i.imgur.com/Rw5afx9.png
- shakesbeard 13y agoYes. The default seed is 1. You can set your own using something like this: rand.Seed(time.Now().UnixNano())
- f2f 13y agoor, better yet, use crypto/rand to seed it.
- aphyr 13y agoBy the way, you can use (dotimes [i 100]) in place of (doseq [i (range 100)]).
- charlieflowers 13y agoFYI, there's a typo: "it’s on yet available" -> "it’s NOT yet available"
- knodi 13y agoTotally off topic, when i see ")))))))" it makes me want to run the other way.
- tel 13y agoI'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 13y agoSeconding 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.
- erichmond 13y agoFair point, but once you understand that every clojure form is: (function arg1 arg2 ... argN) The same impulse that made you want to run the other way should make you want to run screaming towards the language. :)