Arc Forumnew | comments | leaders | submit | cchooper's commentslogin
6 points by cchooper 6396 days ago | link | parent | on: logo

I thought it would be an implementation of LOGO in Arc :(

-----

3 points by cchooper 6396 days ago | link | parent | on: Is Hacker News code available?

The problem of hanging can be fixed by getting the latest version of Anarki stable from the git repository:

http://git.nex-3.com/arc-wiki.git

Details about getting and using stable are here:

http://arclanguage.org/item?id=3849

-----


I think the axiomatic approach is intended to stop this. Lisp suffered because the spec was complex and ambiguous. To port Arc, just port the axioms, run the "spec" et voilĂ , you have a conforming implementation.

It should be noted that pg intends to implement more of Arc in Arc, when he gets the time.

-----

6 points by pau 6395 days ago | link

In fact I have an implementation in SBCL half way (it basically can load arc.arc, but I haven't bothered with network/thread functions yet). To solve the call/cc problem I got to the point of doing a CPS transform, and since Arc has only 4 special forms (very few axioms, as you say) this was 'easy'...

-----

4 points by cchooper 6397 days ago | link | parent | on: Map et al arguments are backwards

In all the Arc code I've written so far, I've used map a lot. That's probably quite common for anyone programming in a language based on lists. I therefore propose that Arc have a special syntax that turns a normal function into one that maps over a list.

For example:

  ($car '((a 1) (b 2) (c 3))) == (map car '((a 1) (b 2) (c 3)))
Note that $car is an expression that returns a function, so you could pass it to other functions.

  (apply $car '(((a 1) (b 2) (c 3))))
Also, $$car would be a valid expression, as would $[car _] or $(fn (x) (car x)) and so on.

-----

2 points by Jekyll 6396 days ago | link

  (def $ (x . lists)
         (if lists
             (apply map x lists)
             (fn (lists) (apply map x lists)))) 
More or less does what you want, without special syntax. ($$car... would have to be written ($($ car)... though.

To me this looks like a specific case of wanting currying/partial application built in for functions of semi-fixed arity, rather than a need for new syntax.

-----

2 points by absz 6396 days ago | link

You can even be cleaner: $car is $.car (though this doesn't work for $.$.car). But I don't think this is really a specific case of partial application (though I see where you're coming from). While partial application would be ((map car) '((1 2) (3 4) (5 6))) and this would be ($car '((1 2) (3 4) (5 6))), the biggest win (for me) is not the partial application aspect, but the conciseness of having just one character, as opposed to (currently) ([map car _] '((1 2) (3 4) (5 6))) or ([apply map car __] '((1 2) (3 4) (5 6))), or even the partial application version. And even so, this strikes me as more similar to a request for explicit partial application, which is already implementable, than implicit partial application.

-----

4 points by cchooper 6395 days ago | link

Yes, it was the conciseness I was after. The syntax was inspired by K, where you can do this by putting a ' before a function name.

Of course, K often looks like line noise, but then K takes this technique to its logical conclusion and uses special syntax for everything. There's no need to go that far.

-----

1 point by nex3 6397 days ago | link

I dislike this idea, mostly because the syntax is pretty trivial anyway using the bracket currying suggested elsewhere. [map car] isn't much shorter than $car, and it's much more flexible and leaves $ (or whatever symbol might end up being used) open for some other meaning. It's also more explicit, although I'm not sure that's worth much.

-----

1 point by absz 6396 days ago | link

That's not necessarily true; as I pointed out further down (http://arclanguage.org/item?id=4704), you can get the effect you want. On the other hand, you do take a speed hit. I'm inclined towards supporting both, but swapping the args is very easy to write and may not belong in the core.

-----

2 points by tel 6397 days ago | link

pg said he is planning on opening up intrasymbol syntax and, presumably, presymbol syntax as well. Once that's there this is a trivial addition to play with.

-----

1 point by absz 6397 days ago | link

But when? That may be the single biggest change I want, actually--much of the rest is implementable within Arc, but this is not. Certainly, when that happens this is trivial, but it hasn't yet, and there's no inkling of when.

-----

4 points by tel 6397 days ago | link

Well, pg has at least suggested that he will add that functionality. This is the first I've seen of this presymbol operator.

If you can't wait, implement this or presymbol syntax into Anarki.

-----

1 point by absz 6397 days ago | link

I really like this idea, but I don't know that $ is the best choice. On the other hand, I can't think of something better... ^car? &car? At any rate, fantastic idea.

-----

1 point by cchooper 6398 days ago | link | parent | on: destructuring the quotation symbol

I think it's a neat idea (it really does bug me when I write really short macros that just delegate) but I agree with almkglor that mixing functions and macros isn't good. I'd prefer that defq just always returned a macro.

Don't want to be too critical though. It's a great idea and could definitely make a few programs shorter.

-----

4 points by drcode 6398 days ago | link

I admit that mixing macros and functions is risky business. A case could be made for having defq always return macros.

-----

2 points by tel 6397 days ago | link

Isn't the point of Arc to always enthusiastically jump into risky business?

-----

1 point by drcode 6397 days ago | link

my thoughts exactly.

-----

1 point by cchooper 6399 days ago | link | parent | on: Will Arc ever be as fast as CL?

Is Scheme slower than CL?

-----

6 points by raymyers 6399 days ago | link

Since Scheme and CL are both specifications, it would be more meaningful to ask "Is MzScheme slower than SBCL?", for instance.

-----

3 points by KirinDave 6398 days ago | link

And to expand on that, the answer is generally yes. MzScheme is a great version of scheme for its completeness, not its speed. However, several schemes are directly competitive SBCL and CMUCL. Arc could be ported to one of these without too much effort.

-----

3 points by sacado 6398 days ago | link

As for speed, Stalin Scheme is amazing for example. Not very complete and well documented for example. However, CL is full of "efficiency hacks". Scheme is full of purity, not always easy to implement efficiently. Optimizing CL is easier than Scheme, IMHO.

-----

2 points by sramsay 6398 days ago | link

Why do you think CL is easier to optimize that Scheme? This isn't a hostile question; I'm just curious. Intuitively, I feel like "purer" languages should be easier to optimize.

-----

2 points by sacado 6398 days ago | link

First, CL has in the standard many possibilities for making declarations reagrding optimization : for the functions you want, you can compile code, declare types (e. g. this var only holds fixnums), declare you want to optimize the speed and ignore type safety, etc. This way, you end up writing code the way you would write it in C. There is no such thing in the Scheme standard. Individual implementations could, of course, but as far as I know no one does.

Abother example is call/cc. This is a very interesting beast, only existing in Scheme. But it is hard to implement efficiently.

The last example I can think of is 'nil. Using nil as false and the empty list is very interesting in this regard : you can implement nil as the NULL pointer, which is also 0, the false boolean. Less manipulations to do on the bare metal. Distinguishing between #f and '(), on the contrary, implies making more tests at the lower levels.

There are other points I guess...

-----

3 points by almkglor 6398 days ago | link

re: call/cc - I think a bit of the lambda the ultimate series of papers eventually boils down to the realization that a machine language jump-to-subroutine is equivalent to a call/cc, and the target of the call/cc just has to access the return address on the stack as a function address.

-----

3 points by kens1 6397 days ago | link

I don't get it. There's the whole stack copying for call/cc, so call/cc is much more expensive.

(I read the "Lambda the ultimate GOTO" paper you referenced earlier; it's about goto vs structured programming, not call/cc. As an aside, it's very interesting to reflect on just how controversial structured programming was.)

-----

4 points by soegaard 6397 days ago | link

Implementing call/cc efficiently has been well-reasearched in the Scheme community. For a very well-written account of a non-stack-copying implementation see

R. Kent Dybvig. "Three Implementation Models for Scheme". PhD. Thesis. http://www.cs.indiana.edu/~dyb/papers/3imp.pdf

Then continue at ReadScheme at "Compiler Technology/Implementation Techniques and Optimization" to see further developments (Look especially for Clinger's papers).

http://library.readscheme.org/page8.html

-----

2 points by almkglor 6397 days ago | link

Who said anything about copying stack? For that matter - who said local variables should be kept in the stack anyway?

-----

1 point by kens1 6397 days ago | link

In MIT Scheme, the stack gets copied; at least that's what I was told last week. Whether or not you use a stack, the state will need to be copied.

-----

1 point by almkglor 6397 days ago | link

In a function call, the state (the current computation being done) is saved anyway, and therefore "copied" if that is your preferred term. So ideally, call/cc should have the same overhead as an ordinary function call; the only difference is that in call/cc the continuation state is the value given to the function, while in a function call it's just one of the values given to the function.

Note however that much of the theoretical analyses of call/cc make a basic assumption of a "spaghetti stack", which would mean that partially unwound stacks would be saved implicitly as long as any continuation exists which refers to that stack, and all stacks themselves are subject to garbage collection. Most machines don't actually have a spaghetti stack and can't make a spaghetti stack anyway ^^. That said a spaghetti stack could be implemented as a simple list, with push == cons and pop = cdr.

Alternatively store the local variables on a garbage-collected heap, and include a reference to the local variables with the continuation/return address (you'll probably need to save the pointer-to-local-variables anyway, since the target function is likely to use that pointer for its own locals). Again, no additional overhead over plain function calls, except perhaps to restructure the return address and the pointer-to-local-variables.

Don't know about MIT Scheme, but if I were to implement call/cc on stock hardware and compiling down to machine language that's what I'd do ^^

-----

3 points by kens1 6397 days ago | link

I'm still totally not understanding your claim that call/cc should have the same overhead as an ordinary function call.

I read the Clinger "Implementation Strategies for Continuations" paper and they found call/cc about 10 times slower than function calls on the tak/ctak tests. I tried those tests on PLT Scheme and the overhead I saw is even worse: .7 seconds with function calls vs 51.8 seconds with continuations on (tak 24 16 8).

Clinger says about the stack strategy: "When a continuation is captured, however, a copy of the entire stack is made and stored in the heap. ... Variations on the stack strategy are used by most implementations of Scheme and Smalltalk-80."

-----

3 points by almkglor 6397 days ago | link

Components of function call: (1) put arguments somewhere (2) put return address somewhere (3) jump to function.

Components of call/cc: (1) put return address as argument somewhere (2) put return address somewhere (3) jump to function.

That said, continuations generally assume that "put X somewhere" means somewhere != stack. Yes, even return addresses are not intended to be on the stack when using continuations. The main problem is when compiling down to C, which always assumes that somewhere == stack. If you're compiling down to machine language where you have access to everything, then you can just ignore the stack, but not in C, which inherently expects the stack.

-----

3 points by sramsay 6398 days ago | link

Ah, I get it. I suppose typing is one of the big issues affecting speed. If the language standard insists on dynamic typing, there might be no way to get certain kinds of optimizations.

And yeah, call/cc is probably always going to be a bear. But man is it cool. :)

I suppose this goes against what a few of us (including me) were saying above -- that the language and the speed are really separate issues. Or maybe it's more coherent to say that language standards (as opposed to "languages" generally understood) can have a profound effect on speed. If they don't give implementors a lot of choice, they can box people into certain corners.

It's interesting that Scheme actually mandates optimization in at least one case (tail recursion). I don't know how many language standards make those kinds of demands, but I suspect there aren't many.

-----

2 points by sacado 6397 days ago | link

Tail recursion is interesting as it is not especially an optimization for speed but as a way to make programmers rely primarily on functional programming : if you don't have it, functional programming is rapidly a dead-end as you can make the stack explode really fast. As a bonus, it is faster :)

-----

1 point by cchooper 6399 days ago | link | parent | on: Lists as functions on symbol arguments

Alternatively, it could be used to do an assoc lookup, which would make alists more like hashes.

  (= x '((a 1) (b 2) (c 3)))

  x!b
  => 2
Note that it returns the value not the pair, as with hashes. It's not as powerful as assoc but more convenient in most cases.

-----

1 point by bogomipz 6399 days ago | link

And a third alternative is to use dotted pairs for the associations, but my point was that by treating a plain list as alternating keys and values, it plays nice with rest arguments in functions.

Generally, a list may be interpreted in different ways in different situations, and a common complaint about lisp is that you can't tell if a cons cell is supposed to be the starting point of a tree, an assoc list, a sequence, or something else. I think the way to tackle this in Arc should be to make better use of annotations.

A rest argument will always be a plain list without a tag. That's the reason for the suggested interpretation of kvp!b.

-----

1 point by nlavine 6398 days ago | link

Why are we assuming that keyword arguments must be passed as flat lists of keywords and values?

  (bar 1 2 ('foo 3) ('baz 4))
I agree the flat way is cleaner, but this is certainly a possibility too.

-----

1 point by cooldude127 6399 days ago | link

well, you could always just use pairs to turn the interleaved list into an alist.

-----

1 point by bogomipz 6399 days ago | link

Yes, with the overhead of the operation plus a let form.

My suggestion only really applies if pg decides against adding keyword arguments to Arc.

-----

2 points by cchooper 6399 days ago | link

Exactly. It's basically a roundabout way of adding keywords into the language. A better idea would be to just add them, and then list-functional notation could be used for something more generally useful.

-----


Combining what gaah (Jesin) said with other things you've pointed out, the final solution would work something like this:

1. Write mock versions of all the Arc primitives that are dangerous.

2. Write a function that calls macex on an expression and then scans it for all instances of (set <symbol> ...).

3. Write a macro that takes an expression or a file, and wraps the whole thing in a with form that locally defines/shadows all the mock functions and the symbols detected with the above function.

4. Create a mock version of eval that wraps its argument in the above macro. This should obviously be shadowed in the macro too.

These are probably all the tools you need.

-----

1 point by lacker 6401 days ago | link

Interesting. I think this roughly makes sense.

There are still some parts that are stumping me. It seems like I need to either (a) mock annotate to use a different mechanism, or (b) prevent the code from annotating pre-existing variables. The same goes for accessing sig.

Also, I'm not sure how to handle scar/scdr/sref. It don't think places are first-class, so I can't have a table of untouchable places, unless I start working in mzscheme. (Which I would rather avoid.)

-----

2 points by kens1 6401 days ago | link

If you want the gory details of places, see my recent document: http://arcfn.com/doc/setforms.html

-----

1 point by lacker 6401 days ago | link

Wow... that is gory. :-/

It seems like the most plausible way to keep scar/scdr/sref from mutating variables outside the no-side-effects scope is to keep a table of the variables they are allowed to operate on, which is basically anything the user created with cons or table inside the no-side-effects scope. This should invalidate code like

(= a (table))

(no-side-effects (= b a) (= (b "key") "value"))

Is there any more natural way to track this than keeping a table keyed by symbol?

-----

3 points by eds 6401 days ago | link

This is probably a horribly inefficient way to implement this... but couldn't you just copy all the variables before executing the body?

Assuming

  (= a (table 'foo 'bar))
then a line like this

  (no-side-effects (= a!foo 'baz) a)
might be converted to something roughly like this

  (let a (copy a)
    (= a!foo 'baz)
    a)
a is unchanged after evaluation because the let made a deep copy that overshadowed the global value of a.

You would still have to think about constructs like def and mac, but at least this deals with shared structure of lists and tables.

Maybe something like http://arclanguage.org/item?id=3572 ?

-----

6 points by cchooper 6402 days ago | link | parent | on: Arc challenge: 8 Queens Problem

Here's an implementation of the n queens algorithm in the wikipedia article:

  (def rot ((x . y)) (join y `(,x)))

  (def nqueens (n)
    (withs (m (mod n 12) r (range 1 n) od (keep odd r) cod (cddr od) s '(1 3) cods (join cod s) ev (keep even r))
      (join (if (pos m '(3 9)) (rot ev) ev)
            (case m
              8 (apply join (map rev (pair od)))
              2 (join (rev s) (rot cod))
              3 cods
              9 cods
              od))))
Arc makes it very nice to implement, although it still bugs me that you can't use join to add a single item to the end of a list (which I've wanted to do a few times already). My preferred semantics:

  (join 1 '(2 3 4) 5 '(6 7 8) 9)
  => (1 2 3 4 5 6 7 8 9)
Edit: made it slightly shorter.

-----

3 points by map 6401 days ago | link

To simulate

  (join 1 '(2 3 4) 5 '(6 7 8) 9)
one could do

  (flat:list 1 '(2 3 4) 5 '(6 7 8) 9)
Of course, you're out of luck if you have nested lists.

-----

1 point by lojic 6401 days ago | link

When I run this via (nqueens 8), I only get one output:

  arc> (nqueens 8)
  (2 4 6 8 3 1 7 5)  
  arc>
There should be 92 solutions displayed.

-----

1 point by cchooper 6401 days ago | link

This algorithm only finds one correct solution for each n.

-----

1 point by map 6401 days ago | link

The wikipedia algorithm only generates one solution.

-----

1 point by lojic 6401 days ago | link

The challenge is to produce all 92 solutions as the Ruby code does in the OP. I linked to wikipedia for the description of the problem, not the algorithm.

I also showed the ellided output for clarification.

-----

1 point by cchooper 6401 days ago | link

I was doing the n queens challenge on Wikipedia because someone had already posted a solution to your challenge.

-----

1 point by map 6402 days ago | link

Ruby version of the wikipedia algorithm.

  class Array
    def rot
      push( shift )
    end
  end

  nqueens = proc{|n|
    odds, evens = (1..n).partition{|x| x % 2 > 0 }
    case n % 12
      when 2
        odds = [3,1] + odds[3..-1] + [5]
      when 3, 9
        odds.rot.rot
        evens.rot
      when 8
        d = -2
        odds.map!{|x| d *= -1; x + d}
    end
    p evens + odds
  }

  nqueens[8]

-----

2 points by map 6401 days ago | link

Shorter:

  class Array
    def rot
      push( shift )
    end
  end

  proc{|n| p ((1..n).partition{|x|x%2>0}.inject{|o,e|
    e + case n % 12
      when 2
        [3,1]+o[3..-1]+[5]
      when 3,9
        o.rot.rot;e.rot;o
      when 8
        d=-2;o.map{|x| x + d*=-1} end})}[ 8 ]

-----

1 point by cchooper 6401 days ago | link

Getting shorter:

  (def rot ((x . y)) (join y `(,x)))

  (def nqueens (n)
    (withs (m (mod n 12) r (range 1 n) od (keep odd r) cod (cddr od) s '(1 3) ev (keep even r))
           (if (join (pos m '(3 9)) (rot ev) (join cod s))
               (join ev (case m 8 (apply join (map rev (pair od)))
                                2 (join (rev s) (rot cod))
                                od)))))
The things Arc is missing from Ruby are partition and testing against multiple objects in a case. With those it would become:

  (def rot ((x . y)) (join y `(,x)))

  (def nqueens (n)
    (withs ((od ev) (part odd (range 1 n)) cod (cddr od) s '(1 3))
           (join ev (case (mod n 12) 8    (apply join (map rev (pair od)))
                                     2    (join (rev s) (rot cod))
                                     3, 9 (do (zap (rot ev)) (join cod s))
                                     od))))

-----

1 point by map 6401 days ago | link

Oops. Both of my Ruby programs above lack a default for the case statement. E.g., the shorter program needs after the last "when":

      else
       o

-----

1 point by map 6401 days ago | link

  (def rot ((x . y)) (join y `(,x)))


  (def nqueens (n)
    (withs (r (range 1 n) o (keep odd r) e (keep even r) m (mod n 12))
      (if (pos m '(3 9)) (= m -1))
      (join (if (is m -1) (rot e) e)
        (case m
          2 (join '(3 1) (cut o 3 -1) '(5))
          8 (flat:map rev (pair o))
          -1 (rot:rot o)
          o))))

-----

2 points by cchooper 6401 days ago | link

Instead of (= m -1) you could use (nil! m) and then test it with (no m). That's 2 fewer atoms!

-----

2 points by kens1 6401 days ago | link

Is nil! an Anarki thing? (wipe m) is the Arc expression to set m to nil. (And (assert m) sets m to t. Assert tops my list of confusingly named functions.)

-----

2 points by nex3 6401 days ago | link

No, nil! was just the old name for wipe.

-----

1 point by map 6401 days ago | link

Good idea. But "(nil! m)" gives me an error. After making the other changes you suggested, it occurred to me to combine the two "if" clauses into one.

  (def rot ((x . y)) (join y `(,x)))
  (def nqueens (n)
    (withs (r (range 1 n) o (keep odd r) e (keep even r) m (mod n 12))
      (join (if (pos m '(3 9))(or wipe.m rot.e) e)
        (case m
          2 (flat:list 3 1 (cut o 3 -1) 5)
          8 (flat:map rev pair.o)
          nil (rot:rot o)
          o))))

Edit: used "wipe" as suggested below.

Edit: using "flat:list" for case 2.

Edit: replaced (wipe m) with wipe.m, etc.

-----


Now that I understand it, I love it.

-----

2 points by kennytilton 6403 days ago | link

I should add that dsb and thus defun support optional and keyword args at the same time only because it is possible; I cannot imagine it ever being sensible. :)

-----

2 points by are 6402 days ago | link

Very nice work on supporting both opt and key args.

Although if you had something like this:

(defun fn (a &o (b 'b) (c 'c) &k (d 'd))

with a usage like this:

(fn 1 'd 'e)

... how would you know whether:

1) 'd is the value of the first opt arg and 'e is the value of the second (the key arg unsupplied)

or

2) 'd is the key for the key arg, and 'e is its supplied value (the 2 opt args unsupplied)

?

Maybe I'm missing something here, but it seems to me that unless you have special syntax for keywords, you will get into trouble.

And if you have to introduce special syntax for keys anyway, it is just as well to make every single argument keyable on its symbol (even vanilla ones), and just worry about combining &o and &rest (which should then be doable).

-----

3 points by kennytilton 6402 days ago | link

"with a usage like this: (fn 1 'd 'e) how would you know..."

The interpretation is that d and e are the two optional args, so any caller wanting to supply a keyword arg has to supply the optionals. Recall that I said it was possible, not sensible. :) But in tool design I think we should let users hang themselves rather than guess (perhaps wrongly) that no one would ever come up with a good use for such a thing.

A second, lesser motivation is that CL works that way.

-----

2 points by kennytilton 6402 days ago | link

The gang on comp.lang.lisp reminded me of (in effect):

  (def read-from-string (s &o eof-error-p eof-value 
                           &k start end preserve-whitespace)
     ...)
Which does make me think the guess about keywords being added as an afterthought might be spot on.

-----

2 points by kennytilton 6402 days ago | link

c.l.lisp just offered a much better observation: the optional args above are standard for the various "read" functions, and the start and end keywords are standard for string functions. Read-from-string then is inheriting consistently from both families.

-----

2 points by eds 6402 days ago | link

Even CL's special keyword syntax doesn't save you from optional and keyword confusion:

  [1]> (defun test (a &optional (b 'b) (c 'c) &key (d 'd))
         (list a b c d))
  TEST
  [2]> (test 1 :d :e)
  (1 :D :E D)
Optional parameters always bind first in CL, and I believe dsb is written to mimic that behavior.

Having all parameters be keyword arguments as well might be interesting, but it wouldn't avoid optional/keyword confusion.

-----

1 point by are 6401 days ago | link

> Having all parameters be keyword arguments as well might be interesting, but it wouldn't avoid optional/keyword confusion.

Why not?

Let's say you have a function with 3 standard args followed by 2 opt args. So you have 5 args, all keyable on the symbol you give them in the def.

Let's further say that in a call to this function, I key the middle standard arg (#2 in the def) plus the first opt arg (#4 in the def) and also, I omit the second opt arg (#5 in the def). So, I'm supplying two standard args apart from the two args I'm keying. Then the function call parser would know, after identifying the 2 keyed args and matching them to positions #2 and #4 in the def, that the first non-key arg supplied corresponds to position #1 in the def, the second non-key arg supplied corresponds to position #3 in the def, and that an arg for position #5 in the def is missing, leading to the usage of the default value for this opt arg.

This would even work when you want to raise an error for a non-supplied, non-opt arg.

Wouldn't this work quite intuitively (keying an arg when calling a function "lifts it out" of the normal "vanillas then optionals" argument sequence, shortening that sequence, put keeping a well-defined order for it)? (You would need special syntax for keys in this proposal. My suggestion is a colon appended to the arg symbol, rather than prepended, like in CL.)

Can someone give a counterexample if they think this somehow wouldn't work?

&rest args are left as an exercise for the reader :-)

-----

2 points by almkglor 6402 days ago | link

It could be, if you had an old function that was using optional arguments, and then eventually had to add even more arguments, which you finally decide to make keyworded; without breaking existing code, you can support both optional and keyword args.

What I would like to see is optional, keyword, and rest arguments. Imagine something like this:

  (def foo (k v)
    'type (k int)
    (+ k v))

-----

3 points by kennytilton 6402 days ago | link

The syntax to add rest args, if it followed the CL example, would be:

  (dsb (r1 &o o1 &r rest &k k1) data ....)
If data was (1 2 'k1 3) then most params would be bound as expected and then rest would be bound to (k1 3).

But now the data (1 2 'k1 3 4) causes an error "Key list is not even", ie, once you say &k you undertake certain obligations as the caller. Even if you even up the list:

   (1 2 'k1 2 3 5)
...you get an error "3 is an invalid keyword", because 3 appears in a keyword position. This can be avoided by announcing your intention to have undeclared keywords:

   (a b &rest rest &key k1 &allow-other-keys)
That of course is CL, and it kinda makes my day that if I were crazy enough to extend dsb in Arc I would end up with:

   (a b &r rest &k k1 &aok)

-----

1 point by kennytilton 6401 days ago | link

"...if I were crazy enough to extend dsb..."

Was there ever any doubt? :)

Still requiring extensions from earlier posts:

  (def dsb-params-parse (params)
    (withs (reqs nil key? nil opt? nil keys nil opts nil
          rest (mem '&r params)
          rest-var (cadr rest)
          aok? (find '&aok cddr.rest)
           resting? nil
           no-mas nil)
    (each p params
      (if no-mas (assert nil "No params &aok, OK?" ',params)
        (is p '&o) (do (assert ~opt? "Duplicate &o:" ',params)
                       (assert ~key? "&k cannot precede &o:" ',params)
                     (= opt? t))
        (is p '&k) (do (assert ~key? "Duplicate &k:" ',params)
                       (= key? t))
        (is p '&r) (= resting? t)
        (is p '&aok) (= no-mas t)
        key? (push-end p keys)
        (and opt? (no resting?)) (push-end p opts)
        (no resting?) (do (assert (~acons p) "Reqd parameters need not be defaulted:" p)
                           (push-end p reqs))))
    (prt 're-obj!!!!! reqs opts rest-var keys aok?)
    (obj reqs reqs opts opts rst rest-var keys keys aok? aok?)))
And man was I happy to have the above as a breakout from the macro itself:

  (mac dsb (params data . body)
  (w/uniq (tree kvs valid-keys aok?)
    `(withs (,tree ,data
              ,@(let plist (dsb-params-parse params)
                  (prn `(reqs ,plist!reqs))
                  (prn `(rst ,plist!rst))
                  (prn `(keys ,plist!keys))
                  (prn `(&aok ,plist!aok?))
                  (with (n -1)
                    (+ (mappend [list _ `(nth ,(++ n) ,tree)] plist!reqs)
                      (mappend [list (carif _) `(if (< ,(++ n) (len ,tree))
                                                    (nth ,n ,tree)
                                                  ,(cadrif _))] plist!opts)
                      `(,plist!rst (nthcdr ,(++ n) ,tree))
                      `(,valid-keys ',plist!keys)
                      `(,aok? ',plist!aok?)
                      `(,kvs (do (prt 'foing ,valid-keys)
                                 (when (and ,plist!rst ,valid-keys)
                                   (assert (even (len ,plist!rst)) "Keyword list not even" ,plist!rst)
                                   (let ,kvs (pair ,plist!rst)
                                     (prt 'vetting ,valid-keys 'againt ,kvs)
                                     (unless ,aok?
                                       (assert (all [find (car _) ,valid-keys] ,kvs)
                                         "Invalid key in" (map car ,kvs)))
                                     (prt 'kvs!!!!! ,kvs)
                                     ,kvs))))
                      (mappend [list (carif _)
                                 `(do (prt 'kvs!!! ',(carif _) ,kvs)
                                      (aif (assoc ',(carif _) ,kvs)
                                        cadr.it
                                        ,(cadrif _)))] plist!keys)))))
       ,@body)))
What is missing (largely) is graceful handling of invalid use.

-----

1 point by almkglor 6401 days ago | link

> What is missing (largely) is graceful handling of invalid use.

(err:tostring:write ...) works fine for me for reporting errors and aborting

-----

2 points by kennytilton 6402 days ago | link

Ok, and then going forward only the new code has the burden of supplying optionals... hmmm, refactoring at 7am with the demo to the CEO scheduled for 9am?...

Lock and load! Add the keywords!! :)

-----

More