11 ms·
ClojureScript 101
- deleted 13y ago[deleted]
- swannodette 13y agoIf there's something in the tutorial that trips anyone up or doesn't make sense please let me know and I will amend. I'm pretty excited about the latest release of ClojureScript that makes all this possible. With respect to speed of incremental compiles, code size, performance of generated code, and debugging we're finally in a good place (with of course many improvements planned in the near future). None of this would have been possible without the tireless work of 62 contributors over the past two years, http://github.com/clojure/clojurescript/ http://github.com/clojure/clojurescript/. A big thank you to all! Happy hacking!
- rtorr 13y agoThis is great. We currently hold a weekly clojure class at work, and this will be very helpful to some of the people who attend. The more content the better!
- jbeja 13y agoLove CLJS!.
- benmathes 13y agoCouldn't really get started (with the latest clean install of leiningen from homebrew). $ lein cljsbuild auto async-tutl ... java.lang.Exception: Unknown build identifier: async-tutl Did the name of the tutorial change? edit: ell instead of 1. I guess that's why I use a heavily-serifed font for my coding. I even re-read the command several times looking for a simple typo to catch exactly this problem.
- chongli 13y agoYou have a typo there. The proper command is: $ lein cljsbuild auto async-tut1 Note the one instead of the ell.
- swannodette_ 13y agoThe project is async-tut1 not async-tutl. Look at your project.clj.
- olivergeorge 13y agoPerhaps note that scripts should be loaded after form elements. You get an odd error if (init) runs before the form elements exist.
- poppingtonic 13y agoI love this example. Working through this made me finally get started on reading Hoare's CSP book. Thanks!
- sitkack 13y agoBeing totally new to Sublime I was actually got confused on configuring paredit and lispindent with Sublime 2. get sublime2 http://www.sublimetext.com/2 package control https://sublime.wbond.net/installation#st2 launch "sublime 2" ctrl-` paste in the package control script (from above link) <ret> re-launch "sublime 2" ⌘-shift-p # dialog comes up install ↓ <ret> # up will pop a dialog after reading the package database paredit <ret> ⌘-shift-p install ↓ <ret> lispindent <ret> The extra level of detail would cloudy the main flow of the tutorial so maybe have some supporting material links for the slower kids?
- cnp 13y agoIf someone could provide a good pointer to better understand lisp syntax / Clojure I would appreciate it. Clojure is so confusing and hard to follow for the beginner!
- NoodleIncident 13y agoThe biggest difference is that functions go after the left-paren, not before. Commas are also unnecessary, they're just whitespace foo(bar, baz) => (foo bar baz) herp(bar, derp(baz)) => (herp bar (derp baz)) This might help as well: http://adambard.com/blog/clojure-in-15-minutes/ http://adambard.com/blog/clojure-in-15-minutes/
- masklinn 13y agoSuggestion: add a form around the input and button, and bind on its submit. That makes the application work when pressing [return] from the input field (you'll have to include a (.preventDefault e) in the (listen) callback though)
- olifante 13y agoThe (.log js/console (dom/getElement "query")) command returns "null" instead of a DOM element. Curiously, manually running the equivalent command in the Chrome console works as expected: > goog.dom.getElement function (element) { return goog.isString(element) ? document.getElementById(element) : element; } > goog.dom.getElement("query") <input id="query" type="text">
- nico 13y agoIt looks great, thanks! Here's another one I just saw: https://github.com/magomimmo/modern-cljs/blob/master/doc/tutorial-01.md https://github.com/magomimmo/modern-cljs/blob/master/doc/tut... I got started with CLJS about a year ago but ran into performance issues with the specific application I was building, hopefully things have improved now.
- swannodette_ 13y agoClojureScript has had performance escape hatches for some time. It's pretty much always possible to get the performance of hand written JS, important otherwise our data structures would be lame. We'll likely document these techniques in the future.
- deleted 13y ago[deleted]
- DigitalJack 13y agoNice. I spent like an hour today looking for a leiningen clojurescript template, so I'm happy to see Mies. It doesn't show up when googling for "leiningen clojurescript template." I wonder if maybe mentioning leiningen in the readme would change that.
- michaelsbradley 13y agoIs there a maintained web page or cheat sheet somewhere that lists the functions and macros unique to ClojureScript? The API Overview[1] page for Clojure itself is great, and many of those facilities are 1-to-1 in cljs. For individual libs, like core.async, it's easy enough to go look in the cljs namespaces (i.e. the source code) to see what's available. But it seems there are at least several important facilities unique to ClojureScript and it would be great if they were spelled out somewhere readily accessible and linked to from the main README[2]. Digging through the source is a bit more challenging given that there's more to wade through than for individual libs. [1] http://clojure.github.io/clojure/ http://clojure.github.io/clojure/ [2] https://github.com/clojure/clojurescript/blob/master/README.md https://github.com/clojure/clojurescript/blob/master/README....
- michaelsbradley 13y agoFor example: I'm aware of 'js*', but where is it defined? Where is it documented? Does it have any "gotchas" that are explained somewhere?
- swannodette 13y agojs* should not be used - it's an implementation detail. I agree we could do a better job documenting what's unique to ClojureScript over Clojure.
- john2x 13y agoGreat write up. ClojureScript 102 should be how to get a REPL running ;)
- jbeja 13y agoCould you please do one that explain how to actually debug in CLJS.
- poppingtonic 13y agoUsing `lein cljsbuild auto` should enable you to see problems in your clojurescript code as soon as you save it, and using the browser's js console enables you to see problems in the compiled/generated js. The stack trace printed out in case of an exception is usually informative enough. I experienced its helpfulness quite recently during my study of OP's example.
- swannodette 13y agoDebugging is a more straight forward now because of source maps, but it still needs a little bit of explaining - will think about it.
- minikomi 13y agoThank you for such a straightforward train I thought/workflow post. Would love to see something detailing how to deal with state in clojurescript. Use atoms? Pass the world in and return it modified? Dispatch within go funcs using symbols at the head of lists?
- swannodette 13y agoA nice suggestion, will think about it.
- michaelsbradley 13y agoJim Duey's protocol-monads[1] library is compatible with ClojureScript, and the State monad[2] can be a great for mechanism for "passing around the world". It's not always the best choice, but it's a good option to have in your toolbelt: [1] https://github.com/jduey/protocol-monads https://github.com/jduey/protocol-monads [2] https://github.com/jduey/protocol-monads/blob/master/src/clj/monads/core.clj#L280 https://github.com/jduey/protocol-monads/blob/master/src/clj...
- graue 13y agoIn what circumstances is it the best choice? I get the impression that monad libraries in dynamic languages are basically toys, or are written to show off. If I'm wrong, I'd love to see examples of how you used them in a ClojureScript project, why (other than novelty value) you chose them over other approaches like atoms, and how they worked out in practice.
- michaelsbradley 13y agoThat's a fair question, to be sure. However, your comments regarding monads see a bit off. I've seen the same sentiments expressed elsewhere (i.e. by others), so I think they represent something of a common misunderstanding. First of all, monads are simply programming patterns which relate a set of values to a set of of functions. For any monad X, we can say that a function is monadic if it returns a value in the X Monad. The set of values are precisely those such that the following three Monad Laws[1] hold: [ Using JavaScript syntax, they can be approximated as... ] Left Identity mBind( mReturn(x), f ) == f(x); // => true Right Identity mBind( mReturn(x), mReturn ) == mReturn(x); // => true Associativity mBind( mBind( mReturn(x), f ), g ) == mBind( mReturn(x), function(x) { return mBind( f(x), g ); } ); // => true Above, x is any value in the Monad X; f and g are monadic functions for the same monad. The definitions of mBind and mReturn will vary depending on the monad, but the same laws (and sometimes additional properties) hold for each group of things taken as a whole – the monadic values, monadic functions, and the mBind and mReturn pair for those values and functions. As you can see, there is nothing special that requires a statically typed language. That being said, if your language is statically typed, and even more so if its type system has advanced capabilities (e.g. Haskell's type system), then the relationships between the monadic values and functions can be leveraged to do a number of useful things, including catching a host of errors at compile time (though that's true regardless of monads). If your language is dynamically typed, then you won't get those extra benefits, but you can still take advantage of the fact that monads can abstract away a great deal of plumbing between various pieces of your program. Sometimes the "monad patterns" will appear in a language under another name, or will be used "under the hood" to implement a particular API. Clojure/Script's `let` for example is essentially the Identity Monad; and its `for` is very much akin to the List Monad (the same is true for list comprehensions in other languages). The core.async library involves an inspiring application of the State Monad pattern[2]. In my case, I'm working on an SCXML-like[3] framework which builds on Google Polymer, core.async and protocol-monads (and core.logic at some point?). By using the State Monad pattern (together with trampolines and core.async channels), the algorithms of SCXML can be implemented in a manner that's not dependent on side effects, instead flowing the "global state" between the computational steps in a manner that is consisent and relatively easy to reason about. To boot, by using channels to link together steps in the "mainEventLoop", time is deliberately yielded back to the CPU, making the framework even more concurrency friendly. The project will be open source soon, and I can follow up with some links to source code once it goes public on GitHub. [1] http://en.wikipedia.org/wiki/Monad_(functional_programming)#Monad_laws http://en.wikipedia.org/wiki/Monad_(functional_programming)#... [2] https://github.com/clojure/core.async/blob/master/src/main/clojure/clojure/core/async/impl/ioc_macros.clj#L53 https://github.com/clojure/core.async/blob/master/src/main/c... [&] https://github.com/clojure/core.async/blob/master/src/main/clojure/cljs/core/async/impl/ioc_macros.clj#L46 https://github.com/clojure/core.async/blob/master/src/main/c... [3] http://www.w3.org/TR/scxml/ http://www.w3.org/TR/scxml/
- stewbrew 13y agoSince this is supposed to be a 101 tutorial, could you please add (maybe in a later episode) some info on debugging and on parsing the stack trace. When I add the text 1+" to core.clsj and have auto compile running, I get a gigantic stack trace but none of the entries points me to the line where the error occured. I wonder how people are supposed to handle a code base bigger than a few lines of code and I stop following the tutorial before having written a single line of clojurescript.
- stolio 13y agoClojure stack traces in general are pretty verbose. A few thoughts from a fellow user (not ClojureScript specific, but remember we're doing all of this in a Clojure project): The top few lines of the stack trace have the most information. Entering some blank space or clearing your terminal will make it easier to find the top. The errors are often caught way down in Java land so you can be left trying to map Java errors and types into your own code - I've learned to ignore 80%+ of the trace for this reason. There's a bit of a learning curve to figure out what this can't be cast to that might mean, both for Java types and Clojure types. For example a missing opening paren may give a (roughly) "number can't be cast to sequence" error. A missing closing paren will generally throw an End Of File (EOF) error. A lot of the time you can get a line number from the top entry in the stack trace, they'll be in parentheses and aren't labeled terribly well - but once you find it once you'll know where it is forever. Sometimes, in certain situations - especially with macros that call across files (like for testing) - the stack trace might be completely unhelpful. I'm definitely hopeful that this situation will improve in the future (and maybe we can get an Edwin style debugger? Please?) In the meantime we can use tools to help us at least keep our braces organized since that's half the difficult to trace problems right there. For Emacs some swear by paredit, I'm still working on my system but it includes electric-parens, rainbow-parens (so helpful!) and auto-closing parens. Also, it's not as robust as Emacs but Lighttable offers some decent parentheses handling out of the box (I went back to Emacs after Lighttable was making connections to outside servers that I at least didn't mean to ask it to and didn't have time to deal with. Other than that Lighttable was great).
- swannodette 13y agoClojureScript generally gives pretty good errors and location information, however there are probably cases still where that's not true. This is likely an issues with tools.reader? Thanks for the report, I'll ask around about getting this looked into.
- sherbondy 13y agoHey swannodette, just curious about why you ended the <input> tag in your tutorial. Was this deliberate?
- sherbondy 13y agoHmm, I seem to have found a limitation/bug in go blocks. I didn't know about the nifty "." syntax for calling methods on js objects, eg: (. js/console (log "hi")). Anyway, I thought this was great, so I tried doing it inside of a go block, like so: (go (while true) (. js/console (log (<! clicks)))) And this yielded a compiler warning: WARNING: Use of undeclared Var async-tut1.core/log at line 50 src/async_tut1/core.cljs So there seems to be a problem with namespace resolution and the go macro? Or maybe a bug with "." in particular? Works fine when I do (.log js/console (<! clicks)) Either way, I would try to be consistent about your notation throughout the article. Either use (.log js/console ...) or use (. js/console (log ...)). Core.async in the browser is a ton of fun otherwise! Kudos.
- sherbondy 13y ago(Even if this is a known limitation and not a bug, I would love to understand why one form works and the other does not.)
- omni 13y agoI think this is a typo in the article. If you remove the parenthesis right before log, this should work. You're using the dot form to chain log onto js/console. When you write "(log" instead of just "log", it tries to call log as its own function, not as a method of js/console. This is why it tells you it's never seen that variable before. (go (while true) (. js/console log (<! clicks)))
- swannodette 13y agoA bug in core.async, I've already opened a ticket for this. Thanks for the feedback!
- andreypopp 13y agoRecreated in pure js with ES6 generators — http://andreypopp.com/posts/2013-11-09-recreating-core-async-tutorial-with-es6-generators.html http://andreypopp.com/posts/2013-11-09-recreating-core-async...
- swannodette 13y agoFantastic! :)
- krosaen 13y agoPerfect intro to core.async in a web app - just enough of an example (jsonp within a click handler would require a nested callback). It would be nice to update the tutorial with a final instruction on compiling with advanced mode so people can see that the end result is a reasonable sized js file.
- macmac 13y agoIncorporating a browser REPL into the template would be fantastic.