There's a comment at the end of arc.arc that might be relevant:
; solution to the "problem" of improper lists: allow any atom as a list
; terminator, not just nil. means list recursion should terminate on
; atom rather than nil, (def empty (x) (or (atom x) (is x "")))
But I think this means (len '(a b c . d)) would be 3, which may not be what you wanted.
I agree with you. I don't think that returning a range is necessary.
Even if call position and assignment weren't handled separately, it would still be possible to work off of the length of the argument and the index, without needing a range.
The question is whether or not pg agrees with us enough to add it to arc3 ;)
True. And I do. Unfortunately, I'm busy working on several other things at once right now. If you want to start working on it, be my guest. Hopefully I'll be able to share what I've been doing soon.
In the particular case of returning multiple indexes into a string though, you don't usually know in advance how many matches there will be, so destructuring isn't an option.
Multiple return values from a form are allocated on the stack, not on the heap. I don't 100% understand what that means, though...
One practical consequence is that you don't have to deal with later multiple values if you don't want to, but when values are returned as a list, you have to deal with them.
I think that your first example is probably best in terms of compactness and clarity (associating each condition with its corresponding code). The completely straight indentation can be difficult to scan quickly if you have more than one condition. IMO, the only problem with your first example is when the conditions span more than one line or are very long.
In terms of 'if, I was looking through some of pg's code just now, and noticed that he does use the wavy form of indentation. Thus, if you're big on proof by authority, the correct form of indentation for 'if would be the wavy one...
However, in this case I agree with Adlai: pg had even planned on getting ppr to "properly indent long ifs". I presume that means to implement the wavy feature. It's also the convention for Anarki, according to the CONVENTIONS file.
So, here's what I'm thinking should be done for ifs:
1) If you only have three expressions, if-then-else, use:
(if a
b
c)
1) If you like the format, and the expressions are very short use:
(if a b
c d
...
e)
3) If you don't like that form, or the expressions are longer, use:
(if (a )
(b )
(c )
(d )
(e ))
I'm not sure whether to use one or two spaces between clauses. The anarki conventions file uses two spaces. Elsewhere I've seen only one space. So, which is it? Now or never ;)
(I'm writing a new version of ppr with proper indentation of forms, in case you wanted to know why I'm so interested in the indentation question)
I think two is better, because it a) clearly distinguishes it as indentation (rather than just an accidental #\space), and b) it's consistent with the indentation of macros & co.
Should it depend on whether they fit on one line or not? If they don't fit, should it still be like b., just with the second part spilling onto the next line?
Your first version is the one currently implemented by ppr.arc. If you would like, you can write the indentation function for your second version (it can even include the "; else")
Otherwise I think that the first version is generally clearer, and the second is rarely needed, as the value of the case statement can't usually be very long. Could you give me an example where you use your second version?
Sure; here's something from my tagged-unions.arc on Anarki (Arc 2). It's responsible for parsing the types specified for slots in the tagged union:
(each type-frag types
; Assemble the ('type name) pairs into two lists, so that we can iterate
; through them.
(case (type type-frag)
; If it's a symbol, then we're defining a new name; add a new set of
; values, and go.
sym
(do
(zap [cons type-frag _] names)
(zap [cons nil _] values))
; Otherwise, we're adding a value to an existing variant.
cons
; I changed my mind about the order (now it's (name pred?) instead of
; (pred? name)), so I'm reversing it here.
(zap [cons (rev type-frag) _] (car values))
; else
(err "Invalid member of tagged union declaration.")))
I should also add (just as another data point) that my multi-condition ifs look like that too, and single-condition ifs look like the following:
(def macexn (n expr)
" Macroexpand `expr' `n' times. NB: `expr' *is* evaluated!
See also [[macex1]] [[macex]] "
(if (and (number n) (> n 0))
(macexn (- n 1) (macex1 expr))
expr))
I'm not hugely wedded to any of my conventions, though, and I haven't coded in Arc for quite a while anyway; this isn't intended to get you to change anything or convert people to my style. As I said, it's just another data point.
I don't get symbol macros. Is there an example of a symbol macro in use? On another thread someone mentioned that a symbol macro can expand into arbitrary code. Is this the same as an ordinary macro with no arguments (except for a pair of parens) ?
On a possibly related note, when compiling (assign ...) forms, ac.scm calls macex on the 1st arg:
(assign (foo bar) 12)
'(foo bar) gets macexed. Later, ac-set requires that its first argument be a symbol. So '(foo bar) must expand into a symbol. I didn't see this obviously being used anywhere in arc ... do I need to look harder? Is its usage buried under layers of macros (deep inside '= for example), or what's the story? Is this somehow related to the concept of symbol macros?
I'm not sure about your related note, but for an example of symbol macros in use:
This example is from Common Lisp's object system, called CLOS. When you want to do some code while accessing specific slots of an object, you can use a 'with-slots form, which lets you name symbols that you'll use to reference specific slots of the object. The 'with-slots form then generates local symbol-macros for each symbol you mentioned, which expand into code to access those slots. An example, taken from PCL [1]:
(with-slots ((bal balance)) account ; Here 'balance is the name of the slot, and 'account is the specific object
(when (< bal *minimum-balance*) ; *symbol* is a CL convention for global variables
(decf bal (* bal .01))))) ; decf is like --
[1] Practical Common Lisp, by Peter Seibel, available online at www.gigamonkeys.com/book/
Another use for symbol macros is in writing package/module systems, where you want to determine which package a symbol goes into based on the compilation context.
You can do this to a certain extent already, using package!symbol notation, but this still doesn't allow using symbols without prefixes.
The lexical binding shadows the global binding if it isn't in functional position:
(def trivial-example (obj with) (foo obj with))
does what you would expect it to do. But I don't know whether or why it's better that global macro definitions override lexical bindings in functional position.
So far, we have lib/arc-read.pack and lib/parser.arc, both in anarki, as well as lib/parsecomb.arc. Not only do you have your reader, but you have a choice of them too!
arc-read seems to be designed for easy extensibility although I haven't tried it at all. (If I had looked carefully before embarking upon parser.arc, I might have saved myself a lot of trouble).
parser.arc doesn't support the full range of scheme numbers, nor does it support |foo| symbol syntax yet, apart from ||. I'm working on a new version that will hopefully be faster - welder depends on the tokeniser for syntax highlighting.
parsecomb.arc, if I understand correctly, isn't an arc parser but a library for building parsers.
In any case, having an implementation of 'read in arc makes a lot of sense, and it's in the todo at the top of arc.arc
parser.arc (at least, the parser lib I wrote; there are at least two others in anarki) uses coerce to get corresponding atoms from the token in string form. 'eval would probably have worked too. Otherwise, parser.arc just returns the forms it reads in, only transforming bracket syntax and string interpolations.
I completely agree ac-tunnel (or mz) is a useful facility to add to arc, especially to use for once-off hacks, or to figure something out before committing to an xdef. I wrote rainbow just so I could use java libs while playing in arc (among other reasons).
As the author, you get to name your babies. I didn't agonise over java-new, java-implement, or (ugh) java-static-invoke - they're mostly buried under arc macros in any case.
In the worst case, if there's a conflict with mz (or ac-tunnel), I would recommend an excellent symbol renaming hack available on github at http://catdancer.github.com/load-rename.html