[parenscript-devel] A more useful destructuring bind

Daniel Gackle danielgackle at gmail.com
Tue Jan 15 20:20:35 UTC 2013


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
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mailman.common-lisp.net/pipermail/parenscript-devel/attachments/20130115/699171cb/attachment.html>


More information about the parenscript-devel mailing list