Lisp has (a b c &optional opt1 (opt2 3) ..) which is perhaps most concise but kinda unlispy.
Scheme has no syntax for optional args, which seems incredible.
Arc uses (a b c (o opt1) (o opt2 3)) which is a few more parens than common lisp. Worse, using the symbol 'o' in this way seems like a wart on a 100-year language.
One solution I've been growing to like more and more is to just say (a b c (opt1 nil) (opt2 3)). But my solution right now can't distinguish destructuring and optional args when the default arg is a bound symbol.
Regardless of whether it can be done, what do people think of this syntax?
(a b (c d)) ; destructured
(a b '(c 3) 'd) ; optional args
The quotes are only required to resolve ambiguity.
(a b (c 3) (d nil)) ; optional args - defaults aren't variables
(a b '(c A) '(d) ; needs quote to disambiguate from destructuring
b) Quote destructured args to distinguish them from optional args.
(a b '(c d)) ; destructured
(a b (c 3) (d)) ; optional args
Unlike the previous case you'll never be able to drop the quotes. But you'll rarely need more than one.
c) Use dot to delimit optional args from required ones. So arg lists could now have 3 different zones: required/destructured args, optional, and rest/body args.
(a b . c d) ; optional args since you can only have one rest arg
(a b . c) ; rest args
(a b . (c)) ; c is optional - rest args will never be destructured.
(a b . (c 3) (d 4) . e) ; optional args with defaults and a rest arg.
The dot delimiter for optional args is only needed for disambiguation, so this is equally clear:
I dunno. It seems I'm in the minority, but I actually like using (o arg default). Only problem is the whole "don't use o when destructuring" thing, which is annoying. As for these options, I agree with conanite. In general, overloading either quotes or dots would be weird.
You could tweak o to be nicer to multiple parameters, though. E.g.,
(def trim (s (o where 'both) (o test whitec))
...)
If CL's &optional came to this forum it would say: If you're going to have a new keyword like o, at least take advantage of the fact that required args can't follow optional args, and get rid of the parens.
I like how you argue this is a rare case :) My motivation is purely aesthetic, under the assumption that I'll want to make a long term commitment to a 100-year language something like arc. I'm trying to add keyword arguments to arc, and I figure I might as well revisit this decision at the same time.
Two problems with (o arg default) - you need to remember not to use 'o at the start of a destructuring list (and not get confused when you see (def handle-request ((i o ip)) ...) ), and as akkartik says it's paren-inefficient, a single keyword to delimit required/optional args would mean fewer tokens.
The first problem is easy to fix though - use a symbol that's less likely to be an arg name to identify optional args. How about '= ?
(def myfun (a b (= c (something)) ...)
it has the advantage of similarity with ruby:
def myfun a, b, c=something
disadvantage: looks silly when you don't supply a default value:
Thinking about the paired-o suggestion I made, it has an advantage over grouping single optional arguments by their own parens -- at least if you have more than 2 optional parameters :). Compare:
(a b (opt-c default-c))
(a b (o opt-c default-c))
(a b (opt-c default-c) (opt-d default-d))
(a b (o opt-c default-c opt-d default-d))
(a b (opt-c default-c) (opt-d default-d) (opt-e default-e))
(a b (o opt-c default-c opt-d default-d opt-e default-e))
It's like a 3-character &optional, just with one of the characters at the end. However, you'd need to specify nil defaults by hand (though you might let an uneven pair at the end default to nil). E.g.,
(def markdown (s (o maxurl) (o nolinks))
...)
could, at best, become
(def markdown (s (o maxurl nil nolinks))
...)
But paired-o also generalizes the current behavior, so we could still use the original signature.
Suggestion: No one should be trying to use the word "quote" as a variable name. Therefore, if (quote o) or (quote optional) or (quote opt) [depending on how verbose we want the word to be; we could even make all of these work] shows up in a parameter list, then the rest of the list shall be optional args. If desired, we could do something similar for keyword arguments. Model:
(fn (a b 'opt (c 1) d e) ...) --> c,d,e optional args
(fn (a b (quote opt) (c 1) d e) ...) --> same thing
(fn ((a b)) ...) --> destructuring
I dislike the idea of declaring anything to be a separator--and therefore a reserved, special word--that isn't already illegal to put in a parameter list.
...egad. It turns out that it is not just legal, but it works fine, to use "quote" as a variable name just like any other, in Arc and Common Lisp and Racket.
Meanwhile, CL does disallow defining a function named "quote". It also disallows using 'flet to create a local function named "quote".
Well, what do you think of forbidding people to rebind quote (locally or otherwise)? I think it's acceptable. quote is a fundamental part of Lisp. If it is rebound, then either that will screw up quoted things, or the Lisp parser will handle (quote blah) forms specially, in which case rebinding quote to a function and attempting to call it will fail (you'll just quote the arguments). In other words, either this will fail to return 12:
arc> (let quote [+ _ 2] (quote 10))
12
Or this will cause a presumably unexpected error when '(1 2 3) is interpreted as something other than a quoted list:
I think both of these possibilities suck[1] and, for the purposes of formally specifying Arc, we should say "This is not supported; we recommend that an implementation throw an error when encountering an attempt to locally or globally rebind quote." I probably wouldn't make it illegal, but I'd make it print something like "COMPILER-WARNING: WTF, you're trying to redefine quote? This will probably not end well."
So, if using "quote" as a parameter is officially unsupported, then this officially makes room for "quote" to be used as a special marker in parameter lists. When a program that parses parameter lists encounters (quote blah), it should stop and say "Aha, this is not a legal parameter. What now?" And at this point we can give it whatever desired features in a nice, modular way.
In official Arc, we would have, say, "If blah is 'opt or 'o or 'optional, then proceed to interpret optional arguments." Then akkartik might add, "If blah is 'key, then proceed to interpret keyword arguments", and aw might add "If blah is 'as, then proceed to interpret coerced arguments", and these would be totally compatible extensions to Arc, as long as they didn't choose the same name.
I do think this is the way to go.
[1]A "Lisp-2 function/variable namespace separation" buff might say at this point, "Aha! See, with the namespace separation, this isn't a problem; you can use quote as a variable all you like and it creates no problems." Retort: "I might just as well want to locally create a quote function with flet, and then you have a problem. (And if your language doesn't let you locally bind functions, then it sucks.)" Example case:
(flet ((quote (x)
(format t "~S~%That's what she said!" x)))
...)
I've been thinking more about making the arc transformer ('compiler' seems excessive) simpler and easier to add hooks into. I don't want to hardcode keywords as non-overrideable; instead I want new keywords like coerced to be easily added to the transformer.
I've been considering using ssyntax in parameter lists to set apart special behavior in parameter lists, with destructuring being the normal behavior. (I was inspired by aw and someone else using (def foo (string:x) ...) syntax for coercing parameters, but I can't find that thread....)
Anyway, here's one of the takes on optional arguments I've been pondering:
(a b c .o opt1 nil opt2 3)
This is pretty similar to the Common Lisp approach, and you can even use &optional instead of .o for added similarity. :-p The main advantage in my mind is that it's less of an abstraction leak; Arc doesn't really support argument lists of the form ((o a) b), so a function with optional arguments has only one extra conceptual boundary in its argument list, and you should only have to punctuate it there.
For a more customizable approach (especially if you do want ((o a) b) to work after all), I added a backtracking pattern matching library to Lathe a while back (http://arclanguage.org/item?id=11956) just for the purpose of making destructuring more customizable. I didn't end up doing anything with it after that, but if and when I did, parameter lists would probably look like this:
(pfn (a b c (.o opt1) (.o opt2 3))
...)
The parameter list here would be wrapped up as '(struct (a b c (.o opt1) (.o opt2 3))) and compiled via the library to determine the variables it binds and what code it uses to bind them. In particular, the library would identify the 'struct operator as a "patmac," and it would use that patmac's implementation like a macro to determine what the rest of the expression meant. In the process, the expressions '(atom a), '(atom b), '(atom c), '(o opt1), and '(o opt2 3)--without the dots--would be sent off to another library that implemented a "list-patmac" system for monadic parsing of sequences.
As I mentioned, I never got this far. The second library doesn't exist at this point, and I haven't added even one patmac since I finished the proof of concept and made that post. It's not like it would be hard to get to this point in the design, but I've just been focusing on niftier things. ^_^;
So there you go, one straightforward syntax and one general-purpose syntax. The ultimate elegance struggle, eh? :-p
optional args don't map nicely to a list, they map more naturally to a dictionary (hash table).
Perhaps, the `o` is not needed?
(fn (a b c (d 1) (e) (f)) ....)
d is optional, but defaults to 1
e and f are optional with not default.
perhaps the builtin special function operator shouldn't take any named argument at all: just a list and a hash table, sort of like a generic python function:
My problem right now: fn((a b)) could define a function that takes a list of two args, or an optional arg a with a default value of b bound in the enclosing scope. This seems like a more serious problem than when I posted this thread.
Though (for what it's worth) I've since realized that I don't like the {a 1 b 2} syntax; I find the key value pairs aren't grouped together well enough visually for me, and the curly brackets don't stand out enough themselves.
Some optional arguments are better done as a list. The greater the number of optional arguments, though, the better off you are using keyword arguments.