About thunks; this is actually a situation where statically typed languages have the potential to do something interesting that is hard in dynamically typed systems.
If foo takes an argument that should be a zero arity function that returns a number, and you write:
(foo (+ x y))
The compiler could recognize that the expression you used for the argument can be converted into a function with the correct signature. I think I have heard of languages on the JVM that do this, not sure which.
Also note that Smalltalk-80 is very much designed around the principle of thunks. E.g:
Yes, once a macro has been used then redefining helper functions won't affect code which has already been compiled. (New or reloaded code which uses the macro will get the redefined helper function).
It makes the helper functions act like macros in that way: redefining a macro won't affect code which has already been compiled either.
I like your approach a lot, aw, but I too noticed this issue. I think it can be overcome just by changing the idiom slightly.
First, some definitions:
(def fn-latemac (mac-getter)
(annotate 'mac
(fn args `(,(mac-getter) ,@args))))
; Not to be hypocritical, let's implement 'latemac as if by using
; 'latemac.
(mac latemac (globalvar)
`(,(fn-latemac:fn () fn-latemac) (fn () ,globalvar)))
Now we can define your example 'foo and 'bar like this:
(mac foo ()
`(,latemac.do
(,latemac.prn "this is macro foo expanding into bar")
(,latemac.bar)))
; Oops, now let's define 'bar. Go go, late binding!
(mac bar ()
`(,latemac.prn "yo, this is macro bar"))
As you can see below, this already works in the current version of ar. ^_^
arc> (foo)
this is macro foo expanding into bar
yo, this is macro bar
"yo, this is macro bar"
arc>
(def bar ()
(prn "hi, this is bar"))
*** redefining bar
#<procedure:g1590>
arc> (foo)
this is macro foo expanding into bar
hi, this is bar
"hi, this is bar"
Essentially, these are hygienic macros at this point. They use the syntactic closure technique for hygiene, in this case implemented using regular old lambda closures. ^_^ A syntactic closure approach has undesirable properties like making the expansion hard to code-walk or serialize, but those activities seem rare enough anyway, probably because they're already difficult. I think this'll do just fine as far as modules go.
Assuming you don't change the syntax of dotted lists, a problem is that you wouldn't be able to use an expression for "xs".
The dot notation creates a cons for you, and a cons creates a regular list if its second argument is nil or another cons (which itself is a regular list).
Thus
'(a . (b))
works like
(cons 'a '(b))
which in turn is like
'(a b)
which you can see for yourself:
arc> '(a . (b))
(a b)
So an expression like
(bar x . (foo))
that we'd want to work like
(apply bar x (foo))
is parsed by the reader like this:
arc> '(bar x . (foo))
(bar x foo)
and by the time the compiler sees the expression there's nothing there to tell it that a dot was used.
Of course, if you change the reader so that a dot doesn't produce a dotted list but instead creates a token for the compiler to see, then you can do whatever you want... though you'd also need to extend the compiler to handle the dot notation in the other places which is currently handled by the reader.
I think the number of times I use 'apply with a variable in the final position is enough for the syntax to be relevant to me... but then I'd be likely to refactor (a b c . d) into (apply a b c (something-else)) and vice versa all the time, which would be a bit of a pain. Then again, it's balanced against the cost of refactoring (a b c) into (a b c . d) and vice versa....
by the time the compiler sees the expression there's nothing there to tell it that a dot was used.
In Racket's syntax there is a way, I think. It makes syntax more complicated than just its conses, though. You'd have to use an Arc variant of syntax-quasiquote (or just syntax-quasiquote) to construct it in macros.
Meanwhile, the (cdr `(foo . ,<scheme-expr>)) quirk would become more of a bug than it already is. But then I assume whatever Arc hack implements this syntax would also have '$ built in, so there's probably no point to keeping the quirk around.
I don't see a way you could use it if xs were the only argument, i.e. a way to rewrite `(apply bar xs)`, but in that case apply is more paletable anyway:
This basically means you have to declare every global you want to make available from the file you load. You could even specify what names to give them:
1. Shouldn't the last line be (watch file-name delegate) so that it continues to watch after the first update? Or should that be optional?
2. It might be a good idea to pass file-name to the delegate. Not strictly necessary, and doesn't give any benefit in this example, but it could mean that you sometimes don't need to use a fn as the delegate. If you didn't want to pr "reloaded" for instance, you could just do (watch file-name load).
3. Is the hash table really necessary? Here is a version without it:
I put the call to watch as the last line inside the file itself.
And the hash table is there because I initially wanted to watch multiple files at the same time. But I suppose if the main file in the web app loads the other files, then it's not needed at all.
The other thing I did is running the (asv) thread only once by checking for an unbound symbol, so that when the file is reloaded it doesn't run the server again.
Ah, so loading the file makes it watch itself. You need to do the load in a thread then?
I still fail to see how the hash table helps since my version above should do exactly the same as yours. I would see the point if you made a thread check all files in the table, and made the first call to watch launch the thread, while subsequent calls just add entries to the table.
With defrule and in akkartik's python-like keyword arguments (http://arclanguage.com/item?id=12657), argument names become significant. Normally you're free to change the names of arguments, for instance because you didn't put enough thought into them first time around. Is it a problem to start treating argument names as a set-in-stone kind of thing? I mean, you never know which args someone is going to rely on not changing, so the names become part of the public interface.
An alternative to public interfaces is to not treat any part of your code as set-in-stone, but instead to improve your code whenever you think of something better -- such as using better argument names as you come up with them.
The traditional assumption is that if I'm using your code and you change it, I'll find it hard and difficult to change my code to use your new version, and so you should put time and effort into coming up with things like stable public interfaces -- things that you wouldn't otherwise do for yourself, but things that you think will help me.
However, using a powerful language such as Arc I've found easy to change my code to make it work with new releases. For example, the home page of arclanguage.org says "Expect change. Arc is still fluid and future releases are guaranteed to break all your code." Which sounds pretty dire. Yet in practice I've easily rebased my code on every release of Arc since arc0.
Easier in fact for me than my experience using other languages that strive to maintain compatibility with earlier versions -- their libraries end up having so many layers and compatibility modes that it becomes tedious for me to figure out what they're doing and where their bugs are.
The arguments against strings as lists are that they're going to be incredibly slow and take up many times more memory. Characters will in practice be cons cells, which means they take up something like 8 bytes each, are allocated individually, and generate lots of work for the garbage collector. As others have mentioned, accessing the nth element of a linked list is very slow, and even iterating the list is slower than for arrays.
I would much rather prefer string to be a subtype of list, with the functions that actually access the elements dispatching on the type. This would have all the benefits without the performance loss.
The missing bits to enable this in a general way are transparent type/annotate, generic functions, and hierarcical types. By transparent I mean that annotate should not return a cons cell with the original value inside. The runtime should have support for marking each value with a type.
I dislike the idea of declaring anything to be a separator--and therefore a reserved, special word--that isn't already illegal to put in a parameter list.
...egad. It turns out that it is not just legal, but it works fine, to use "quote" as a variable name just like any other, in Arc and Common Lisp and Racket.
Meanwhile, CL does disallow defining a function named "quote". It also disallows using 'flet to create a local function named "quote".
Well, what do you think of forbidding people to rebind quote (locally or otherwise)? I think it's acceptable. quote is a fundamental part of Lisp. If it is rebound, then either that will screw up quoted things, or the Lisp parser will handle (quote blah) forms specially, in which case rebinding quote to a function and attempting to call it will fail (you'll just quote the arguments). In other words, either this will fail to return 12:
arc> (let quote [+ _ 2] (quote 10))
12
Or this will cause a presumably unexpected error when '(1 2 3) is interpreted as something other than a quoted list:
I think both of these possibilities suck[1] and, for the purposes of formally specifying Arc, we should say "This is not supported; we recommend that an implementation throw an error when encountering an attempt to locally or globally rebind quote." I probably wouldn't make it illegal, but I'd make it print something like "COMPILER-WARNING: WTF, you're trying to redefine quote? This will probably not end well."
So, if using "quote" as a parameter is officially unsupported, then this officially makes room for "quote" to be used as a special marker in parameter lists. When a program that parses parameter lists encounters (quote blah), it should stop and say "Aha, this is not a legal parameter. What now?" And at this point we can give it whatever desired features in a nice, modular way.
In official Arc, we would have, say, "If blah is 'opt or 'o or 'optional, then proceed to interpret optional arguments." Then akkartik might add, "If blah is 'key, then proceed to interpret keyword arguments", and aw might add "If blah is 'as, then proceed to interpret coerced arguments", and these would be totally compatible extensions to Arc, as long as they didn't choose the same name.
I do think this is the way to go.
[1]A "Lisp-2 function/variable namespace separation" buff might say at this point, "Aha! See, with the namespace separation, this isn't a problem; you can use quote as a variable all you like and it creates no problems." Retort: "I might just as well want to locally create a quote function with flet, and then you have a problem. (And if your language doesn't let you locally bind functions, then it sucks.)" Example case:
(flet ((quote (x)
(format t "~S~%That's what she said!" x)))
...)
I've been thinking more about making the arc transformer ('compiler' seems excessive) simpler and easier to add hooks into. I don't want to hardcode keywords as non-overrideable; instead I want new keywords like coerced to be easily added to the transformer.
What a coincidence! My account was a 1000 days old the day I read your post :)
At first I wondered if this was a bug in news or something, then I noticed that your account acutally said 1003 now.