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

"Yeah, but if we're having two different behaviors, then we wouldn't be using = for both, right? :P"

I meant to imply that you could reintroduce 'o for the official-Arc semantics and still use '= for the Python semantics. (Of course, you could pick a different name for the "reintroduced" 'o.)

---

"In any case, you should be able to use = as an argument name just fine. That's because ssyntax ... And on top of that , you can selectively disable/change ssyntax. So what's the problem? :P"

Er, what does ssyntax have to do with this? I assume by default that whatever ssyntax system you have will allow "=" as at least a global variable name, or else you'll have to come up with a new name for Arc's '=. :-p

You overlooked the only complaint I have--probably my fault for neglecting to explain it, sorry--which is that I'd like to be able to use '= at the beginning of a three-element destructuring parameter, however rarely such a case actually comes up.

That is to say, since I can destructure like this:

  (fn ((a b c))
    ...)
I want it to have the same meaning if I change a variable name:

  (fn ((= b c))
    ...)
Consistency's my only goal here. (That's not quite true: I value consistency 'cause it reduces the number of cases documentation and code-manipulating code need to handle, making both of those more convenient to read and write.)

By the way, having an a=b syntax for optional args is a nice idea. It wouldn't be groundbreakingly commonplace or save much indentation, but it would occasionally remove the need to edit in two places just to put in or take out a left paren. ^_^ It'd conflict with 'expand=list, but there's hardly a point to 'expand=list anyway.



2 points by Pauan 5131 days ago | link

But... that's a problem with most optional argument systems. In fact, the only way I can see to get around that would be to use something like this for optionals:

  (fn (a b ? c 1 d 2))
I figure = won't be terribly common with destructuring, hopefully. Less common than 'o, anyways. I could try making it so escaping it with |=| would work, but that seems clunky.

Actually... come to think of it, using ? for optionals would work nicely in PyArc, because all arguments are optional. The primary reason I disliked the ? optional syntax was because you needed to do stuff like this:

  (fn (? a nil b nil c nil))
Note the nil's after each argument name. But in PyArc, that's less of an issue, because you can just use this:

  (fn (a b c))
The only issue with that is, if you want to assign to an optional argument, and have it followed by more arguments, you need to do this:

  (fn (? a 1 b nil c nil))
Ew. Another idea would be to require specifying ? once per argument:

  (fn (? a 1 b c))
  (fn (a b ? c 1 ? d 2))
Kinda clunky, though... Hm... what if we replaced ? with = ...?

  (fn (= a 1 b c))
  (fn (a b = c 1 = d 2))
Kinda weird. Also that wouldn't allow us to use the a=b ssyntax, either. It seems every optional argument system has some flaw or other. Let's suppose we used .= so as to prevent problems when destructuring... then you come along and say that you want this to work:

  (fn (a b |.=| c d))
At some point, something has to give. :P I agree that consistency is good, but I haven't yet seen an optional argument system that works great in every situation. They all have some gotcha, so at some point we just gotta say, "this is good enough".

Is (= a b) good enough? I'm tempted to say yes, for the reasons I gave earlier: stands out better, less likely to conflict, synergy with assignment, and the a=b ssyntax. The only major issue is using = as the first element when destructuring. That's not too bad, I think, considering the alternatives...

Oh, by the way, there is one optional argument system that would work properly all the time... not using optional syntax at all. So you would do this:

  (fn (a b c)
    (or= a 1)
    (or= b 2)
    (or= c 3))
Of course, that's more verbose.

---

Also, my idea was to have two kinds of ssyntax: global and argument-only. So the a=b ssyntax would only apply in the arguments list, unless the user decides to make it global. Thus, no conflict with the global `expand=list`. Unless there's a desire to make it global, and fix expand=list...?

-----

1 point by rocketnia 5130 days ago | link

Ohh, you're totally right about there being a dilemma here. Here I was advocating the use of ssyntax symbols for things that weren't variable names, and I somehow overlooked how nonsensical that becomes when ssyntax is in the reader. XD Not to mention how nonsensical it is when ssyntax is customizable.

(I totally value both those things, but I came to a compromise myself a while ago and henceforth forgot there was a problem: The compromise, in Penknife, is to have fixed alphabets of punctuation and non-punctuation characters, and to treat punctuated things completely differently when parsing parameter lists. (Actually, advanced parameter list parsing isn't a near-future thing for Penknife; I plan to tackle it at the same time as regexes, which will be well after I've reengineered the core.) I don't know how much an alphabet separation would help you, but it's an option.)

Hmm, what about reading a=b as a tagged value? That way the different kinds of parameters nodes can be fully distinguished by calling [only.type _].

By the way, argument-only ssyntax is fine by me, as long as you can make sense of it. You really have to do ssyntax and macros in the same step in order to pull that off, so if you move to a read-then-expand model like official Arc's, you'll run into some trouble. You can have ssyntax read as tagged types and then expand those, but I'm not sure that's something you'd like.

-----

1 point by Pauan 5130 days ago | link

"Hmm, what about reading a=b as a tagged value? That way the different kinds of parameters nodes can be fully distinguished by calling [only.type _]."

So a=b would expand to (annotate 'optional '(a b)) ? I like how a=b has a really simple expansion though: (= a b) And then what if you want a destructuring list that has `annotate` as the first element?! :P

If you're talking about making it an internal detail: I already do. In fact, just last night I changed it so (o a b) evals at run time (like Arc), but (= a b) evals at creation time (like Python).

Actually, in PyArc, (o a b) is seen as a destructuring list, with o as the first element. But you can use the --backwards-compat switch to change it's behavior. This switch is intended to let you parse/eval existing Arc scripts in PyArc.

-----

1 point by rocketnia 5130 days ago | link

"So a=b would expand to (annotate 'optional '(a b)) ? I like how a=b has a really simple expansion though: (= a b) And then what if you want a destructuring list that has `annotate` as the first element?! :P"

Not quite. I'm suggesting a=b could expand to #(tagged optional (a b)), which is to say it would be the same as the result of evaluating (annotate 'optional '(a b)).

To illustrate, these expressions would be equivalent (if you don't consider memory consumption or mutation):

  '(def foo (a b c=d e)
     (+ a b c e))
  
  `(def foo (a b ,(annotate 'optional '(c d)) e)
     (+ a b c e))

-----

1 point by Pauan 5130 days ago | link

Okay, but then how would this work:

  (def foo (a b (= c 1) (= d 2)))
? Unless you're saying that you would need to use a=b for optionals, which I don't like. As I said, I like that a=b expands into (= a b). Very nice and simple, like the other ssyntax expansions.

So... your approach would work, in the sense that you can then use = while destructuring without causing problems, but then if you don't want to use the a=b ssyntax, you would need to do this:

  (with (c (annotate 'optional '(c 1))
         d (annotate 'optional '(d 2)))
    (def foo (a b c d)))
...which is pretty much the most verbose and complicated optional system I've seen yet. :P You'd be better off using these, I think:

  (def foo (a b ? c 1 d 2))
  (def foo (a b ? c 1 ? d 2))
Though... if I were willing to get rid of the symmetry between a=b and (= a b), I could use a hybrid approach. Then, I could support both ? and a=b for optionals:

  (def foo (a b c=1 ? d 2))
Seems kinda confusing, though, using two different syntaxes and semantics (individual infix/collective prefix) for the same thing.

P.S. I find it amusing that we're coming up with increasingly convolted and complicated systems just so we can allow = as the first element when destructuring. :P

-----

1 point by rocketnia 5130 days ago | link

...you would need to do this:

  (with (c (annotate 'optional '(c 1))
         d (annotate 'optional '(d 2)))
    (def foo (a b c d)))

My first impression of that code is that it doesn't do what you intend. Rather than using the tagged values held in the variables 'c and 'd, you're making new variables named 'c and 'd which happen to shadow the first ones.

I think what you're going for would need to look more like this:

  (def foo (a b #(tagged optional (c 1)) #(tagged optional (d 2)))
    ...)
Here, #(tagged ...) is vanilla Arc's reader syntax for tagged values. The result of reading #(tagged a b) is pretty much the same as the result of evaluating (annotate 'a 'b); in fact, they satisfy Racket 'equal?. However, this syntax is largely an implementation detail of official Arc, and I expect it would have an altogether different meaning in your system, so this is nothing more than a sketch.

Seeing this sketch, you might still say it's verbose to write #(tagged optional (a b)), and you might continue to say it's verbose if I change the example to use a syntax like {tag = a b}. (I dunno, you might not. ^_^ ) The thing is, we could avoid that syntax in most cases since a=b would work just as well.

---

Oh, I forgot to mention this the first time you brought it up, but the "?" alternatives don't help with this particular issue, since I might want to name a variable "?" too. It's not that I really need to name a variable "?", any more than I really need to name a variable "o" or "=", but it's the same principle.

---

"P.S. I find it amusing that we're coming up with increasingly convolted and complicated systems just so we can allow = as the first element when destructuring. :P"

Well, depending on how much things like this are important to you, they might influence your design of the whole language. A solution can seem convoluted in one language and straightforward in another, and not just 'cause the other provides convoluted libraries. :-p IMO, finding convoluted solutions to inane problems is a great exercise, 'cause sometimes two inane problems end up sharing a solution, and that solution becomes a more obvious thing to try to streamline when building a new foundation.

-----

1 point by Pauan 5129 days ago | link

Yeah, I couldn't really think of a good way to do it without using quasiquotation + unquote. Which pretty much only proves my point further that it's a clunky system that relies heavily on the a=b ssyntax to reduce the clunkiness. :P

Which is okay, if you want users to always use a=b for optional arguments, but I don't think that's so great if you want a=b to desugar to S-expressions (like the other ssyntaxes). I guess it's just the eternal battle between simplicity and correctness.

---

Yeah, but it does help with the issue of destructuring. See what I mean about no system being perfect? Anyways, if I used the hybrid approach, I would expect this to work:

  (def foo (a b ?=nil))
Since ssyntax is expanded at read time, this would bypass the ? symbol detection. Thus, as near as I can see, the hybrid system would work in every situation, but at the cost of using two different systems to accomplish the same thing (optional arguments).

That's fine if you don't mind the additional complexity. But I tend to like simplicity, even at the cost of small corner-case issues.

---

I'm not necessarily saying it's a bad thing that we're discussing alternatives (even convolted ones), just that I found it amusing. I personally consider that system too convolted, but it might make for an interesting experiment. And as you say, sometimes experimenting with convolted systems can lead to wonderful new solutions.

-----

1 point by rocketnia 5129 days ago | link

"Yeah, I couldn't really think of a good way to do it without using quasiquotation + unquote."

In PyArc, I believe you. A syntax like #(...) doesn't work if # is going to be interpreted as prefix syntax. That said, is {tag = a b} not an option? No worries either way, I'm just happy you know what the heck I'm talking about. ^_^

With all this talk about this one idea, I've been meaning to throw yet another idea at you: How about an alternate way to destructure? If (= a b) makes an optional argument 'a, maybe something like (% = a b) could mean three destructuring local variables '=, 'a, and 'b. A macro that wants to destructure using a user-supplied variable could use the (% ...) version of destructuring, rather than letting '= mess things up or jumping through more complicated hoops to deal with it.

In fact, % and = could someday be part of a user-extensible framework of pattern matchers, although that would take a lot more infrastructure.

---

"Since ssyntax is expanded at read time, this would bypass the ? symbol detection."

Whoa, nifty.

---

"But I tend to like simplicity, even at the cost of small corner-case issues."

And the reason I care about corner-case issues is 'cause I like the simplicity of not having to deal with them. ^_^

Well, I could just refuse to deal with them anyway, but then I sacrifice correctness. So it is a simplicity-and-correctness struggle after all.

---

"I'm not necessarily saying it's a bad thing that we're discussing alternatives (even convolted ones), just that I found it amusing."

Yeah, sorry for giving a more serious and advisory sort of response, I know just what you mean. ^^ For some reason, when I'm in a forum setting, I tend to take the time to refine my words into diamonds of ultimate seriousness.

-----

2 points by Pauan 5129 days ago | link

It's an option in the sense that it would work, but I dislike it because then the user must either A) use the syntax, or B) use eval with quasiquote + unquote. I really like that almost all [1] syntax expands to plain old S-expressions, and would like to keep it that way as much as I can.

Yeah, we could go the alternate route: rather than trying to fix optional syntax, we could change destructuring, since it seems to be the major pain point. If I recall, akkartik was talking about using ' for destructuring:

  (def foo (a b '(= c d)) (list a b = c d))

  (foo 1 2 '(3 4 5)) -> (1 2 3 4 5)
I kinda like that, actually. Makes it clear that the (= c d) part isn't going to be evaluated. Of course, that would mean hardcoding (quote ...) to mean "destructuring" when in the argument list, but that's not really any worse than hardcoding (= a b) to mean "optional assignment".

In fact, I like that enough that I'm going to implement that in PyArc. You can use --backwards-compat to use the old behavior for destructuring. In case you're worried, it would work even if you want to use quote as a normal argument name or as an argument name when destructuring:

  (def foo (quote '(quote a b)))
This also completely frees us from syntax worries. If we want to use (& a b) for something, we don't need to worry about it conflicting with destructuring. The only downside is that now (quote ...) is reserved for destructuring, so you can't use (quote ...) to mean something else. Until I add in customizable semantics for argument lists...?! :P

And as an added bonus, I can make it so it distinguishes between symbols and lists:

  (def foo ('a '(b c)))
So we can have ' followed by a symbol mean something different than ' followed by a list. Not sure what it would mean, but it's a possible future option.

This also fixes one thing I wanted to add: destructuring assignments. In Python, you can use this:

  a, b = (1, 2)
But I don't know of any analogs in Arc. But I could change = so it destructures when the first argument is (quote ...), like so:

  (= '(a b) '(1 2))
Which does the same thing as the Python version. Basically, this would assign 'a to 1, and 'b to 2. Useful if you call a function and it returns a list, since then you can destructure it into separate variables. Consider the case where a function returns a list, but you only care about the first item:

  (def foo () (list 1 2 3))

  (= '(a) (foo)) -> a is now 1
Oh, and this would lead to a possible use for "," as well:

  (= a,b (foo))
But that's not super important. Just something to think about.

[1]: The only syntax I can think of that can't/doesn't expand to S-expressions is "." I could even change PyArc so "" gets expanded to (string ...) and #\ gets expanded to (char ...). I find this one-to-one mapping between syntax and S-expressions really neat.

---

Oh, yeah, and if you had ? and not = you could even do this:

  (def foo (a b ? ? nil))
Would it work? Yes. Would it be confusing? Yes.

-----

1 point by rocketnia 5129 days ago | link

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.

-----

1 point by akkartik 5130 days ago | link

Making optionals a first-class datatype is certainly an interesting idea, but it sounds like you're giving it special compile-time semantics. Interesting/weird.

-----

1 point by rocketnia 5130 days ago | link

Could you be more specific? Arc already gives 'o "special compile-time semantics," so that hardly seems like the weird part.

-----

1 point by akkartik 5129 days ago | link

o is a keyword. No different from assign or tagged or if. Your suggestion is to match a 'keyphrase': (tagged optional _) or even having to detect then evaluate things like (annotate 'optional 'a) at compile-time. Maybe even read time. Does that ever happen right now?

I'm having trouble articulating myself, but it definitely seems like another whacky rocketnia idea :)

-----

1 point by rocketnia 5129 days ago | link

"o is a keyword. No different from assign or tagged or if."

You might already know this (search for "AST" at http://arclanguage.org/item?id=13079), but I'm irked by special forms like 'assign or 'if too. I like reducing the places where some message almost always has one meaning, unless it's some particular message which has a completely different meaning. I'm just fine when language designers make knowing sacrifices, but whenever possible, I want to be able to say "the eagle has left the nest" to talk about actual eagles--or at least to be sure I won't ever want to talk about actual eagles.

I think 'o is a place where neither sacrifice is necessary (neither giving up (o b c) destructuring nor giving up 'o as a variable name).

As for 'tagged, that's a symbol you can't observe from Arc unless you drop to Racket, whereas macros see the symbol 'o all the time.

---

"Your suggestion is to match a 'keyphrase': (tagged optional _) or even having to detect then evaluate things like (annotate 'optional 'a) at compile-time. Maybe even read time. Does that ever happen right now?"

Eh? O_o You're all over the map in misinterpreting me (which isn't something I blame you for).

Some parts of what you're talking about "happen right now" in PyArc, as far as I understand: Under default options, there's no distinction between compile time and read time, and macros are expanded in that one step, just like ssyntax is. This isn't necessarily the final reader design, since it causes problems with things like (afn ((= a b)) ...). Keep in mind that I'm talking about solutions for PyArc in this turbulent time, so suggestions I make may be more generalized, more wacky, and more misinterpretable than the same suggestions applied to any given concrete version of PyArc. ^^;

The solution I'm talking about does apply to official Arc too, except that official Arc doesn't have ssyntax in the reader, so the approach isn't as practical. Official Arc does save ssexpansion until surrounding macros have done their work (post-read-time), so we can use an easier solution like '.o instead.

Here's exactly how the tagged value approach would look in official Arc:

  (def foo (a b #(tagged optional (c d)) e)
    (+ a b c e))
Note that #(tagged a b) is read as a tagged value:

  arc> (type '#(ta­gged a b))
  a
  arc> (mac stx-types args `',(map type args))
  #(tagged mac #<procedure: stx-types>)
  arc> (stx-types 4 a (b) () "c" #\d #(tagged e f))
  (int sym cons sym string char e)
However, I've never seen anyone write a literal tagged value this way, and I think we oughta consider it to be mostly an implementation detail, since it piggybacks on Racket vector literal syntax. The most useful thing we'd do with it is serializing a tagged value to a file and reading it back in. Still, it does "happen right now."

By the way, if the #(...) syntax didn't exist, we could still define functions with optional arguments this way:

  (eval `(def foo (a b ,(annotate 'optional '(c d)) e)
           (+ a b c e)))
I'm not saying it's a good idea. Like I said, this idea is targeted at PyArc, where a read-time a=b syntax would easily be the most attractive way to stuff values of type 'optional into the code.

-----

2 points by Pauan 5129 days ago | link

"You might already know this (search for "AST" at http://arclanguage.org/item?id=13079), but I'm irked by special forms like 'assign or 'if too."

Yeah, I don't like those either. In fact, I was thinking about making `fn` a macro in PyArc. Also, if I can, I plan to allow programs to shadow things like `assign` and other special forms:

  (def foo (bar assign)
    (= bar "something"))

  (foo) -> error, because = uses assign, but assign is nil
I've also tried to keep the number of special forms in PyArc to an absolute minimum. Right now, there's quote, quasiquote, if, fn, assign, and apply. I hope to get rid of apply, and possibly fn.

The only way I can see to get rid of if, assign, quote, and quasiquote would be to create a third "function" type: it behaves like a function, so it's evaluated at run time, but it doesn't evaluate it's arguments, so it's basically a function/macro hybrid.

Haha, I just had a crazy idea. Make (fn) desugar into the special hybrid form, but have it evaluate all it's arguments. Then there would only be two function types, and (fn) would be a thin wrapper around one of them. I should be able to get rid of all the special forms then, leaving only functions and macros (which are just annotated functions anyways).

---

"I think 'o is a place where neither sacrifice is necessary (neither giving up (o b c) destructuring nor giving up 'o as a variable name)."

But you do give up (o a b) destructuring:

  (def foo ((o a 1) (o b a)) (list a b))

  (foo '(1 2 3) '(4 5 6)) -> ((1 2 3) (4 5 6))
Unless of course you're talking about ab/using ssyntax like .o, in which case you're not really using (o a b) anymore, nor would it work if ssyntax is expanded at read time.

Thus, it has the same gotcha as (= a b), which is that you can't use o as the first element when destructuring. By the way, I mentioned earlier that I could treat escaped symbols differently:

  (def foo ((|=| a b)) (list = a b))

  (foo '(1 2 3)) -> (1 2 3)
I think this approach would be better than using ssyntax, because || already means "escape this thing", so we don't need to invent new syntax for it. Not sure if I'll go that route, but it is an option.

-----

1 point by rocketnia 5129 days ago | link

You have 'apply as a special form? In most Schemes it's a function. :)

You can definitely implement 'quasiquote and 'if as macros.

That leaves 'quote, 'fn, and 'assign. Those are all pretty fundamental. While you could make them macros, there's pretty much nothing to turn them into, unless you go for the tagged type route.

---

"The only way I can see to get rid of if, assign, quote, and quasiquote would be to create a third "function" type: it behaves like a function, so it's evaluated at run time, but it doesn't evaluate it's arguments, so it's basically a function/macro hybrid."

Hey, look, fexprs. XD Every sufficiently long Arc Forum topic seems to get around to them at some point. :-p

Since you're writing an interpreter, fexprs are a natural thing to do. They have certain elegance advantages over classical macros, since you can avoid special forms altogether, and programmers can (re)define their fexprs after the places they use them. The main disadvantage is that they're much harder to efficiently compile--not an issue for an interpreter.

---

"Haha, I just had a crazy idea. Make (fn) desugar into the special hybrid form, but have it evaluate all it's arguments. Then there would only be two function types, and (fn) would be a thin wrapper around one of them. I should be able to get rid of all the special forms then, leaving only functions and macros (which are just annotated functions anyways)."

Kernel, the most promising design I've seen for an efficient fexpr language (er, with lexical scope; sorry, PicoLisp) does the opposite: Functions are annotated fexprs. All forms are applied in exactly the same way, but functions take the extra step of evaluating their arguments. (FYI, Kernel calls functions "applicatives" and fexprs "operatives.") The one thing about Kernel is that it's kinda vaporware....

There was recently an Arc Forum topic about Kernel: http://arclanguage.org/item?id=13354

Here are a couple of topics about PicoLisp while I'm at it: http://arclanguage.org/item?id=1110 http://arclanguage.org/item?id=10829

Eight's another fexpr language with lexical scope. Like Kernel, it isn't quite complete, but at least it's developed in the open on GitHub. (Hi diiq!) There's a very old topic on Eight here: http://arclanguage.org/item?id=10719

Recently, like a few months ago, diiq came back to Eight and reimplemented it in JavaScript. I've been too busy to bother with it yet, but it oughta be awesome if it's both similar to Arc and runnable in a browser.

---

"Unless of course you're talking about ab/using ssyntax like .o, in which case you're not really using (o a b) anymore, nor would it work if ssyntax is expanded at read time."

That's exactly the kind of thing I'm talking about. :) That's a solution I'm satisfied with for official Arc, but I'm confident there'd be at least some solution for PyArc that I liked better than 'o. It may just be too early to identify it.

---

"By the way, I mentioned earlier that I could treat escaped symbols differently: [...] I think this approach would be better than using ssyntax, because || already means "escape this thing", so we don't need to invent new syntax for it. Not sure if I'll go that route, but it is an option."

Well, I'm not sure what s-expressions you consider the 'def macro to see as input. If you manage to be consistent about that, and if you don't actually need to use the || in practice too much, then it's an idea I could get behind.

-----

1 point by Pauan 5129 days ago | link

It's both a function and a special form. Yes, it's weird. Yes, I hope to change it.

I don't see how `if` could be implemented as a macro. Consider this case:

  (def foo (a) (if a 1 2))
The `if` form needs to be evaluated every time the foo function is run, and then return either 1 or 2, depending on what 'a is. Keep in mind this is Python, so I can't have `if` desugar to `cond`. :P

---

Yeah, that's what I was talking about: have `fn` be a fexpr that returns functions that are annotated with type 'fn. And since macros are just functions with type 'fn, that would mean that everything in PyArc boils down to fexprs.

I didn't realize that what I described was fexprs, but in hindsight I probably should have. I actually messed around with NewLisp for a bit; they use fexprs. I prefer Arc's macro system, since I don't think NewLisp has quasiquote/unquote, which made certain things more verbose than they should have been.

However, the ability to have everything desugar to fexprs is appealing, especially since I no longer need special forms hardcoded into eval. PyArc will still have normal functions and macros, of course. They'll just be fexprs underneath. :P

-----

1 point by rocketnia 5129 days ago | link

Here's a version of 'if that depends only on a built-in function and a built-in macro:

  (def fn-if (condition then else)
    ((if condition then else)))
  
  (mac if body
    (whenlet (first . rest) body
      (iflet (then . elses) rest
        `(fn-if ,first (fn () ,then) (fn () (if ,@elses)))
        first)))
These implementations are just for demonstration, since they depend on 'if themselves.

-----

1 point by Pauan 5129 days ago | link

Huh, interesting. I think I'll implement it as a fexpr, though, both for consistency (with the other special forms) and simplicity.

-----

1 point by rocketnia 5129 days ago | link

Oops, no time to comment right now, but I figure I'll mention that I just finished ninja-editing that comment to put in links to PicoLisp and Eight.

-----

1 point by Pauan 5129 days ago | link

"Eight's another fexpr language with lexical scope. Like Kernel, it isn't quite complete, but at least it's developed in the open on GitHub. (Hi diiq!) There's a very old topic on Eight here: http://arclanguage.org/item?id=10719 "

Okay, that's a very cool idea: using ' to mean "don't evaluate the argument" (http://arclanguage.org/item?id=10745):

  (def foo ('a b) (list a b))

  (foo (+ 1 2) (+ 1 2)) -> ((+ 1 2) 3)
Of course, actually trying to implement that in PyArc sounds tricky, but that's okay. I can always add it later.

-----

1 point by akkartik 5129 days ago | link

Ah ok, I clearly haven't digested the implications of PyArc.

-----

1 point by rocketnia 5130 days ago | link

I've been meaning to ask, how are you doing macros in the read step? I take it they're able to parse their bodies as text, or else things like argument-only ssyntax wouldn't even be an option. This would make them similar either to Penknife syntaxes or Scheme/CL reader macros, depending on whether their input is string-like or stream-like. But doesn't this mean they're (at least sometimes) defined in a completely different way from Arc macros?

-----

1 point by Pauan 5130 days ago | link

I tokenize the input stream (which is probably a string) one step at a time, and when I see a "(" I then keep parsing until I find a closing ")" Now, it's available as a list, so I check if the car is a macro, and if so I then call it with the remaining arguments, and return that.

However, I ran into a bit of a problem while parsing arc.arc, so I'll likely be refactoring that system. For instance, I may want it to only expand macros one level, rather than doing them all. The problem is, let's say you have this:

  (mac foo () "something")
  (mac bar () (foo))
If you use macex in Arc, you would get this:

  ... (fn nil "something") ...
  ... (fn nil (foo)) ...
But in PyArc, you would get this:

  ... (fn () "something") ...
  ... (fn () "something") ...
Oops! Note how it expanded the (foo) call, whereas Arc didn't. In other words, PyArc is a little bit too greedy about expanding macros.

In any case, the whole tokenize/parse/transform/macro expand stage is handled at the same time, so once that part's done, it can just use eval on the rest.

---

As for handling argument-only ssyntax, I've been wondering about that myself. But here's my current idea: when eval sees an (fn) form, it then creates and returns a (Python) function, which it can then execute later. Arguments and closures are handled when the function is created, so I should be able to do ssyntax expansion then.

And since macros are just annotated functions, I figure that approach will work with macros too. Thus, global ssyntax expands at read time, but argument ssyntax expands when the function is created, but before it's actually run.

-----

1 point by rocketnia 5130 days ago | link

Ah, was afraid of just that gotcha. It means if you say (afn ((= b c)) ...), you'll end up expanding the = macro before even realizing it's an argument list. o.o; (Right?)

There's another approach I thought you might have taken, and it's free of that trouble. You mentioned looking for (fn) forms, and it's probably the same general idea: When you encounter a left paren, read the next expression after that, and use it to determine what happens next (like a reader macro). If it's a symbol, look it up and see if it's bound to a macro, and let the macro (somehow) take care of reading its body and finding the right paren. Otherwise, read elements until you get a right paren, and treat the expreaaions as a function call.

Unfortunately, I don't know how to polish up that idea to make syntax like "a.(b c).d" work out correctly, especially in cases like "catch.(b throw).d" where 'catch determines the meaning of 'throw. Actually, I could figure something out for that too, but it would be a bit arbitrary, and you may already know you're not headed this way. :-p

-----

1 point by Pauan 5130 days ago | link

Yes. I'll need to do some thinking on how to fix macro expansion.

---

Actually, I've been thinking about looking into reader macros, and seeing if I could use something similar to implement customizable ssyntax. That would have the benefit of potentially allowing for more powerful syntax.

-----