[parenscript-devel] A more useful destructuring bind

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


Vladimir -- writing the below made me think of whether we should
incorporate it into the PS docs somehow, with whatever changes
are needed to fit the docs' conventions. Do you agree? What would
be the best way to do this?

We really should document LOOP as well, especially since it
has the :MAP and :OF extensions for working with JS objects.

On Tue, Jan 15, 2013 at 1:20 PM, Daniel Gackle <danielgackle at gmail.com>wrote:

> 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/63d2d164/attachment.html>


More information about the parenscript-devel mailing list