Hi David,
Parenscript does have such an operator, called BIND. Since it hasn't been documented yet, here are some examples. The idea is that you use keywords to bind to object properties and ordinary symbols to bind to array elements. Let's look at arrays first. A simple example:
(bind (a b c) '(10 20 30) (list c b a)) => (30 20 10)
Bind elements to NIL to ignore them:
(bind (a nil c) '(10 20 30) (list c a)) => (30 10)
To ignore the tail of an array, just omit it:
(bind (a b) '(10 20 30 40) (list b a)) => (20 10)
You can use &rest (or .) in the destructuring list:
(bind (a &rest others) '(10 20 30) (list others a)) => ((20 30) 10)
(bind (a . others) '(10 20 30) (list others a)) => same
You can nest array bindings:
(bind (a (b (c d))) '(10 (20 (30 40))) (list d c b a)) => (40 30 20 10)
Now for objects. A simple example:
(bind (:a :b :c) (create :a 10 :b 20 :c 30) (list c b a)) => (30 20 10)
Since the properties are named, order doesn't matter:
(bind (:a :c :b) (create :a 10 :b 20 :c 30) (list c b a)) => (30 20 10)
If you want to bind to a property using a different name, you can use a binding pair instead of a keyword:
(bind ((my-name :original)) (create :original 10) (list my-name)) => (10)
I use that sparingly because the extra parens can impede readability, but it's handy to avoid naming collisions:
(let ((original 99)) (bind ((mine :original)) (create :original 10) (list original mine))) => (99 10)
You can bind to an object inside an array:
(bind (a (:b)) (list 10 (create :b 20)) (list b a)) => (20 10)
However, you can't bind to an array inside an object, or an object inside an object, in a single BIND form — you have to use two:
(bind (:a) (make :a '(10 20 30)) (bind (nil b c) a (list c b))) => (30 20)
(bind (:a) (make :a (make :b 20 :c 30)) (bind (:b :c) a (list c b))) => (30 20)
That's because the notation doesn't seem to allow for any unambiguous way to do such nesting. (If you can think of one, please show us some examples.) This is the chief difference from the notation in your example, which adds additional syntax to support more complex destructuring lists. The tradeoff here is that BIND, lacking syntax, handles the simplest and most common cases more elegantly.
There is a form BIND* which allows multiple binds in a row to avoid unwanted indentation:
(bind* ((a b) '(10 20) (:c) (make :c 30)) (list a b c)) => (10 20 30)
It simply takes a list of binding pairs and turns them into a nested series of BIND forms.
Finally, note that if you mix keyword and non-keyword symbols in a binding list, it's considered an array binding and not an object binding:
(bind (:a b) '(10 20) (list b a)) => (20 10)
(bind (:a b) (make :a 10 :b 20) (list b a)) => (undefined undefined)
But it's bad practice to mix keywords and non-keywords in the same binding list. Perhaps BIND should throw an error when given such input.
So now let's look at your example:
(d-bind (:obj name (:obj firstname lastname) likes (:arr first-like second-like)) (create :name (create :firstname "Joe" :lastname "Blo") :occupation "Web Developer" :likes '("programming" "woodworking" "cycling")) (alert (+ "Your name is " firstname " and you like " first-like)))
As I mentioned, the main difference is that PS's BIND doesn't use special syntax like :obj and :arr to convey what's being destructured. No doubt tastes will differ on this. In any case, to translate your example, we'll have to use nested BINDs:
(bind (:name :likes) (create :name (create :firstname "Joe" :lastname "Blo") :occupation "Web Developer" :likes '("programming" "woodworking" "cycling")) (bind (:firstname) name (bind (first-like) likes (+ "Your name is " firstname " and you like " first-like)))) => "Your name is Joe and you like programming"
We can do the same thing with BIND* like this:
(bind* ((:name :likes) (create :name (create :firstname "Joe" :lastname "Blo") :occupation "Web Developer" :likes '("programming" "woodworking" "cycling")) (:firstname) name (first-like) likes) (+ "Your name is " firstname " and you like " first-like)) => "Your name is Joe and you like programming"
It would be a straightforward exercise to write your D-BIND as a macro that interprets the :obj and :arr directives, uses gensyms to create intermediate bindings, and emits the above nested BIND form.
If you find anything else in CoffeeScript that you think would be a natural fit for PS, please post it here.
Daniel