Arc Forumnew | comments | leaders | submitlogin
1 point by rocketnia 5129 days ago | link | parent

That whole post is awesome. The (= '(a b) ...) syntax might be more consistent as (= (list a b) ...), so that you can (zap rev (list a b)), but the quote version could have more advantages than consistency:

  (zap idfn '(a b c))    ; define self-evaluating symbols?
  (= '(a b c=4) x)       ; multiple assignment with optionals?


3 points by Pauan 5129 days ago | link

Yeah, it's an awesome idea. But akkartik deserves the credit for it: I merely added on the thing about destructuring assignments. I found the post where I got the idea from: http://arclanguage.org/item?id=12575

Turns out the idea originated with waterhouse, akkartik tweaked it, you mentioned something similar which reminded me of it, aaand then I added on destructuring assignments. Hurray for collaboration!

Also, why can't we do both? If I understand it correctly, = works based on the name, so we could have (list ...) and (quote ...) do the same thing. I like how (= '(a b)) is short, and it has synergy with destructuring in argument lists, so I'd like to keep that. :P

By the way, I got quoted destructuring in argument lists working hours ago. Now I'm working on getting fn, quote, if, etc. as fexprs.

-----

1 point by rocketnia 5129 days ago | link

Yeah, I just didn't think it was important to mention having both. And I remembered the conversation, vaguely, and I took that into account when saying that your post was awesome. ^_^

-----

2 points by Pauan 5129 days ago | link

By the way, your great idea for optional assignment got me thinking: maybe we could define = so when it sees (quote ...) it basically wraps around a function, like `let`. Then we would get things like optionals for free, and if myself or others extended the argument list further, those changes would also automagically work with =

In fact, if we did that... we could have destructuring destructuring assignments:

  (= '(a '(b c (d)) e)
     '(1  (2 3 (4)) 5))

  a -> 1
  b -> 2
  c -> 3
  d -> 4
  e -> 5
Or we could use rest args:

  (= '(a b . c)
     '(1 2 3 4 5))
     
  a -> 1
  b -> 2
  c -> (3 4 5)
Oh, and the (let (a b c) nil) idiom would work too:

  (= '(a b c) nil)
I love that symmetry. I'm not sure why, but I figure the more things we can desugar into functions, the better.

P.S. Destructuring destructuring might be useful for alists:

  (= '(_    '(_ a))
     '((a 1) (b 2))) -> a is now 2
...though the above is probably better written like this:

  (= foo '((a 1) (b 2)))
  (= a foo.'b)

-----

1 point by rocketnia 5128 days ago | link

Yeah, I use destructuring destructuring all the time. Arc lets you use optionals in destructuring too, but I've only used that once or twice.

You have an example with _, but be careful, '_ is one of the most common Arc variable names. :-p Replacing '(_ a) with '('() a) should make it safer.

You're talking about doing (= '(...) ...) in terms of functions, but I have trouble seeing how that would work, and I was thinking the reverse. What if every function began by doing a destructuring assignment? You could have no parameter list syntax at all, in a sense, and it would be completely extensible using 'defset. On the other hand, it means 'defset extensions would have to return four elements instead of three, the fourth element being a list of local variables that a function will have if the form is used as a parameter list.

-----

1 point by Pauan 5128 days ago | link

But in PyArc, you can assign to nil. :P Besides, wouldn't that throw an error in pgArc anyways, since you can't assign to nil?

Hm... yeah, I wasn't entirely sure how my idea would work either. I like the idea of using defset to extend the argument list, rather than the other way around. Then we'd get extensible argument syntax for free, rather than me having to add in any infrastructure. :P

-----

1 point by rocketnia 5128 days ago | link

In pg-Arc, (let (() a) '(1 2) ...) destructures just fine. It provides a local variable "a" initialized to 2, and it ignores the 1.

In PyArc, I assume (= '() a) and (= nil a) would be distinct cases. Besides the difference of a (quote ...) form, you've already said nil and () were different.

If pg-Arc were changed to require (quote ...) around each destructuring form, (() a) would become '('() . '('a . '())). Note that simple variable names like "a" are themselves destructuring forms; they're degenerate ones which have nothing but a rest arg.

-----

1 point by Pauan 5128 days ago | link

Internally different. Externally, () and nil are eq to each other. The only way to tell the difference is to print them.

Of course I could change it so it ignores nil when destructuring. That may be a good idea.

Yoink, another chunk of code to add to the unit tests.

-----

1 point by rocketnia 5128 days ago | link

"Internally different."

Oh, right. Still, at least (= nil 4) and (= 'nil 4) have the (quote ...) difference....

Come to think of it, maybe it doesn't work to consider a rest parameter as a destructuring form, since conflating (fn 'a ...) with (fn a ...) would also conflate (= 'a ...) with (= a ...). (From the future: In fact, there might be trouble integrating setforms and parameter lists at all. See below.)

Here's a full grammar for pg-Arc parameter lists:

  === parameter list ===
  
  ; This matches based on matching the car and cdr of a cons. It raises
  ; an error on other kinds of value, *including nil*.
  (<destructuring form> . <parameter list>)
  
  ; This binds what it's matched to to a new local variable.
  <non-nil symbol>
  
  ; This raises an error if it's matched to something other than nil.
  ()
  
  ; When matched to nil, this evaluates <default> and matches that to
  ; <destructuring atom>, and it matches nil to <parameter list>. When
  ; matched to a cons, this matches <destructuring atom> to the car and
  ; <parameter list> to the cdr. When matched to another kind of value,
  ; this raises an error.
  ((o <destructuring atom> <default>) . <parameter list>)
  
  ; The ((o <> <>) . <>) rule takes precedence over the (<> . <>) rule.
  
  
  === destructuring form ===
  
  ; This matches based on matching the car and cdr of a cons *or nil*.
  ; It raises an error on other kinds of value.
  (<destructuring form> . <destructuring form>)
  
  ((o <destructuring atom> <default>) . <destructuring form>)
  
  <destructuring atom>
  
  ; The ((o <> <>) . <>) rule takes precedence over the (<> . <>) rule.
  
  
  === destructuring atom ===
  
  <non-nil symbol>
  
  ; This ignores what it's matched to altogether.
  ()
Here's how I currently imagine one of our grammars under discussion, without setforms integration:

  === parameter ===
  
  ; This matches based on matching the car and cdr of a cons or nil. It
  ; raises an error on other kinds of value.
  (quote (<parameter> . <parameter>))
  
  ; When matched to nil, this evaluates <default> and matches that to
  ; <parameter a>, and it matches nil to <parameter b>. When matched to
  ; a cons, this matches <parameter a> to the car and <parameter b> to
  ; the cdr. When matched to another kind of value, this raises an
  ; error.
  ((= <parameter a> <default>) . <parameter b>)
  
  ; This binds what it's matched to to a new local variable.
  <non-nil symbol>
  
  ; This ignores what it's matched to altogether.
  ()
  
  ; This matches as though it's (quote (<parameter a> . <parameter b>)).
  (<parameter a> . <parameter b>)
  
  ; The (<> . <>) syntax has the lowest precedence.
With setforms integration, it seems like it would be a bit troublesome. The (<> . <>) case would need to cover (= (my-table k) v), but it would also need to cover (fn (my-table k) ...). Hmm, not as seamless as I hoped. ^^;

-----

1 point by Pauan 5128 days ago | link

Hm... why wouldn't something like this work?

  (let old fn
    (mac fn (names . body)
      (w/uniq a
        `(,old ,a
           (let ,names nil
             (= ',names ,a)
             ,@body)))))


  (fn args (+ 1 5))  -> (<fexpr fn> gs524
                          (let args nil
                            (= 'args gs524)
                            (+ 1 5)))
    
    
  (fn (n x) (+ n x)) -> (<fexpr fn> gs525
                          (let (n x) nil
                            (= '(n x) gs525)
                            (+ n x)))
Remember: the destructuring/optional/rest/etc. functionality would only be when the first argument to `=` is quoted.

Derp, I just realized that `let` desugars to `fn`, which doesn't have destructuring. I guess you could replace `let` with `with` and it would still work, albeit it would be clunkier. Something like this:

  (let old fn
    (mac fn (names . body)
      (w/uniq a
        `(,old ,a
           (with ,(if (isa names 'cons)
                        (mappend [list _ nil] names)
                      (list names nil))
             (= ',names ,a)
             ,@body)))))
You could also optimize it, so it expands into a normal `fn` when the argument list is a symbol, like (fn args) but does the special stuff when it's a cons, like (fn (a b)):

  (let old fn
    (mac fn (names . body)
      (if (isa names 'cons)
            (w/uniq a
              `(,old ,a
                 (with ,(mappend [list _ nil] names)
                   (= ',names ,a)
                   ,@body)))
          `(,old ,names ,@body))))
The above does expand properly in Arc 3.1... except you need to replace (let old fn with (let old 'old-fn because Arc doesn't have first-class special forms like PyArc. :P

-----

1 point by rocketnia 5128 days ago | link

Yeah, 'fn is the most basic way to make local variables in pg-Arc, and everything else is based on that. The problem I see with basing 'fn on a simpler, non-destructuring version of 'fn, 'with, or 'let is that somehow you need to take things like (a (b c) d=1 e=2) and extract their variables for use in the simpler form. Otherwise you get cases like this:

  (simple-fn gs525
    (simple-let-nil (a (b c) d=1 e=2)
      (= '(a (b c) d=1 e=2) gs525)
      ...))
By the time you to all the trouble to make this 'simple-let-nil work, you might as well have made 'fn with destructuring built in.

...Well, since you're in an interpreter, you may be able to get away with this:

  (simple-let gs1 (argument-list-vars '(a (b c) d=1 e=2))
    (simple-fn gs2
      (interpreted-simple-let-nil gs1
        (= '(a (b c) d=1 e=2) gs2)
        ...)))
There are probably plenty of other possibilities I'm overlooking too. ^_^

Oh, and I suppose my Penknife plan is another option. The core language could supply a simple 'fn, and another namespace could supply a more advanced 'fn once enough functionality has been built up based on the core.

-----

1 point by Pauan 5128 days ago | link

Hm... yeah, that makes sense. Alrighty then, let's try to get `=` to desugar to a function, so we can get destructuring assignments. :P I figure (= '(a b) nil) could desugar to this:

  (with (gs204 nil gs205 nil)
    (= a gs204)
    (= b gs205))
And then (= '(a b (c d)) '(1 2 (3 4))) could desugar to this:

  (with (gs206 1
         gs207 2
         gs208 (3 4))
    (= a gs206)
    (= b gs207)
    (= c gs208))
Hm... but that wouldn't work recursively, so it would only be one level of destructuring. In which case you might as well use something simpler:

  (do
    (assign a 1)
    (assign b 2))

-----

1 point by rocketnia 5127 days ago | link

It sounds like you're ending up with the same thing as setforms. That'll at least work, but it's not what I thought you meant by having '= desugar to a function.

I thought you meant something like this:

  (= '(a b (c d)) '(1 2 (3 4)))
  -->
  (apply (fn (a b (c d))
           (export-locals-to-parent-scope))
         '(1 2 (3 4)))
And come to think of it, that's something which might actually make sense in an interpreter.

The setforms way is what I'd choose, at any rate. ^_^

-----

1 point by Pauan 5127 days ago | link

That would work, yes, but it'd require adding an `export-locals-to-parent-scope` built-in (or similar), as you say. Preferably, I'd like to define it in a way that's compatible with pg-Arc.

It's not a big deal, though. In a worst-case scenario, we can define `=` so it does a single level of destructuring. That should be simple to add, even in pg-Arc, while still providing some usefulness.

P.S. `with` expands to a function, so technically it is desugaring to a function. :P Your idea is quite a bit shorter and simpler, though.

Also... this just reminded me of something: lambdas in JavaScript. If I recall, there was a proposal for "thin" functions in JS, which (among other things) would not have "var" scope:

  function foo() {
    (lambda {
      var bar = 10;
    })()

    return bar;
  }

  foo() -> 10
Basically, any "var" statements would act as if the lambda wasn't even there, thus assigning them to the outer scope. After looking it up, it seems that "lambda" was changed to "#", which is quite a bit shorter (http://brendaneich.com/2011/01/harmony-of-my-dreams/).

A similar "thin-fn" form in Arc might be an interesting experiment: any assignments within the thin-fn would always assign to the outer scope. You could still assign locally, by using `let`, since that desugars to a "heavy" function:

  (def foo (a)
    ((thin-fn (a)
      (= a "foo")))
    a)
      
  (foo) -> "foo"
        
        
  (def foo (a)
    ((thin-fn (a)
      (let a nil
        (= a "foo"))))
    a)
        
  (foo) -> nil
Then we could have `=` desugar to a thin-fn, which would work without needing an `export-locals-to-parent-scope` built-in. The advantage of this approach is that it's more general: other functions might be able to make use of thin-fn's too.

...but, Arc fn's already behave like the lambda proposal for JS, so the problems that JS is trying to solve simply don't exist in Arc. Thus, it's arguable whether Arc should have "even thinner" fn's or not.

-----