Arc Forumnew | comments | leaders | submitlogin
Optional args in Clojure: destructure the rest parameter (thinkrelevance.com)
3 points by akkartik 2024 days ago | 53 comments


1 point by evanrmurphy 2023 days ago | link

Oh wow. Did we never consider this possibility in either thread dedicated to the topic? +

  ; arc3.1                     ; arc4.0 ?
  
  (def foo (a b (o c) (o d))   (def foo (a b . (c d))
    ...)                         ...)
Beautiful! And you could even get a rest parameter in addition to the optionals by making it an improper list:

  > (def bar (a b . (c . args))
      (list a b c args))
  #<procedure:bar>
  > (bar 1 2)
  (1 2 nil nil)
  > (bar 1 2 3)
  (1 2 3 nil)
  > (bar 1 2 3 4)
  (1 2 3 (4))
  > (bar 1 2 3 4 5)
  (1 2 3 (4 5))
It seems too good to be true. What's the problem with this?

---

+ http://arclanguage.org/item?id=12565, http://arclanguage.org/item?id=13029

-----

2 points by waterhouse 2022 days ago | link

All Lisp readers I know of treat '(a . (b c)) the same as '(a b c).

  arc> (read "(a . (b c))")
  (a b c)
The reason is, '(a . something) = (cons 'a 'something), and if "something" is a list like '(b c d), then '(a . (b c d)) = (cons 'a '(b c d)) = '(a b c d). You can even write code like this.

  * (+ 1 . (2 3)) ;SBCL
  6
This has the advantage of making it really easy to write a basic print function:

  (def print (x)
    (if (acons x)
        (do (disp "(")
            (print (car x))
            (disp " . ")
            (print (cdr x))
            (disp ")"))
        <handle remaining cases: x is a symbol, number, string...>))
I'm not too strongly opposed to this change, but it would break compatibility with probably all existing Lisp readers, including Racket's.

-----

2 points by rocketnia 2022 days ago | link

This is a bit off-topic, but technically, Racket's syntax reader treats (a b c) and (a . (b c)) differently. It wraps just about every node of the parse tree in a syntax object, but the exceptions are the nodes which, from a certain point of view, aren't even part of the parse tree: The list tails which aren't written using their own parentheses. In particular, the (b c) part of (a b c) isn't wrapped, but the (b c) part of (a . (b c)) is.

  Welcome to Racket v5.1.
  > (define abc (read-syntax #f (open-input-string "(a b c)")))
  > (define a.bc (read-syntax #f (open-input-string "(a . (b c))")))
  > (define abc.null (read-syntax #f (open-input-string
                                       "(a b c . ())")))
  > abc
  #<syntax::1 (a b c)>
  > a.bc
  #<syntax::1 (a b c)>
  > abc.null
  #<syntax::1 (a b c)>
  > (syntax-e abc)
  '(#<syntax::2 a> #<syntax::4 b> #<syntax::6 c>)
  > (syntax-e a.bc)
  '(#<syntax::2 a> . #<syntax::6 (b c)>)
  > (syntax-e abc.null)
  '(#<syntax::2 a> #<syntax::4 b> #<syntax::6 c> . #<syntax::10 ()>)

-----

2 points by akkartik 2022 days ago | link

Yeah I would be opposed to using . this way. But we could use a different token.

-----

2 points by Pauan 2022 days ago | link

But, let's suppose we used ? for the token:

  (def foo (a b ? (c d . e)))
How is this better than:

  (def foo (a b ? c d . e))
?

True, the first version is more explicit, and the parens make it clearly distinct from the required arguments, but then again, it does add an extra level of (arguably useless) parens. I still think it looks nicer than Arc's current version, though:

  (def foo (a b (o c) (o d) . e))

-----

1 point by evanrmurphy 2022 days ago | link

If you're going to create a special syntax for optional arguments, then I agree with you. But why uphold the required arg / optional arg distinction at all? If you make function arguments always optional (as they are in JavaScript) then you need no special syntax, and it is more concise.

What do you think of this model?

-----

3 points by Pauan 2021 days ago | link

As somebody who has programmed for years in JavaScript, I am certainly used to that style, but I actually like required arguments, even if simply for error checking. If I call a function, and it spits back an error, "function expected 3 arguments (2 given)" then I have a pretty good idea of what to do. When the function call fails silently, however, it can lead to some icky debugging situations.

On the other hand, even if you made all arguments default to nil, you could still have some error checking:

  (def foo (a b c))
  
  (foo)          ; (a b c) are (nil nil nil)
  (foo 1 2)      ; (a b c) are (1 2 nil)
  (foo 1 2 3)    ; (a b c) are (1 2 3)
  (foo 1 2 3 4)  ; error: expected 0 to 3 arguments (4 given)
  
In other words, it would still be possible to throw an error when calling with too many arguments, but not when calling with too few.

I'm pretty okay with that tradeoff, actually. It gives up a bit of safety and control, but in exchange it avoids the whole icky mess with "what syntax do we use for optional arguments?" I might try that approach with my interpreter.

In fact, we could reverse the question. Rather than asking, "how do we define optional arguments?" we can instead ask "how do we define required arguments?"

In other words, all arguments default to nil, but it's possible to specify certain arguments as required, or to change what the default is. That may be a better approach than specifying what arguments are optional.

There is one problem with that approach, though. You'll likely want a way to specify what the default for an argument is, which would require some sort of construct. But then if you want to specify a required parameter, you'll need a second (different) construct.

If you have required arguments by default, then you can use a single construct to specify both optional arguments and what their defaults are. But if you have optional arguments by default, you need two constructs.

Unless you're suggesting to not have required arguments at all, and require the programmer to manually write an (if (no a)) check and manually throw an error? That could work, assuming most people don't need required arguments most of the time.

You could even wrap it up in a macro or something, like this:

  (def foo (a b c)
    (require a))
Which could be extended for type information as well:

  (def foo (a b c)
    (require a 'sym))
Which would specify that the first argument is required, and must be a symbol. I kinda like that approach, though it does mean running checks at runtime. On the other hand, how frequently do you actually need those checks? Not very often, right? So that could work, I think.

-----

3 points by Pauan 2021 days ago | link

By the way, here's a quick mock-up for the `require` macro. This assumes that all arguments are optional:

  (mac require (n t)
    `(if (no ,n)         (err (string "parameter " ',n " is required"))
         (no:isa ,n ,t)  (err (string "parameter " ',n " must be of type " ,t))))
When called with (require a) it will throw an error if `a` is nil.

When called with (require a 'cons) it will throw an error if `a` is nil, or if `a` is not of type 'cons.

-----

1 point by shader 2021 days ago | link

Here's a more functional version of require that works in the current version of arc:

  (mac require (n (o typ))
    `(if (and ,typ ,n (no:isa ,n ,typ))
           (err:string "parameter "
                       ',n
                       " must be of type "
                       ,typ)
         (no ,n)
           (err:string "parameter " ',n " is required")))

-----

1 point by shader 2021 days ago | link

Interesting, there is a bug (feature?) in arc that lets you shadow t in a function, even though you normally can't rebind it. Should this be fixed?

-----

1 point by Pauan 2021 days ago | link

Why? `t` is a nice and short variable name, and I would expect it to work fine in function arguments. Hm...

  ((fn (t) t) 10)         -> 10
  ((fn (quote) quote) 10) -> 10
  ((fn (nil) nil) 10)     -> nil
Seems nil doesn't like being shadowed. Works fine in my interpreter, though: they all return 10.

-----

1 point by rocketnia 2021 days ago | link

"Seems nil doesn't like being shadowed."

That's because ((fn (nil) nil) 10) is supposed to be the same as ((fn (()) nil) 10), which destructures the first 0 elements from 10, treating it as a degenerate cons list.

I actually use this behavior in practice, 'cause it's useful to have at least some parameter syntax that doesn't bind any variables, rather than just using a conventional name like "ignored" or "_". Most of the time I use it in a much-too-big 'withs block, where I abuse one of the bindings for sanity-checking or other side effects. ^^ I wouldn't worry about it too much, though.

On the other hand, nil's behavior makes it more verbose to determine whether something's usable as a local variable; you have to check (and x (isa x 'sym) (~ssyntax x)). In Lathe, I define 'anormalsym to do just that. (It might be more accurate to change it to (and (isa x 'sym) (~in x nil t 'o) (~ssyntax x)).)

Speaking of (~ssyntax x), ssyntax is usable for local variable names, but naturally, you can only get the values from the Racket side:

  arc> (let a.b 4 a.b)
  Error: "reference to undefined identifier: _a"
  arc> (let a.b 4 ($ a.b))
  4
That could be a bug.

Personally, I'd like for ssyntax-burdened names in parameter lists to use user-defined parameter syntaxes, like custom forms of destructuring and whatnot. And then, just for axiomatic simplicity's sake, I'd like things like 'o to be implemented using the same system, using ssyntax-burdened names like ".o".

-----

1 point by Pauan 2021 days ago | link

"That's because ((fn (nil) nil) 10) is supposed to be the same as ((fn (()) nil) 10)"

nil is such a weird value... it's false, a symbol, and an empty list all at once. I actually had to do some hacky stuff to get () and '() and nil to behave properly.

"Speaking of (~ssyntax x), ssyntax is usable for local variable names, but naturally, you can only get the values from the Racket side:"

I plan to implement ssyntax at the reader level in my interpreter, so that shouldn't be an issue.

-----

1 point by evanrmurphy 2021 days ago | link

"nil is such a weird value... it's false, a symbol, and an empty list all at once."

Perhaps nil should serve as the base case for every type, rather than just some of them. In Arc, it already does this for booleans, lists and strings (""), but it could be extended to support tables (#hash()), numbers (0), symbols (||) etc. Then it would have more consistency as a concept.

-----

2 points by shader 2021 days ago | link

I've run into issues with nil the symbol vs nil the value. It's caused some issues when I wanted to use arc's sym type to represent variable names in a class compiler project I'm working on. If the variable's name is nil, all kinds of things stop working, and I've had to resort to calling some scheme functions directly to get proper handling of symbols.

I wish that 'nil and nil could be kept somewhat separate, with the first being just a symbol, and the second being equivalent to the base value for all data structures. Otherwise the symbol type is broken and inconsistent, since you cannot accurately (or at least completely) represent code as data.

-----

2 points by Pauan 2020 days ago | link

My interpreter treats 'nil and nil as separate. Not sure if that's a good idea, but I'd rather wait and see if it causes problems before changing it:

  (is 'nil nil) -> nil
This does raise one interesting question, though... obviously 'nil is a sym, but if (is nil ()) is t, then shouldn't (type nil) return 'cons?

-----

2 points by waterhouse 2019 days ago | link

() is not a cons cell, and while (car ()) is well-defined (to be nil), you can't set the car or cdr of (). It is definitely not a cons.

On the other hand, (listp nil) should be true. In fact, its precise definition should probably be (and is, or is equivalent to) this:

  (def listp (x)
    (or (is x nil)
        (and (acons x)
             (listp (cdr x)))))

-----

1 point by Pauan 2018 days ago | link

However, I can easily define nil so that it's type is 'cons, but it would throw an error if you try to assign to the car or cdr. That may break code that assumes that 'cons != nil though.

Actually, I could represent nil as an actual 'cons cell, so that assigning to the car or cdr would work. Crazy? Probably. Especially since nil is a singleton and you can't create more nil's, so it would be a global change.

Right now, nil does have a type of 'sym, but it seems weird to treat it mostly like an empty cons cell, but not have it's type be 'cons. So I figured I could play around with it and try giving it a type of 'cons and see how badly it breaks stuff.

-----

2 points by evanrmurphy 2018 days ago | link

"Actually, I could represent nil as an actual 'cons cell, so that assigning to the car or cdr would work. Crazy?"

That's a bit crazy. :)

PicoLisp does something reminiscent. Every one of its data structures (numbers, symbols, nil and conses) is implemented using the low-level cons cell structure (i.e. a pair of machine words). [1] They talk about nil's representation fulfilling its dual nature as both a symbol whose value is nil and a list whose car and cdr are nil; both the symbol predicate and the list predicate return true when applied to nil:

  : (sym? NIL)
  -> T
  : (lst? NIL)   
  -> T
I'm not sure that they let nil's car and cdr be assignable though, because "NIL is a special symbol which exists exactly once in the whole system." [2]

--

[1] http://software-lab.de/doc/ref.html#vm

[2] http://software-lab.de/doc/ref.html#nilSym

--

Update: Oops, I just noticed a lot of this comment could be considered redundant with the grandparent comment by waterhouse. Sorry for that.

-----

1 point by rocketnia 2021 days ago | link

My code has a lot of [string:or _ "nil"] to handle that kind of stuff. ^_^ I've just kinda accepted it ever since http://arclanguage.org/item?id=10793.

I do 'string lists into strings a lot, but that would continue to work if 'nil and '() were separate values.

-----

1 point by aw 2021 days ago | link

If the variable's name is nil, all kinds of things stop working

Can you give an example? Are you trying to use nil as a variable name in Arc, or simply to have a data structure representing variables where some of those variables are named nil?

-----

1 point by shader 2019 days ago | link

I'm creating a compiler for a class, and I've been representing variable names in the ast with symbols. I could use strings instead, and may end up doing so, but symbols seemed a more elegant solution.

-----

1 point by Pauan 2020 days ago | link

So nil would be an ubertype that basically just means "empty"? One issue with that is (is 0 nil) would be t... I think it's useful to not always treat 0 as the same as nil.

Not sure about empty tables, though... maybe it would be fine to treat those as nil. We already treat an empty list as nil, after all.

Actually, what you're suggesting is very similar to how languages like JavaScript and Python do it, with 0 null undefined NaN "" etc. being falsy. So in JS, you can do this:

  var foo = 0;
  if (foo) {} // will not be evaluated

  foo = "";
  if (foo) {} // will not be evaluated

  foo = NaN;
  if (foo) {} // will not be evaluated
On the other hand, both those languages support actual Booleans, and they treat an empty array as true.

-----

1 point by rocketnia 2021 days ago | link

Yeah, t should be rebindable, right? :)

-----

2 points by aw 2021 days ago | link

http://awwx.ws/localt0

-----

1 point by rocketnia 2021 days ago | link

I mean making 't an everyday global variable, like fallintothis does at http://arclanguage.org/item?id=11711.

As far as prior work goes, I think this topic is the last time "can't rebind t" came up: http://arclanguage.org/item?id=13080 It's mainly just me linking back to those other two pages, but it's also a pretty good summary of my own opinion of the issues involved.

-----

2 points by aw 2021 days ago | link

In my runtime project, nil and t are ordinary global variables... if someone turns out to want the rebind protection feature, I'll add it as a compiler extension (which other people could then choose to apply, or not, as they wished).

-----

1 point by Pauan 2021 days ago | link

What I would do is make it print a warning, but still allow it. Something like, "hey, you! it's a bad idea to rebind nil; you'll probably break everything! use (= nil ()) to fix the mess you probably made"

-----

1 point by aw 2021 days ago | link

Printing a warning is a good idea. Whether rebinding nil could be fixed with "(= nil ())" is an interesting question, you might (or might not, I haven't tried it) find that rebinding nil breaks Arc so badly that = no longer works... :-)

I see a potential for a contest here: how to permanently break Arc (with "permanently" meaning you can't recover by typing something at the REPL), in the most interesting way, using the fewest characters. (Non-interesting being, for example, going into an infinite loop so that you don't return to the REPL).

-----

1 point by Pauan 2021 days ago | link

Quite possibly. It should work in my interpreter, though. Actually, () and nil are two different values, but they're special-cased to be eq to each other. It was the only way I found to make one print as "nil" and the other print as "()" From an external point of view, though, they should seem the same.

Also, if = doesn't work, assign probably would:

  (assign nil '())
I just tested it in my interpreter:

  (assign nil 'foo) -> foo
  nil               -> foo
  'nil              -> nil
  ()                -> ()
  '()               -> nil
  (assign nil '())  -> nil
  (is nil ())       -> t
  
So yes, it should be possible to recover, even after overwriting nil (at least in my interpreter. I don't know about MzScheme)

-----

1 point by Pauan 2021 days ago | link

Exactly! nil too. :P

-----

2 points by shader 2021 days ago | link

Hmm... the issue with that is that you might start having to quote nil or t whenever you want to actually mean nil or t, instead of just typing them in normally.

I do wish there was an easier way to tell whether or not a value was provided as nil, or was left empty and defaults to nil. Maybe doing destructuring on rest args would help solve that problem in most cases?

-----

1 point by Pauan 2020 days ago | link

"I do wish there was an easier way to tell whether or not a value was provided as nil, or was left empty and defaults to nil."

I too have sometimes wished for that in JavaScript, but let me tell you a little story. I was writing a syntax highlighter, and got it working fine in Chrome and Firefox 3.5, but there was a bug in Firefox 3.0.

You see, I was using this bit of code here:

  output.push(text.slice(curr.index[1], next && next.index[0]));
If `next` doesn't exist, it will pass the value `undefined` to the `slice` method. In JS, if you don't pass an argument, it defaults to `undefined`, so this is supposed to behave like as if I hadn't passed in the argument at all.

But in Firefox 3.0, the slice method behaves differently depending on whether you pass it `undefined`, or don't pass it any arguments. So, I had to use this instead:

  if (!next) {
      output.push(text.slice(curr.index[1]));
  } else {
      output.push(text.slice(curr.index[1], next.index[0]));
  }
This was (thankfully) fixed in 3.5. The moral of the story: most of the time it doesn't matter whether the caller passed nil, or didn't pass anything. You can treat the two situations as the same.

Consider this hypothetical example in Arc:

  (your-func 5 (and x y z))
If x, y, or z are non-nil, it will be passed in as usual. On the other hand, if any of them are nil, it will be like as if you had used (your-func 5 nil).

By behaving differently when nil is passed in vs. not passing in an argument, you might cause the above example to break. Or perhaps it would work, but the behavior would be subtly different... introducing bugs.

By having different behavior depending on whether an argument is passed or not, you force callers to do this, instead:

  (iflet val (and x y z)
    (your-func 5 val)
    (your-func 5))
Note the redundancy. In fact, this is even more important in Arc (compared to JavaScript) because you can use any expression, such as (if), a macro call, etc.

So... let me ask: what situations do you really need to know whether the caller actually passed in nil, or didn't pass anything at all?

-----

1 point by rocketnia 2020 days ago | link

Great point. In fact, I don't check whether an optional argument was passed very often, and the times I do, I usually expect to regret it at some point, for exactly that reason. ^_^

-----

1 point by rocketnia 2021 days ago | link

"I do wish there was an easier way to tell whether or not a value was provided as nil"

I share this sentiment. One thing we could do is have a space of hidden-from-the-programmer variables which tell you whether other variables have been bound. They can be accessed using a macro:

  (= given-prefix* (string (uniq) "-given-"))
  (mac given (var)
    ; NOTE: I don't think this will work properly for nil, but nil is
    ; never a local variable name anyway.
    (sym:+ given-prefix* var))
The implementation of argument lists would need to be aware of 'given-prefix* and bind the prefixed variables at the same time as the regular ones.

---

"Maybe doing destructuring on rest args would help solve that problem in most cases?"

What do you mean by that?

-----

2 points by shader 2021 days ago | link

Well, if you use a rest arg for all optional values, and then use some form of destructuring bind on that list to extract your optional arguments, then you can tell whether or not they were passed in or merely defaulted to nil by just searching the arg list.

  (def test args
    (if (assoc 'c args)
          (pr "c was passed")
        (pr "c was not passed")))

-----

1 point by rocketnia 2021 days ago | link

I still don't follow. We can already manage the argument list manually, but in most of the suggestions here, we can only do it if we don't destructure it in the signature (unless we use more complicated kinds of destructuring).

  ; Current options:
  
  (def test args
    (let ((o c)) args
      (pr:if (len> args 0)
        "c was passed"
        "c was not passed")))
  
  (let missing list.nil  ; This is just something unique.
    (def test ((o c missing))
      (pr:if (is c missing)
        "c was not passed"
        "c was passed")))
  
  
  ; Some hypothetical options and non-options:
  
  (def test (& (c))
    (pr "no way to tell if c was passed"))
  
  (let missing list.nil
    (def test (& (c))
      (pr "still no way to tell if c was passed")))
  
  (def test (& args)
    (let ((o c)) args
      (pr:if (len> args 0)
        "c was passed"
        "c was not passed")))
  
  (def test (& (&both args (c)))  ; Destructure twice.
    (pr:if (len> args 0)
      "c was passed"
      "c was not passed"))
  
  (def test ((o c nil c-passed))
    (pr:if c-passed
      "c was passed"
      "c was not passed"))
  
  (def test ((o c))
    (pr:if given.c
      "c was passed"
      "c was not passed"))
  
  (def test (c)  ; Parameter lists are just destructuring.
    (pr:if given.c
      "c was passed"
      "c was not passed"))
  
  (def test (&both args (c))
    (pr:if (len> args 0)
      "c was passed"
      "c was not passed"))

-----

1 point by Pauan 2020 days ago | link

Brilliant! In fact, you could write a macro that would do that for you:

  (mac defreq (name args . body)
    `(w/uniq gen
       (def ,name ,(map (fn (x) `(o ,x gen)) args)
         ,@(map (fn (x) `(if (is ,x gen) (err:string "parameter " ',x " is required"))) args)
         ,@body)))

  (defreq foo (x y) (+ x y))
  (foo)     -> x is required
  (foo 1)   -> y is required
  (foo 1 2) -> 3
It probably breaks with rest arguments, but I think you could get those working too.

-----

1 point by Pauan 2020 days ago | link

Or this version, which is even better:

  (mac defreq (name vars . body)
    (if (isa vars 'cons)
          (let exp (len vars)
            `(def ,name args
               (let giv (len args)
                 (if (< giv ,exp)
                       (err:string "expected " ,exp " arguments (" giv " given)")
                     (apply (fn ,vars ,@body) args)))))
        `(def ,name ,vars ,@body)))


  (defreq foo (x y) (+ x y))
  (foo)     -> error: expected 2 arguments (0 given)
  (foo 1)   -> error: expected 2 arguments (1 given)
  (foo 1 2) -> 3
  
  (defreq foo args args)
  (foo)     -> ()
  (foo 1)   -> (1)
  (foo 1 2) -> (1 2)
  
It fails on functions that take required and rest args, though:

  (defreq foo (x y . args) (list x y args)) -> error
Err... right, you were talking about detecting if an argument was nil or not given... but I realized that the same technique could be used to write a version of def that implements required arguments even in a language where every argument is optional.

-----

1 point by Pauan 2021 days ago | link

Only if you actually rebind them. It's like using `quote` as a variable name: you can do it, but most people won't because that's silly. I just think it's nice to allow it, on the off chance it's actually useful. It just feels weird to arbitrarily say "you can't rebind nil and t" but allow rebinding of everything else.

-----

1 point by Pauan 2021 days ago | link

Using err:string is good, but why the unnecessary `and` check? Am I missing something?

-----

1 point by shader 2021 days ago | link

The and is required to make sure that a type was provided, otherwise it will always fail the type check. Also, if you leave n out of the and clause, it will still pass if the required type is sym. Maybe that should be fixed.

-----

1 point by Pauan 2021 days ago | link

Hm... yes, you're right. Odd, I remember it working fine when I tested it earlier. This should work correctly:

  (mac require (n (o t))
    `(if (no ,n)                  (err:string "parameter " ',n " is required")
         (and ,t (no:isa ,n ,t))  (err:string "parameter " ',n " must be of type " ,t)))

-----

2 points by Pauan 2023 days ago | link

Time to implement this in my Arc interpreter written in Python. I should be able to get (. (a b)) to work too.

Also, if (. (a b)) is equivalent to ((o a) (o b)), then how would you specify the value, like with ((o a 'x) (o b 'y))?

-----

2 points by Pauan 2022 days ago | link

It only took about 30-60 minutes to implement it. My Arc interpreter now supports both forms of optional parameters:

  (def foo (a b (o c) (o d) . e))  ; works
  (def foo (a b . (c d . e)))      ; works
  
I plan to support this style of optionals too:

  (def foo (a b ? o d . e))
  
  
This could all change, though. I haven't yet seen the Perfect Holy Grail for optional arguments. I kinda like Python's take on it, though:

  def foo(a, b, c=None, d=None, *e):
      pass
      
Which would be this, in Arc:

  (def foo (a b c=nil d=nil . e))
  
Which would expand to:

  (def foo (a b (= c nil) (= d nil) . e))

-----

1 point by Pauan 2019 days ago | link

Okay, scrap that. Here's my new plan:

Make all arguments optional, as per evanrmurphy's suggestion. I'll still support the (o foo) form for backwards compatibility, but I think it has so many problems that it really should be avoided.

This isn't final, but I figure the simplicity of not supporting required parameters will be a net gain most of the time. If you really need required parameters, it's possible to write macros that do the checks at run-time. I might even provide such a macro in arc.arc so you don't have to write it yourself.

Also, for telling Arc what the default is (it's normally nil), you use the (= foo 'bar) form, like so:

  (def foo ((o a 1) (o b 2))) ; pgArc
  (def foo ((= a 1) (= b 2))) ; PyArc
I also plan to support argument-ssyntax, which is basically ssyntax that is only expanded in the argument list. Then you can do this:

  (def foo (a=1 b=2))
This should, of course, be customizable within Arc.

-----

1 point by evanrmurphy 2023 days ago | link

The first wart I observe is that it provides no way to specify all the parameters as optional, e.g. the arc3.1 definition

  (def baz ((o a) (o b))
    ...)               
cannot be expressed.

Hmm... perhaps I like the scheme where parameters are always optional best. Its concision cannot be beat!

-----

1 point by akkartik 2023 days ago | link

Why, does this not work? (I still haven't installed clojure. JVM, yuck..)

  (def baz (& a b)
    ..)

-----

1 point by evanrmurphy 2023 days ago | link

That should work in Clojure, you're right. + But the analogous code in an arc-like would not work because (. (a b)) is not a valid list (since it has no car).

---

+ Technically, it would be:

  (defn baz [& [a b]]
    ..)

-----

1 point by akkartik 2022 days ago | link

There's a lot more wrong with it :) You can't have non-atoms after the dot either.

I was taking for granted that you have to change the reader, or replace dot with a different token.

-----

1 point by akkartik 2023 days ago | link

Yeah it's an interesting blind spot. I think it was the dot syntax - we 'knew' that you can't have anything after dot except an atom.

-----

1 point by akkartik 2023 days ago | link

Some more (informal, crappily formatted) notes, primarily from Rich Hickey's "Clojure for Lispers" talk: http://akkartik.name/blog/2011-03-12-04-30-47-soc

Perhaps the coolest thing there other than optionals-using-rest: to make all sequences lazy, and use into to realize them into specific types like vectors, maps, etc. That really seems like the best solution I have seen to make map generic (http://arclanguage.org/item?id=13566)

-----

1 point by evanrmurphy 2023 days ago | link

"You cannot be concerned about ephemeral consing any more. It's a real design misfeature to prioritize that."

I'd like to hear this elaborated upon.

-----