Comparing JavaScript, CoffeeScript & ClojureScript
dosync.posterous.com
dosync.posterous.com
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.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.
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.
But perhaps I'm a bit old school - I explained something with a minor flaw to leave something for the reader to solve :)
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)))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"
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".
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.
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.
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.
Proxies are an awesome feature!
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.
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 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."
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.
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.
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.
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...>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.
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)
}+ 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.