Regarding your second suggestion, we could also use . instead of &, as that's what Perl and PHP do - feels a little more natural to me. But . might cause me a little mental friction in differentiating between the different uses of . in (. "a" "b") and (do (= a '(1 2)) a.0).
To be honest, I'm still not crazy about the idea simply because I don't need the speed boost and + doesn't seem to cause me to use extra mental cycles when reading my code. I'd be open to it though if the community really wanted it that way.
We could vote and also ask PG what he thinks and then make a decision.
With regard to (2), to destructively append the list '(1 2 3) to xs, you can:
(zap join xs '(1 2 3))
"zap join" is several characters longer than ++, but zap has general utility.
I use the string function to concatenate strings. It seems to work identically to +, as long as the first argument is a string. I do give heterogeneous arguments to string usually, and I like seeing that they will clearly be coerced into a string.
I have a couple of ideas.
1. It would be little problem for me if Arc provided + as is and provided a "plus" function that worked only on numbers and allowed me to simply go (= + plus) and proceed normally. Unfortunately, that would break all the functions in arc.arc and so forth that use + to concatenate lists (which includes, among other things, the setforms function, which is used in expand=). It would be really nice if one could "freeze" the variable references in those functions, so that changing the global value of + wouldn't change what "+" in the bodies of those functions referred to.
2. If you use concatenation so much, perhaps we could allocate an ssyntax character for concatenation. & is currently used for andf (experiment with ssexpand to see). We could boot out that usage, or perhaps have "&&" become andf. Good/bad idea? (Precedent: my memory tells me the TI-89 uses & for string concatenation.)
Given the way I defined a Bank-Account, you could implement this rather simply:
(def deposit (x . args)
(apply x 'deposit args))
Or, with akkartik's implementation, which I agree is nicer and better:
(def deposit (x . args)
(apply x!deposit args))
And you could make another "object" and give it a distinct 'deposit "method" and it would work fine.
So... it seems that one can easily implement these things in Arc. If you have some project in mind for which you want OO stuff, you can just do the things posted in this thread, and tell everyone how it goes if you like.
If you think OO should be put into the core of the language, well, there's a certain resistance to adding features just because someone suggests it. The core would become large, disorganized, and difficult to maintain if we did that. So, you would have to give a convincing argument as to why it's worth putting in there, and a demonstration of how useful it is in some real project of yours is probably the best argument you could give.
I've spent some time thinking about how to extend it for multiple-dispatch, and I didn't want to also think about setting the arg index to dispatch on.
In case you didn't know, the point of 'atpos is to implement atstrings, an optional string interpolation feature that lets "@qty @(plural qty \"taco\") @@ $@price" compile so that it evaluates to things like "2 tacos @ $11". This option isn't enabled by default, so any Arc program that uses atstrings, such as news.arc, has to (declare 'atstrings t) first.
IMO, an @ at the end of an atstring is a syntax error, since it isn't escaping anything. It might be convenient to give "...@" a meaning like (sym "..."), (err "..."), or even just "...@@", but that's adding functionality rather than fixing bugs.
What other goodies does your arc.vim plugin have? Is your editor at all integrated with the arc repl? Lack of a repl that I could easily send arc code to was the reason I switched to emacs after years of using vim. These days, using emacs with viper (vim emulation mode), I don't miss vim at all.
String concatenation is particularly convenient after the + change for arc3.tar: http://arclanguage.org/item?id=9937. That's probably what I use + for most of the time.
I find thinking about whether or not I have to escape the "@" character is distracting
I find this is easier with proper syntax highlighting. My arc.vim ftplugin can detect if you have (declare 'atstrings t) and, if so, highlights the escaped parts of strings. That way, you know if @ is escaped just by glancing. But I don't mean to shamelessly plug, haha. I don't use atstrings either, but my reason is far lazier: in the middle of writing code, it's less effort to just use + than it is to declare then go back and start using @s.
I make heavy use of + for concatenation throughout my code. I prefer it for a few reasons:
1. I find myself concatenating lists frequently and I prefer that frequently used functions be short. join has 4 chars to +'s 1
2. I also find ++ convenient for modifying a list variable. What would you use for the equivalent for join? In Racket's style, it would be join!, but I don't see a good analogue in arc for your proposal.
3. I'm constantly doing string concatenation. + is good for that because it's a short function name and also because I expect it to work because it works like that in many popular high-level languages (python, ruby, javascript). I also don't like to use arc's "@" for string-escaping because I find thinking about whether or not I have to escape the "@" character is distracting.
For what it's worth, I practically never add numbers. I actually use subtraction much more than addition! So I don't mind whatever these operations are named--and I mind the speed even less, actually--but I can testify that this change would sorta hinder my own code at this point. :-p
You've almost certainly encountered this comment above the definition of 'join, but I'll paste it here just in case it elucidates anything:
What you describe is doable with closures, at least as far as your example goes. Your example can be implemented almost word-for-word in Arc as it stands:
obj is a macro that creates a hash table (you don't need to quote the keys, by the way). So, (Socket url port) will return a function that accepts 1 or more arguments, assumes the first is a method name, looks it up in the hash-table o, finds the corresponding function, and applies it to the rest of the arguments.
If you want to give your object thing internal state that you'll want to modify, use a let.
Here is an example that probably demonstrates most of what you're looking for. It is pretty much cribbed from SICP[0].
Am i wrong if I think that, in the end, the main difference between 'def'+'eval' and 'mac' is that 'mac' allows its code to be evaluated and expanded once, whereas 'def' needs to expand its code every time it is called ?
It's 'eval that expands its code once every time it's called. If you don't use 'eval at all, then all your arc code will be expanded exactly once, at the time you enter it at the REPL or load it from a file (since those are the points where 'eval is called internally).
When you enter the expression (def foo (x) (obj key x)) is at the REPL, it's evaluated as a two-step process: First it's compiled (which expands the (def ...) and (obj ...) macro forms), and then the compiled code is run. When it's run, the body of the function isn't run yet; instead, it's packed up into a procedure and stored as foo. When you call foo later, no expansion takes place, only running of already compiled code.
A better (but not perfect) way to use 'eval equivalently to 'mac is something like this:
(def mywhen (test . body)
`(if ,test (do ,@ body))
(eval `(time:for i 1 1000 ,(mywhen '(is i 666) '(prn "Oh no!"))))
If we wanted to make 'do a non-macro, we could do that too:
(def mydo body
`((fn () ,@body)))
(def mywhen (test . body)
`(if ,test ,(apply mydo body)))
(eval `(time:for i 1 1000 ,(mywhen '(is i 666) '(prn "Oh no!"))))
Finally, if we wanted to avoid the use of macros altogether, the example would turn into something like this:
; The procedure corresponding to an Arc macro can already be obtained
; using 'rep.
(assign mywhen rep.when)
(assign mytime rep.time)
(assign myfor rep.for)
(eval:mytime:myfor 'i '1 '1000 (mywhen '(is i 666) '(prn "Oh no!")))
I could make a few guesses as to why this kind of programming isn't popular in lisps, but my personal reason is that I tend to consider all operators to provide their own syntax, with procedure call syntax just being the default choice. For me, it's inconsistent to have the '(prn ...) syntax be quoted when the (mywhen ...) syntax isn't.
You can get around the problem also in the following "weird" way:
(let x ''(+ 1 2) (xrepeat 3 `(pr ,x)))
but the way you proposed is more "natural" since it respects the "rule" of quoting functions and variables passed at calling time.
Am i wrong if I think that, in the end, the main difference between 'def'+'eval' and 'mac' is that 'mac' allows its code to be evaluated and expanded once, whereas 'def' needs to expand its code every time it is called ?
Thank you for the answer, very interesting. Here's some more things I found:
1) actually the "reference to undefined identifier" error can be avoided un-quoting the variables at calling time, as in:
(let i 2 (eval `(+ ,i 1)))
(time:for i 1 1000 (mywhen `(is ,i 666) '(prn "Oh no!")))
2) the last expression is computed in 176 msec, whereas the regular macro 'when' is computed in 2 msec (much faster as you forecasted).
It deals with lists and strings well, which is decent: Arc's only other sequence-like type is the table (I don't think you'd ever want to treat symbols as a sequence of 1-character symbols; you'd just use a string). Tables would work better if they were properly coerced, cf. the comment above tablist and listtab in arc.arc.
The more I think about it, the more I like this model. Conceptually, it seems that map should behave like
(def map (f seq)
(map-as (type seq) f seq))
even if it's not implemented like that -- all the coercions would surely be slow. (Tangential: map would also need to handle multiple sequences.) But it makes more sense for map and coerce to at least have compatible behavior. Plus, map's current behavior is a degenerate case of the coerce-compatible map:
Btw, you need an additional quote to get values like your "x" into the eval in all cases... "blub " works because a literal string evaluates to itself, but consider a list like (+ 1 2)
Also, you got around the limitation that eval doesn't have access to the lexical variables of its caller by passing in the value of "x", but if you wanted to have your eval/macro to set the value of a variable, that would be harder.
As waterhouse mentioned, two differences between macros and eval are that macros are expanded once when your program is loaded, but eval has to expand and compile its argument every time its called. And like any function eval doesn't have access to the lexical variables of its caller unless you pass in values yourself.
Aside from that, macros and eval are quite similar in the sense that they both treat code as data, and so you have the opportunity to manipulate the code before it gets evaluated.