Hacker News new | comments | show | ask | jobs | submit login
Comparing JavaScript, CoffeeScript & ClojureScript (dosync.posterous.com)
147 points by swannodette 1819 days ago | hide | past | web | 47 comments | favorite



One man's "reusable abstraction" is another man's "why the hell is this thing being abstracted?" ... perhaps with Lisps more than anywhere else ;)

For example, in David's final gist, we have:

    (defprotocol ISlice
      (-shift [this]))

    (deftype Slice [start end]
      ISlice
      (-shift [_] (Slice. (inc start) (inc end)))
      IFn
      (-invoke [_ x]
        (cond
          (string? x) (.substring x start end)
          (vector? x) (subvec x start end))))

    (def s (Slice. 0 5))
    (def v ["List Processing" [0 1 2 3 4 5 6]])

    (map s v)
    ;; >> ("List " [0 1 2 3 4])
    (map (-shift s) v)
    ;; >> ("ist P" [1 2 3 4 5])
When I think there's a good case to be made for the clarity of this alternative:

    slice = (start, end) ->
      (list) ->
        if typeof list is 'string'
          list.substring start, end
        else
          list.slice start, end

    vector = ["List Processing", [0, 1, 2, 3, 4, 5, 6]]

    vector.map slice 0, 5
    # ["List ",[0,1,2,3,4]]

    vector.map slice 1, 6
    # ["ist P",[1,2,3,4,5]]
... but with careful compile-to-JS languages, it's pretty great that each can do it their own way, and the resulting code can play nicely together.


The last example in the post doesn't really show off the power of protocols (well, it does, but it's buried a bit deep).

In your example (and in David's) the implementation of the slice function is locked into the definition of that function. The real power of protocols comes when you use them to decouple this.

Here's an example (in Clojure because I don't know the names of the types Clojurescript uses off the top of my head, but the ideas are the same):

    (defprotocol Sliceable
      (slice [this start end]))

    (extend-protocol Sliceable
      clojure.lang.PersistentVector
      (slice [this start end]
        (subvec this start end)))

    (extend-protocol Sliceable
      java.lang.String
      (slice [this start end]
        (.substring this start end)))

    (slice "Hello" 0 2)
    (slice [:a :b :c] 0 2)
Here we've defined a protocol called "Sliceable" and added support for it to two built-in classes. We've done so in a safe way -- the "slice" function isn't visible anywhere outside this namespace unless you import it.

Now say you come along and want to use my little "slicing" library to carve up someone else's data structures. You can add support without touching my files or their files:

    (ns foo
      (:use fancylists [:only (FancyList)])
      (:use stevelosh.slicing [:only (Sliceable)]))

    (extend-protocol Sliceable
        FancyList
        (slice [this start end]
          (... slice up the fancylist here ...)))
And now in another file you want to slice up a FancyList:

    (ns bar
      (:use fancylists [:only (FancyList)])
      (:use stevelosh.slicing [:only (slice)]))

    (def my-fancy-list (FancyList 1 2 "cat" "dog"))

    (slice my-fancy-list 2 4)
    ; => (a FancyList containing "cat" and "dog")
Now you've added support for some random person's FancyList library to my Slicing library, without touching the code in either of them. This is the really cool part about Protocols.

This concept actually does appear in David's post when he adds support for his Slice type to the IFn protocol, but it's a bit obscured by the fact that function calling has some syntactic sugar so you don't have to write -invoke.


Yes, that's a much nicer protocol demo.

When you end up writing switches on types in your protocol implementation ... it fails to demonstrate any particular advantage over switching on types in a simple function.


Glad you think so. Thanks stevelosh. I avoided taking the extra step since I was writing for people of varying levels of familiarity with JavaScript, CoffeeScript, and ClojureScript.

But perhaps I'm a bit old school - I explained something with a minor flaw to leave something for the reader to solve :)


As a busy person who finds your ideas interesting but has no time for riddles, I would really appreciate it if you could at least drop some hints here.


In this context this looks somewhat similar to Haskell's type classes, and maybe to a lesser, or greater(?), extent C#'s extension methods. The latter I guess lacks the concept of the "protocol" as extensions methods live at the namespace level in a way (they're wrapped in a static class, but this is somewhat meaningless). Then again, the protocol concept here seems to be mainly used for the same purpose of having a namespace for storing implementations and also importing them?


What seems really interesting to me here is the ability to extend an existing class by implementing an interface (instead of a set of extension methods), and therefore becoming able to cast any instance of this type as an instance of this interface. I'd love to have that in C#, as it would simplify (if not eliminate) the creation of wrappers around types you don't own.


An important difference between Clojure protocols and C# extension methods is that protocols are one of the tools for polymorphism in Clojure, the (only?) other being multi-methods, whereas extension methods are just syntactic sugar for static methods and therefore aren't polymorphic.


Clojure also supports prototype-based polymorphism via Associative objects, inheritance polymorphism via Java interop/proxy, interface-based polymorphism with deftype/defrecord, polymorphism via abstraction (see the Clojure sequence operations for example, which use Java interfaces under the hood but you'd never know....). And probably a few more I can't remember right now..... there's a reason Rich Hickey calls it "polymorphism a la carte".


Except isn't that missing the actual point? The example was contrived to drive home that we can use objects as functions in ClojureScript - I respect that the engaged reader understands that these tools must be applied tastefully. I could have implemented it just like you've shown but would we have learned anything? :)

Also my example is one step from being open, extensible to any type instead of hard coding string/array which you copied. You can't do this in CoffeeScript in a safe way:

  (deftype Slice [start end]
      ISlice
      (-shift [_] (Slice. (inc start) (inc end)))
      IFn
      (-invoke [_ x]
        (-slice x start end)))


"But when our hypothetical Blub programmer looks in the other direction, up the power continuum, he doesn't realize he's looking up. What he sees are merely weird languages. He probably considers them about equivalent in power to Blub, but with all this other hairy stuff thrown in as well"


yeah, I see what you're trying to do here, except you're calling Jeremy a blub programmer when he's anything but.

it also comes off as a bit dickish to try and refute a point with (what amounts to) "I have more knowledge than you, and there's no point explaining because you wouldn't understand anyway"


Not trying to refute anything. Just a passage that comes to mind when reading that opening sentence. Especially with the specific reference how that seems to be common occurrence with Lisp.

In fact, you might argue that OP and PG are saying the same thing. Programming languages are habits of mind, and until you have real understanding of an abstraction, it's a lot easier to think "that looks weird, why don't I just use this other thing I already know".


Isn't this just the "the example code that shows off a feature can be implemented more simply" gambit? The point is always that the code that really needs the feature is too long to fit in an example.


I appreciated the look into ClojureScript as someone who's never touched it (and whose interest is definitely piqued now), but I'm baffled by the article's initial hook of 'let's compare how many characters it takes to accomplish the same task'. Is this really a meaningful metric? There's value in comparing the relative clarity and expressibility of each language, but that's not necessarily synonymous with brevity. I get that it's not the heart of the piece, but starting with such pedantry seems to detract from the point.


>I get that it's not the heart of the piece, but starting with such pedantry seems to detract from the point.

I'm guessing it's there to persuade people that have preconceptions about lisps verbosity/readability to read the article instead of immediately dismissing it.


Granted, clarity and power aren't necessarily the same thing:

http://www.paulgraham.com/power.html


And brevity is not the same as either. I'd argue that the three are actually orthogonal.


Brevity is not orthogonal to clarity; it is clear that overly verbose code is not as readable, just because e.g. it doesn't fit on your screen (or in your head) all at once.


I'd concede that. But it's still possible to have brief code that is less clear than verbose code.


Brevity is a feature of clarity, and clarity usually attends very powerful notions like reuse and application to a new domain.

What's truly orthogonal is some notion of "expressiveness" or "teachability" or otherwise being-able-to-read-and-understand.

The example that you should think of here is mathematics papers. The ideas are stated in their clearest form, which usually is absurdly brief: lemma, theorem, proof. You can then spend about an hour poring over each line to figure out what the heck it's expressing, before suddenly a moment of clarity dawns on you and you see all of the connections together.

Why would they write incomprehensibly? It's not because they don't want to be understood; these are peer-reviewed journals we're talking about here, and someone else must read and OK your work. But it's because when they make the fewest assumptions and the most broad argument, their work maximizes power and usability and robustness.


> neither JavaScript nor CoffeeScript provide any kind of doesNotUnderstand: hook

Feature is Proxies, it's in Harmony. I know v8 has an implementation, having used it in node (takes a command line flag) but not sure what the status is elsewhere.


Firefox has been shipping Proxy since Firefox 4 and uses it a great deal internally.

Proxies are an awesome feature!


Its like it always was with lisp, you don't have to wait for the language designers.


The biggest advantage of CoffeeScript is that it translates directly into JavaScript and it doesn't require any library to run after it's compiled.


Both CoffeeScript and ClojureScript “transpile” into JavaScript.

ClojureScript does come with its own standard library just as you say. However, I don’t know if that’s CoffeeScript’s “biggest advantage,” althout it’s related. The way I would put it is that while the author claims that ClojureScript is “OO, the Good Parts,” CoffeeScript is JavaScript, The Good Parts.

Meaning, CoffeeScript programming is still very close in mental model to JavaScript programming. This is why it doesn’t require its own library, you are writing JavaScript and can use any abstraction or library you like.

Its syntax for OO programming is really sugar for using JavaScript’s built-in semantics in a legible way. Locally namespaced extensions are a wonderful idea, but they aren’t really JavaScript. For better or for worse, CoffeeScript eschews such deep departures from JavaScript.

That’s a disadvantage when you want something that’s a big improvement, but it’s an advantage when you are familiar with JavaScript and just want to get things done.


I don't have any evidence to back this up, but I'm guessing that the only reason CoffeeScript embraces Javascript is for debugging. It firmly places itself as an "improved Javascript" which compiles out to "normal" Javascript -- so that you can sanely debug it, and sanely use the language.

ClojureScript doesn't have this problem. It's normal Clojure code that (I assume) can be debugged like normal Clojure.

EDIT: I haven't debugged ClojureScript yet, but I assume you can debug it like normal Clojure code.


I'm guessing that the only reason CoffeeScript embraces Javascript is for debugging.

I wonder whether CoffeeScript embraces JavaScript is because its author, jashkenas, likes JavaScript’s semantics. he has created several extremely useful JavaScript tools and has described JavaScript using words like “gorgeous."


I'd call this interpretation indisputable unless I saw jashkenas himself disputing it. In many ways, CoffeeScript is just JavaScript, minus a whole ton of redundant key-tapping.


I have yet to find myself in a situation after 15 years of programming when the primary problem was "redundant key-tapping".

The more languages I learn, the less I care about syntax and the more I care about being able to express solutions to problems. In that respect, then, CoffeeScript has been no help to me whatsoever, since semantically, it's no different than JavaScript.


Well ... sort of. The Clojure debugging story is still only OK and under development. Any ClojureScript debugging story really should integrate with existing tools - I have some ideas about that brewing here: http://dev.clojure.org/display/design/ClojureScript+Debugger


We've written a Parenscript/JS debugger that runs in Emacs and interacts with V8 via Node.js as an Emacs subprocess. We started off using the JSON debugging protocol you mention on that linked page, but had to abandon it because it insisted on printing large arrays in their entirety, which hangs V8 if you ever encounter a large array as a local variable. In the end, we found V8's in-process debugging API much easier to work with. The JSON protocol is just a (bloated) wrapper around that, which gives less control and doesn't work particularly well. (I'm not aware of any systems out there that actually use it.) Of course, if you drop the JSON wrapper, you have to write your own server-side code and pass your own messages to the client. But in return, you have fewer hoops to jump through, and it felt to me like less overall work to make a good debugging client this way. The in-process API isn't documented but it isn't hard to figure out from the V8 source.


Ah, I see. I'd probably take the route of allowing ClojureScript to be run as normal Clojure, but I guess the problem is that you don't have access to the browser environment. Debugging browser-based apps can be tricky because of that.


Actually, to a large extent you can have access to the browser environment. See e.g. the ClojureScript One video and guide: http://www.clojurescriptone.com/.


Another option is source mapping, allowing the browser's tools to map from the JavaScript that is running over to the original source files (with line and column mapping). Some support for that has landed in WebKit, and we have patches for some integration in Firefox (remarkably enough with the same file format!)


> I'm guessing that the only reason CoffeeScript embraces Javascript is for debugging.

The other reason is to avoid code bloat. Adding your own runtime library adds a lot of (generated) JS that has to be pushed down to the client.

This is something we're constantly fending with in Dart. Dart does have a runtime library (including a different DOM API!) and managing that without generating enormous amounts of JS is tricky. We're getting pretty good at dead code stripping, but doing that isn't easy. Without type annotations, it would be even harder.


As far as I know, ClojureScript works the same way.


No, there's a very sizable and useful ClojureScript standard library:

https://github.com/clojure/clojurescript/blob/master/src/clj...

... the idea is that the parts of it you don't use will be optimized away by the Google Closure Compiler.


I thought that this would be an advantage as well but what ended up happening is that I found myself spending a lot of time in the generated javascript. Either I was checking the generated javascript too often to see if it mapped to the coffescript I wrote, or I'm fixing bugs in the generated javascript forgetting that I was using coffescript.


But the great disadvantage of those compilers (CoffeeScript and ClojureScript) is that the generated code doesn't matches up with the lines of the source code so its debugging is much more difficult.


Knowing no javascript, I didn't have a hard time debugging my first try at compiled-to-js clojurescript. It's pretty easy to see the naming correlations and conventions used by the compiler in the generated source.


> We've extended all objects including numbers to respond to the bar function.We can provide more specific implementations at anytime, i.e. by using extend-type on string, array, Vector, even your custom types

Mimicking that in javascript:

    Object.defineProperty Object.prototype, 'bar',
        value: -> 'whatever'
        
    'foo'.bar()
    (1).bar()
    [1,2,3].bar()
More specific implementations can be defined on the other prototypes. This is totally safe (except for oldIE).

Making objects behave as functions (actually the opposite) is also possible:

    MagicHash = (props) ->
      f = (key) -> f[key]
      f[k] = v for k,v of props
      return f
  
    address = new MagicHash
      street: '1010 Foo Ave.'
      apt: '1111'
      city: 'Bit City'
      zip: '000000000'

    address.apt
    #> '1111'
    ['street', 'zip'].map address
    #> [ '1010 Foo Ave.', '000000000' ]
or

    map = (fn, arr) -> arr.map fn

    map address, ['street', 'zip']
    #> [ '1010 Foo Ave.', '000000000' ]
    
And no, I have no idea how this could be useful...


>> We've extended all objects including numbers to respond to the bar function.We can provide more specific implementations at anytime, i.e. by using extend-type on string, array, Vector, even your custom types

>Mimicking that in javascript:

>This is totally safe (except for oldIE)

No, not in his definition of safe it isn't. That's just monkeypatching -- the new functions are visible everywhere in the program, and it's entirely possible to be stung badly by name collisions. The ClojureScript version doesn't have these problems -- the protocol only exists in the namespaces where it is defined or imported.


Apparently these Clojurescript guys really love parenthesis...


'(Really? I couldn\'t tell)


I prefer a sintaxis more expressive, concise and clean like Go's:

  package main

  type Foo struct {
	a, b, c int
  }

  func (f *Foo) bar(x int) int {
	return f.a + f.b + f.c + x
  }

  func main() {
	afoo := Foo{1, 2, 3}
	afoo.bar(3)
  }


If I've said that one, it is because there is a compiler from Go to JavaScript under (advanced) development with several advantages over another compilers:

+ The lines numbers in the unminified generated JavaScript match up with the lines numbers in the original source file.

+ Generates minimized JavaScript.

+ Allows many type errors to be caught early in the development cycle, due to static typing.

https://github.com/kless/GoScript




Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | DMCA | Apply to YC | Contact

Search: