"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))
? 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
(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.
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.
"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.
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:
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?
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.
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. ^_^
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:
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.
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
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.
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. ^^;
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:
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)):
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
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.
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:
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. ^_^
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.
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.
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 :)
"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 '#(tagged 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.
"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.
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....
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.
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
"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 "