Arc Forumnew | comments | leaders | submit | rocketnia's commentslogin
2 points by rocketnia 5236 days ago | link | parent | on: The GNU extension language

"Delimited continuations can transfer control through functional combinators while preserving referential transparency."

I was going to try to describe delimited continuations, but I don't know as much as Wikipedia does: http://en.wikipedia.org/wiki/Delimited_continuation

I think the point about referential transparency is that when you call a delimited continuation, unlike an undelimited continuation, it will actually return. Delimited continuations can be pure functions, while undelimited continuations always have the side effect of ditching the calling code.

I think people have implemented delimited continuations in terms of undelimited continuations, but if we consider delimited continuations to be innate to a language, that probably has static analysis advantages.

---

"Macros compose with modules to preserve lexical scoping."

I don't think that's even true. XD My experience with macros and modules is that, in order to preserve lexical scoping, I frequently have to think about the ramifications of macros on modules and vice versa. To me, they're not features that "compose," they're features that are really tightly coupled.

---

"Dynamic bindings behave sanely when continuations are unwound and reinstated."

IMO, that's just what it means to be a dynamic binding. Without continuations, you don't need dynamic bindings, because their "temporarily set" behavior can be implemented in terms of regular old mutable boxes.

With continuations, I think it helps to see the program in the shape of a stack: Usually the program merely grows and shrinks, but when there are (undelimited) continuations in the mix, there are multiple stacks branching from each other that can grow and shrink independently, with only one being active at any given time. A dynamic binding is a special box that lets you give it a value only seen by the part of the program that emerges from the tip of this branch.

With a mutable box, you can remember the old value, insert the new value, run some code, and restore the old value. But at least in Arc, inserting the new value will happen "on the heap" rather than in a way that's local to the current continuation. Other continuations will observe that change, and often that isn't what we want.

Now, if we have 'dynamic-wind, we don't really need dynamic bindings again, 'cause we can maintain the value of a mutable box as we leave one program branch and enter another. But Racket's parameters still have an edge over this technique, because the body of 'parameterize is in tail position.

On another note, continuations aren't the only way we might consider the program's dynamic behavior to branch. So are threads. But thread-local boxes are usually called just that.

-----

1 point by akkartik 5236 days ago | link

Welcome back, rocketnia! I missed you ^_^

I had a sense of what delimited continuations were; I was looking for exactly your explanation.

And the imagery of continuations as trees that grow at the leaves is really powerful.

I don't follow the final two paragraphs, but I will reflect on them and read more about dynamic-wind.

-----

1 point by rocketnia 5236 days ago | link

"Welcome back, rocketnia! I missed you ^_^ "

Sorry for leaving you hanging. XD I've been here the whole time, but I've been a bit more of a silent lurker than usual. I tend to check Arc Forum on my phone, at times when I'm already a bit tired and not patient enough to search out all the exact quotes and links I'd want to use in a reply. Thumb-typing is not my obstacle; dealing with poor interfaces for tabbed browsing is. :-p

---

"I don't follow the final two paragraphs, but I will reflect on them and read more about dynamic-wind."

I'll elaborate--er, rant about those paragraphs anyway. >.>

---

Me: "Now, if we have 'dynamic-wind, we don't really need dynamic bindings again, 'cause we can maintain the value of a mutable box as we leave one program branch and enter another. But Racket's parameters still have an edge over this technique, because the body of 'parameterize is in tail position."

In Java (or your favorite Java-like language :-p ), when an exception is thrown, the program races to unwind the stack, making a stop at each "catch" or "finally" to see if there's something else it needs to take care of along the way, and to see if it should stop unwinding.

In Scheme, when you call a continuation, the program unwinds itself down one stack branch and winds back up onto another, making a stop at each "dynamic-wind" to do the same kinds of duties. A dynamic-wind form has a before part and an after part. (It might as well be two separate forms.) Arc exposes the after part by way of 'protect and 'after, but it leaves out the before part.

The continuation-tree imagery is particularly important with 'dynamic-wind. During a continuation jump, we don't just do every after parts in the stack, swap in the new stack, and do all the before parts. We only wind back just as far as the point where the continuations meet. (But so many languages with continuations neglect to even provide 'dynamic-wind (cough Arc, Ruby cough) that I wouldn't be surprised to see Scheme implementations that do provide it but accidentally give it nonstandard semantics.)

---

Me: "On another note, continuations aren't the only way we might consider the program's dynamic behavior to branch. So are threads. But thread-local boxes are usually called just that."

As far as thread-local boxes go, I like to think of a multithreaded program as a tree where the initial thread is the root, and each thread it spawns is a branch coming off of it. In different branches of the tree I might want different configuration settings to hold, in a similar way to what I'd achieve with dynamic boxes. In Java this can be achieved with InheritableThreadLocal, and in Racket it can be achieved by constructing a thread cell with a truthy value for its "preserved" parameter.

I don't use threads much. The people who do apparently prefer to manage thread-local state on an individual thread-by-thread basis, rather than putting them in a scope tree like this. In Java, ThreadLocal is easier to type than InheritableThreadLocal, and in Racket, (make-thread-cell null 'preserved) is easier to type than (make-thread-cell null). Maybe that makes sense: A thread isn't always merely a "part" of its creator's computation the way a stack frame is; threads can be passed around and collected into all kinds of spaghetti-like dependency combinations. Maybe that's what people tend to do with them. :-p

-----

1 point by akkartik 5236 days ago | link

And what does parameterize and being in tail position have to do with it?

-----

1 point by rocketnia 5236 days ago | link

Oh, right.

The body of a dynamic-wind isn't in tail position, because just after it determines a return value (or other exit), it runs some extra code (the after part).

The Racket documentation specifically says that the last expression in a 'parameterize form is in tail position, so they must use some magic or other. What I suspect is that for every stack frame, Racket keeps a table of the parameterizations made by that stack frame. Any given parameterize form doesn't need to introduce a new stack frame; it can just mutate the most recent stack frame's association data structure, overwriting any previous value it had been binding to that parameter. Technically it's storing something on the stack this way, but it's only storing a maximum of one thing per parameter per frame.

In fact, they vaguely spell this out if you look in the right place in the Racket docs (http://docs.racket-lang.org/reference/eval-model.html#(part....): "Each frame is conceptually annotated with a set of continuation marks . A mark consists of a key and its value[...] For example, marks can be used [...] to implement dynamic scope."

-----

1 point by rocketnia 5236 days ago | link | parent | on: How to get Racket to run arc?

Since aw started the wiki, there's always been a link to http://www.arclanguage.org/item?id=12397 on the front page. ^_^

Maybe there's a better way to organize that page...?

-----

3 points by rocketnia 5236 days ago | link | parent | on: Try Arc ported to WordPress

If the REPL session takes any input, including by measuring timings or generating random numbers, it won't be deterministically recreated by a list of commands. And what if I want a fresh REPL?

When I go to Try Arc, I imagine there's a single REPL per window/tab, which goes away as soon as I leave the page. So I think it would be cool to be able to copy out transcripts (without spurious hyphens showing up :-p ) and load/paste Arc libraries, but that's all the power I hope for.

If you're looking for more of a server-side challenge (and more of a productivity site rather than a tutorial :-p ), it would be awesome to have a user account system that lets me paste a bunch of Arc utilities and load them automatically every time I start a REPL.

-----

4 points by akkartik 5236 days ago | link

Nah, don't get into the business of managing user logins and passwords if you can possibly avoid it.

An alternative is a session system. Menu commands: "save this session", "past sessions". Give each session a URL so it can be bookmarked/shared.

-----

3 points by rocketnia 5236 days ago | link

Works for me. ^_^

I'm still a bit suspicious about abstraction leaks in the concept of "save this session." I'd prefer to think in terms of a library pastebin, where I may choose to turn my REPL session into a library but I'm not misled into thinking it'll preserve more about my session than the raw code I entered.

-----

2 points by evanrmurphy 5236 days ago | link

For what it's worth, managing user logins and passwords is a very easy business with WordPress! The challenge would be figuring out how to interface those WordPress user accounts with REPL functionality.

-----

2 points by thaddeus 5236 days ago | link

> And what if I want a fresh REPL?

If implemented like HN x-id's you could just refresh or reload the page in order to get a new session. And like HN one could just expire a stored session after x minutes without being used.

Note though: Originally when I posted the session-id idea I was thinking tryarc was only one session with many threads, where each session-id would really just point to its own namespace. Had this been the case it would just be the functions and variables being stored in memory. And then threads/session-ids/namespaces (whatever) could be spun up or down and managed in a myriad of ways.

-----

2 points by rocketnia 5236 days ago | link

Me: "And what if I want a fresh REPL?"

You: "If implemented like HN x-id's you could just refresh or reload the page in order to get a new session."

That's the behavior I expect, but then I also expect to get a new session if I navigate somewhere and click the back button. If the back button case does not give me a new REPL, I don't see why refreshing the page would.

-----


I expect that to get 'car.seq unevaluated, as you say. Those two simplified examples are both expected behavior, IMO, and so your problem is that ~f isn't acting like (fn (_) ~f._).

-

I don't know if this is on-topic, but here's your definition of 'complement:

  def complement($f)
    fn '$args
      eval `(no:,$f ,@$args)
Wouldn't it be easier (and possibly more hygienic) to do it this way?

  def complement(f)
    compose no f
-

"Is there a better implementation for compose ? Or is my eval broken?"

I don't know your scoping rules well enough to say. ^^; Here's your 'compose:

  def compose($f $g)
    fn '$args
      eval `(,$f (,$g ,@$args))
What environment does 'eval evaluate things in? What expression does it evaluate, exactly (including anything to do with implicit gensyms)?

-----

1 point by akkartik 5250 days ago | link

Ah, thanks for the simplified complement :/ Yeah my tests made no sense.

"I don't know your scoping rules well enough to say. ^^"

:) I just meant that if compose seems right then eval must be broken.

"What environment does 'eval evaluate things in? What expression does it evaluate, exactly?"

I'm not sure how to answer what expression it evaluates, but you're right, the problem's the non-standard scopes. The anonymous macro created by compose and bound to f inside rem gets as its arg just car.seq, so it seems to be having seq bound to the same variable on every recursive call. That's the bug; now to go track down why it is happening.

-----

2 points by akkartik 5247 days ago | link

I figured it out. I was being very lax in ordering my scopes.

The data structure of traditional lexical environments consists of a table of bindings, and a pointer to the next outer binding:

  A -> B -> C -> D
Here A is the innermost scope that gets checked first. The sequence of scopes checked is ABCD.

Wart's new scoping rules are for macros: we provide access to the caller's scope.

  A -> B -> C -> D
   \
    E -> F -> G -> H
Each 'level' of scopes is a traditional lexical environment. Since we want to only use caller scopes for bindings we can't find in the lexical environment, we traverse the lexical environment before 'dropping down a level to the caller's environment. Thus the order of lookups is ABCDEFGH.

This was where things stood until I ran into keep. The problem with keep was that the tree structure was more complex. Consider this slightly more complex scope tree:

  A -> B -> C -> D
   \         \
    E -> F    G -> H
It's clear that ABCD comes before EFGH. But what order should the caller scopes be traversed in? It turns out you want to unwind the stack first close to the callee: ABCDEFGH, which corresponds to a breadth-first traversal. However, I had lazily done the simplest possible thing, a depth-first search. ABCDGHEF. Wrong.

---

This took forever to debug because I struggled to find a nice way to visualize scopes. One drawback of everything being an f-expr is that I quickly run into complex trees that are impossible to visualize (http://github.com/akkartik/wart/commit/3932165bbf until http://github.com/akkartik/wart/commit/9ffcdec864). Eventually I came up with the idea of visualizing paths instead (http://github.com/akkartik/wart/commit/b234594cfa). I came up with a helper that takes a sym and enumerates in order all the paths that find bindings to it in the current environment. Once I did that it became easy to see that 10 should come before 0010 (0 is a horizontal edge, 1 is a diagonal edge)

-----

1 point by akkartik 5244 days ago | link

This seems relevant: "With macros, you have hygiene issues because you work with quoted identifiers, that somehow have to refer back to their original bindings. With fexprs, you have the luxury of using live values instead of identifiers referring to values, thereby achieving hygiene simply through good old lexical scope, already built-in to the language anyway." http://lambda-the-ultimate.org/node/4345#comment-66868

-----


"the following bug"

Hey, that's not a bug, that's just metafns. :-p One of my mini-goals writing Penknife was to make metafns more consistent and user-definable, more like macros than special forms. Every syntax would return a "parse fork", which could expand in a few different ways based on being in functional position (metafns and macros), being in assignment position (setforms), etc. The infrastructure was there, and I used it to define ":", but I don't think I ever got around to making an in-language syntax for conveniently defining metafns.

Even if there were a convenient way to define metafns in Penknife, 'compose would still be a complex case: Given ((compose a b) c), we have to decide what data structure 'a receives as input. For maximum flexibility in Penknife, my choice was to define an all new data structure just for this purpose and extend the compiler to deal with it properly. On the downside, that's not a very brief way to go about it. XD

Anyway, with fexprs instead of a compilation phasse, metafns are streamlined already, since--as you've demonstrated--things can return fexprs.

I don't really know the Wart semantics, but here's my Kernel-like-pseudocode take on 'compose (if I choose not to make a separate expression type for the occasion):

  ; Given ((compose a b c) d e f), we evaluate
  ; ('<value of a> ('<value of b> ('<value of c> d e f))), using the
  ; binding of 'quote captured at compose's definition site (i.e., right
  ; here).
  (def compose ops
    (unless ops (err "Not enough operators to 'compose."))
    (let (last-op . ops) rev.ops
      (zap [map [list quote _] rev._] ops)
      (zap [list quote _] last-op)
      (vau args env
        (eval (foldr list ops (cons last-op args)) env))))
Here's a non-varargs version of the same thing:

  (def compose (a b)
    (zap [list quote _] a)
    (zap [list quote _] b)
    (vau args env
      (eval (list a (cons b args)) env)))
With your version, I'm a bit concerned about hygiene: What if the $f and $g expressions contain '$args? I think it could at least help to make 'compose a function rather than a macro, so that any preexisting instance of '$args is resolved outside your own '$args scope.

-----

1 point by akkartik 5262 days ago | link

$args is an implicit gensym :)

"I think it could at least help to make 'compose a function rather than a macro, so that any preexisting instance of '$args is resolved outside your own '$args scope."

There's no difference between functions and macros in wart.

-----

2 points by rocketnia 5262 days ago | link

"$args is an implicit gensym :)"

I wasn't asking a question like that. :-p What I was asking was more along the lines of whether some expression like (compose foo $args) could end up with variable capture issues.

For all I know, maybe it wouldn't. Kernel's approach to evaluating an arg in the caller's scope is to pass scopes around as first-class values. Your approach is to change the scoping rules around in ways I don't really grok yet. Also, the way you're expanding $foo might be a kind of code-walking that essentially takes the form of its own kind of scoping rule.

-

"There's no difference between functions and macros in wart."

I think I already understand what you're trying to clarify here, too. What I'm saying is to use something other than "mac" to define 'compose.

If we forget about "functions" for a moment, what I mean is that instead of doing this...

  mac compose($f $g)
    `(lambda '$args
       eval `(,,$f (,,$g ,@$args)))
...it might be more hygienic to do something like this:

  mac compose($f $g)
    with (f eval.$f g eval.$g)  ; outside the scope of $args
      `(lambda '$args
         eval `(,,f (,,g ,@$args)))
I expect it's easier to make this change by replacing "mac" with "def":

  def compose(f g)
    lambda '$args
      eval `(,f (,g ,@$args))
(Keep in mind that this version still doesn't quote 'f and 'g, which I recommend doing in case they're lists or something.)

-----

2 points by akkartik 5262 days ago | link

Wow, you're right, compose doesn't need to be a macro!

Why was I thinking of it as a macro? Ah, I was blindly copying the arc version. And it looks like that doesn't need to be a macro either:

  (def compose fs
    (if (no cdr.fs)
      car.fs
      (fn args
        (car.fs (apply (apply compose cdr.fs) args)))))
Does that seem right? It seems to be working fine..

---

I still don't follow your arguments about hygiene. I suppose it's conceivable that code could use args2179 directly without going through gensym. Or that compose could call itself recursively and override $args in the caller's scope, and that it would be for some reason undesirable. Is either scenario what you were thinking of?

Thanks a lot for the comments. I think you understand the implications of my design decisions where I've been flailing around writing unit tests as I think up special-cases.

-----

1 point by rocketnia 5260 days ago | link

"Does that seem right?"

Yeah, I don't know why Arc's 'compose is a macro. I think I've tried to (apply compose ...) at least once and gotten bitten by that. ^^

---

"I still don't follow your arguments about hygiene. I suppose it's conceivable that code could use args2179 directly without going through gensym. Or that compose could call itself recursively and override $args in the caller's scope, and that it would be for some reason undesirable. Is either scenario what you were thinking of?"

I don't think so.... What I mean is, is it possible for the value inserted with ",$f" or ",$g" to contain the symbol '$args (assuming $ isn't a reader macro) in a way that causes it to be confused with the lambda's meaning of '$args?

  mac compose($f $g)
    `(lambda '$args
       eval `(,,$f (,,$g ,@$args)))

-----

1 point by akkartik 5260 days ago | link

Ah, I see. The answer is no, there can be no $vars after evaluation. $ is indeed expanded at read time.

-----

1 point by akkartik 5262 days ago | link

"(Keep in mind that this version still doesn't quote 'f and 'g, which I recommend doing in case they're lists or something.)"

You mean for something like:

  (compose '(1 2 3) *)

?

-----

1 point by rocketnia 5260 days ago | link

Yep. Here's a slightly-less-toy toy example:

  (let (foo nil bar nil)
    ; ...build foo and bar up as lists...
    ; ...
    (foo:- len.bar 1)
    ; ...
    )
If 'compose creates (<value of foo> (<value of -> len.bar 1)), then <value of foo>, being a list, might evaluate as an fexpr call.

-----

1 point by akkartik 5260 days ago | link

In wart fexprs either evaluate to a compiled op or to a list with the atom evald-lambda in the car.

You do raise the question of dealing with lists and hashes in function position. Wart doesn't do that yet, and it wasn't even on my todo list so far.

-----

1 point by rocketnia 5260 days ago | link

"You do raise the question of dealing with lists and hashes in function position. Wart doesn't do that yet, and it wasn't even on my todo list so far."

Oh, if you choose not to add that, I don't mind. ^_^

Still, as long as there's no quoting, I'm betting people can inject code in there, like (compose '$args '(while t)). Not that it's wrong to design 'compose with that code-injection feature in mind, but it would surprise me.

-----


I think it's kinda intuitive for `(foo ,@(list 1 2)) to expand to (quasiquote (foo (unquote 1 2))). If unquote takes multiple subexpressions, as it does in Common Lisp (http://arclanguage.org/item?id=9912), this can evaluate to (foo 1 2).

However, I really think this should be considered part of the behavior of 'quasiquote. Just like 'unquote has no meaning except as interpreted by 'quasiquote, it's fine for @ to have no meaning except as interpreted by function calls and 'quasiquote.

Hmm... if you say `@(list 1 2), does that pass (splicing (list 1 2)) or something to 'quasiquote, or does it actually pass 1 and 2? If I were trying to accomplish @ with fexprs, I think I'd prefer the former, and I'd treat regular functions/applicatives as though they were fexprs that expanded (splicing ...) manually.

-----

1 point by rocketnia 5284 days ago | link | parent | on: Rainbow-js usage

Thanks a lot for the interest! ^_^

I wrote Rainbow.js with the idea that it could be hooked up to whatever external definitions of System_in, System_out, and System_err are provided to it.

In fact, the file rainbow.js itself is laid out as though it's in function scope, rather than just global scope. It declares all its variables, except for System_in, System_out, and System_err. For instance, I believe you could even do something like this...

  ; Untested code!
  (w/outfile out "rainbow-constructor.js"
    (w/stdout out
      (pr "function makeRainbow( System_in, System_out, System_err ) {"
          (filechars "rainbow.js")
          "}")))
...and get yourself a new library that can make multiple instances of Rainbow. (Keep in mind that this Arc code can't be run in Rainbow.js itself, thanks to the file IO involved.)

Alternatively, you can do this...

  ; Untested code!
  (w/outfile out "rainbow-constructor.js"
    (w/stdout out
      (pr "exports.makeRainbow = function (
               System_in, System_out, System_err ) {"
          (filechars "rainbow.js")
          "};")))
...and now you have a Node.js module with the ability to construct Rainbow instances. Implementing the streams in terms of Node.js streams is an obvious choice, but not the only one, and maybe not even the best one. :)

I recently found out that ES.next is supposed to change ECMAScript so that there's no global scope; it's lexical scope all the way down. Rainbow.js should cope with that change pretty well (which is NOT to say it will take advantage of new optimization features or be secure for mashups).

This "stream implementation as a parameter" approach could also generalize to apply to filesystem operations. That is to say, there could someday be a "filesystem" parameter in addition to "System_out" and friends, and Rainbow.js could define file operations in terms of it, just in case someone had a good idea of how the filesystem should behave. Like, the Rainbow.js filesystem could delegate to Node.js's filesystem operations, or in the browser, it could provide an awkward interface for local storage or AJAX requests. That's not in the current Rainbow.js, but feel free to fork it or something. ^_^

I just updated the REPL (http://rocketnia.github.com/rainbow-js/test/) so that the global variable 'window is defined. It's as easy as putting this code after Rainbow.js, but in the same scope:

  Symbol.make( "window" ).setValue( new JsObject( window ) );
Well... it was as easy as that after fixing a bunch of JsObject-related bugs. :-p

Using this, it should be generally possible to get whatever else you need using eval: (java-invoke window 'eval (list "2 + 2")). A more in-depth way to extend Rainbow.js is to define new builtins just like the others are defined:

  // Untested code!
  
  function Alert() {
      Builtin.call( this );
      
      // Finish Builtin initialization, which sets the named symbol to
      // this function instance.
      this.init( "alert" );
  }
  
  Alert.prototype = new Builtin();
  Alert.prototype.className = "Alert";  // for error messages
  
  // A specialized one-argument implementation, for speed. (Well, speed
  // in Java Rainbow at least. I haven't benchmarked how effective this
  // still is in Rainbow.js.)
  Alert.prototype.invokef1 = function ( vm, arg ) {
      alert( "" + arg );
      vm.pushA( ArcObject.NIL );  // the return value
  };
  
  Alert.prototype.invoke = function ( vm, args ) {
      Builtin.checkExactArgsCount( args, 1, this.className );
      this.invokef1( vm, args.car() );
  };
  
  // Now invoke that initialization process. The standard builtins are
  // initialized in Environment.init(), but that's just to be consistent
  // with the Java code.
  new Alert();
This may be especially necessary if you want to define IO operations. Rainbow.js defines 'readc, 'readb, and 'sread so that they're synchronous to Rainbow.js code (just like in Rainbow) but implemented in terms of asynchronous JavaScript code. Making them asynchronous operations would also be somewhat tough, since you'd have to start a new Rainbow.js continuation for handling the event.

Starting a new continuation isn't all that bad. Rainbow.js doesn't define a standard mechanism for it yet, but you can currently take advantage of its REPL implementation:

  var result;
  Console.compileAndEvalAsync_( new VM(),
      ArcParser.readFirstObjectFromString(
          "((fn a a) ((fn a a) 4 3 2) 1)" ),
      function ( error, r ) { result = r; }, !!"sync" );
The !!"sync" parameter means that it will not allow asynchronous operations. If 'readc, 'readb, and 'sread can't finish their read operation synchronously, an Arc exception will be thrown.

Anyway, I hope this is the kind of information you're looking for. I'm open to any questions and suggestions. ^_^

-----

1 point by rocketnia 5286 days ago | link | parent | on: Mzscheme vs PLT Scheme vs Racket

Only documentation I've used too. ^_^ With Arc documentation as it is today, part of Arc programming is being prepared to read the source code to get the whole story. The Arcfn documentation is still a good place to get to know the essential utilities.

Anarki provides "help" functionality too, in case that, uh, helps.

-----


What we're looking at here is the base case of a dotted list:

  (a b . rest)
  (a . rest)
  rest
The syntax for a single cons cell is (a . b). The (a b c . d) syntax is a shorthand for (a . (b . (c . d))), and the (a b c) syntax is a shorthand for (a b c . ()), where () is nil. Here's a bit of a demonstration:

  arc>
    (def inspect-conses (x)
      (treewise (fn (a b) (+ "(" a " . " b ")")) [tostring:write _] x))
  #<procedure: inspect-conses>
  arc> (inspect-conses '(a b c))
  "(a . (b . (c . nil)))"
  arc> (inspect-conses '(a b . c))
  "(a . (b . c))"
  arc> (inspect-conses '(mac lol (first second . rest) ...))
  "(mac . (lol . ((first . (second . rest)) . (... . nil))))"
  arc> (inspect-conses '(mac lol (first . rest) ...))
  "(mac . (lol . ((first . rest) . (... . nil))))"
  arc> (inspect-conses '(mac lol all-args ...))
  "(mac . (lol . (all-args . (... . nil))))"
If you come from Common Lisp, maybe you already know all this, but I hope it helps someone out there. :-p

-----

1 point by orthecreedence 5294 days ago | link

Thanks for the help, guys!

-----

1 point by rocketnia 5298 days ago | link | parent | on: Syntax for list ranges

"Nope, no can do. "." is used for ssyntax as well."

Even though . is used for ssyntax, .. isn't, at least in Arc 3.1. While (ssyntax 'a..b) returns t, ssexpanding anything with .. in it throws an error.

You've talked about classifying ssyntax as prefix, postfix, and infix before, so I suspect you'd want a..b to expand through (a .b) to (a (get b)). As for me, I think it's nicer to treat multiple-character sequences like .. as their own operators.

-----

More