[parenscript-devel] A more useful destructuring bind
Daniel Gackle
danielgackle at gmail.com
Fri Jan 18 01:29:54 UTC 2013
Come to think of it, there's a problem. PS's LOOP uses
BIND. It's super handy and I rely on it all the time to do
things like (loop :for (a (:b)) :in c...). I suppose there are
a few options for dealing with this but the easiest would be
to keep (or move) LOOP into the same package as BIND.
On Thu, Jan 17, 2013 at 3:13 PM, Daniel Gackle <danielgackle at gmail.com>wrote:
> I think it makes sense to put BIND in a more outer circle, so to speak.
> It's definitely more of a library macro, not any kind of standard.
>
> I'm not sure I understand this, though:
>
> > The non-CL stuff should also, whenever possible, define
> > macros/functions with equivalent functionality in Common Lisp, to
> > provide an easy way to run PS code in CL.
>
> Do you mean that there should be a CL implementation
> of (to use the same example) BIND?
>
> Dan
>
>
>
> On Thu, Jan 17, 2013 at 12:41 PM, Vladimir Sedach <vsedach at gmail.com>wrote:
>
>> I'm all for documenting LOOP extensions. I was actually thinking of
>> deprecating BIND and BIND* since they're non-cl. I propose this: split
>> off CL and non-CL stuff into two different packages, that are both
>> imported and re-exported from the PARENSCRIPT package. The non-CL
>> stuff should also, whenever possible, define macros/functions with
>> equivalent functionality in Common Lisp, to provide an easy way to run
>> PS code in CL.
>>
>> Thoughts?
>>
>> Vladimir
>>
>> On Tue, Jan 15, 2013 at 12:30 PM, Daniel Gackle <danielgackle at gmail.com>
>> wrote:
>> > 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
>> >>
>> >
>> >
>> > _______________________________________________
>> > parenscript-devel mailing list
>> > parenscript-devel at common-lisp.net
>> > http://lists.common-lisp.net/cgi-bin/mailman/listinfo/parenscript-devel
>> >
>>
>> _______________________________________________
>> parenscript-devel mailing list
>> parenscript-devel at common-lisp.net
>> http://lists.common-lisp.net/cgi-bin/mailman/listinfo/parenscript-devel
>>
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mailman.common-lisp.net/pipermail/parenscript-devel/attachments/20130117/1fe432fc/attachment.html>
More information about the parenscript-devel
mailing list