[parenscript-devel] A more useful destructuring bind
Vladimir Sedach
vsedach at gmail.com
Thu Jan 17 19:41:57 UTC 2013
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
>
More information about the parenscript-devel
mailing list