Hacker Newsnew | comments | ask | jobs | submitlogin
Comparing JavaScript, CoffeeScript & ClojureScript (dosync.posterous.com)
142 points by swannodette 814 days ago | comments


jashkenas 814 days ago | link

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.

-----

stevelosh 814 days ago | link

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.

-----

jashkenas 814 days ago | link

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.

-----

swannodette 814 days ago | link

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 :)

-----

iamgilesbowkett 814 days ago | link

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.

-----

nxn 814 days ago | link

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?

-----

jpatte 813 days ago | link

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.

-----

duelin_markers 814 days ago | link

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.

-----

Mikera 808 days ago | link

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".

-----

swannodette 814 days ago | link

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)))

-----

programnature 814 days ago | link

"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"

-----

cubicle67 814 days ago | link

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"

-----

programnature 814 days ago | link

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".

-----

moomin 812 days ago | link

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.

-----

lazerwalker 814 days ago | link

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.

-----

moonchrome 814 days ago | link

>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.

-----

Diederich 814 days ago | link

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

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

-----

vannevar 814 days ago | link

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

-----

drostie 814 days ago | link

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.

-----

aidenn0 814 days ago | link

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.

-----

vannevar 813 days ago | link

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

-----

grayrest 814 days ago | link

> 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.

-----

dangoor 814 days ago | link

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

Proxies are an awesome feature!

-----

nickik 814 days ago | link

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

-----

Void_ 814 days ago | link

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.

-----

raganwald 814 days ago | link

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.

-----

jlongster 814 days ago | link

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.

-----

raganwald 814 days ago | link

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."

-----

iamgilesbowkett 814 days ago | link

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.

-----

erichocean 814 days ago | link

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.

-----

swannodette 814 days ago | link

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

-----

jlongster 814 days ago | link

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.

-----

asmala 813 days ago | link

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/.

-----

gruseom 814 days ago | link

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.

-----

dangoor 814 days ago | link

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!)

-----

munificent 814 days ago | link

> 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.

-----

jlongster 814 days ago | link

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

-----

jashkenas 814 days ago | link

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.

-----

twism 814 days ago | link

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.

-----

Archos 814 days ago | link

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.

-----

gtrak 813 days ago | link

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.

-----

ricardobeat 814 days ago | link

> 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...

-----

Tuna-Fish 814 days ago | link

>> 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.

-----

thatmiddleway 814 days ago | link

Apparently these Clojurescript guys really love parenthesis...

-----

sukuriant 814 days ago | link

'(Really? I couldn\'t tell)

-----

Archos 814 days ago | link

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)
  }

-----

Archos 814 days ago | link

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

-----




Lists | RSS | Bookmarklet | Guidelines | FAQ | DMCA | News News | Feature Requests | Bugs | Y Combinator | Apply | Library

Search: