I suppose we can sleep and poll the timestamp, but I'm running into a problem, mtime is defined in lib/files.arc,
The way I'm settings things up, my experimental "app" is in a completely isolated folder, and once I'm in there, "lib/files.arc" is no longer in the current directory, how should I go about loading stuff from "lib/"?
For now I only need the 'mtime' function so I just snatched it, but I hope there's a better way of importing standard libs.
Otherwise, here's a little file watcher I just wrote, seems to work with my tiny test case:
(def mtime (path)
" Returns the modification time of the file or directory `path' in
seconds since the epoch. "
($.file-or-directory-modify-seconds path))
(= file-ts* (obj))
(def file-ts-changed (file-name)
(no:is (file-ts* file-name) (mtime file-name)))
(def update-file-ts (file-name)
(= (file-ts* file-name) (mtime file-name)))
; watch file system for updates to file-name
; and run 'delegate' when file is updated
(def watch (file-name delegate)
(update-file-ts file-name)
(while (no (file-ts-changed file-name))
(sleep 2))
(delegate)
(update-file-ts file-name))
(def watch-load (file-name)
(repeat 2 (prn))
(pr "watching " file-name " for changes ..")
(watch file-name
(fn() (load file-name) (pr "reloaded " file-name))))
(watch-load "web.arc")
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.
I first encountered this idea in a theorem-proving class - that if you have a hard time proving a theorem, often a really good approach is to find a more general 'lemma' to try to prove. Stronger theorems are often easier because they let you assume more in the induction step. Recursion has a similar 1-1 correspondence with inductive proofs.
optional args don't map nicely to a list, they map more naturally to a dictionary (hash table).
Perhaps, the `o` is not needed?
(fn (a b c (d 1) (e) (f)) ....)
d is optional, but defaults to 1
e and f are optional with not default.
perhaps the builtin special function operator shouldn't take any named argument at all: just a list and a hash table, sort of like a generic python function:
My problem right now: fn((a b)) could define a function that takes a list of two args, or an optional arg a with a default value of b bound in the enclosing scope. This seems like a more serious problem than when I posted this thread.
Though (for what it's worth) I've since realized that I don't like the {a 1 b 2} syntax; I find the key value pairs aren't grouped together well enough visually for me, and the curly brackets don't stand out enough themselves.
Some optional arguments are better done as a list. The greater the number of optional arguments, though, the better off you are using keyword arguments.
The pattern of commas and quotes is rather confusing and seems arbitrary
I got there by replacing 'eval' with 'prn', and then I kept playing with commas and quotes until it printed the right thing, then I put eval back in.
So it seems to work but I have no idea why.
EDIT:
I'm not sure if that sort of thing is common in lisp/arc?
Am I approaching macros in a totally wrong way?
I presume I'm thinking in python, where a lot of meta programming techniques constitute of taking strings and using them to get fields that would normally be accessed using the symbol directly.
getattr(obj, 'field') # same as obj.field
and so we get into the habit of doing things like:
def apply_to_fields(obj, field_name_list, fn):
for field in field_name_list:
new_value = fn(getattr(obj, field))
setattr(obj, field, new_value)
I see you've encountered the strange ,',name idiom. I congratulate you for writing it yourself... I read about it in "On Lisp", in its description of an "abbrev" macro. The whole book is pretty good to read through, and it says a good deal about macros, so I would recommend you take a look; but section 16.1 explains directly how one can come up with such a thing:
http://www.bookshelf.jp/texi/onlisp/onlisp_17.html#SEC109
The full book is there, but if you want it as a pdf or
something, see Paul Graham's web site:
http://paulgraham.com/onlisptext.html
Also, note that there are functions called macex and macex1. It stands for "macro-expand". They work on any macros, including ones you've just defined; they are very useful for debugging macros. See:
arc> (macex1 '(let x 1 (+ x 2)))
(with (x 1) (+ x 2))
arc> (macex1 that)
((fn (x) (+ x 2)) 1)
Meanwhile, regarding the problem at hand. First, note that, since you're already using eval, 'apply-mac may as well be a function, defined and used like this:
(def apply-mac (mac-name args)
(each arg args
(eval `(,mac-name ,arg))))
;Usage
(= tag-list '(html title head body))
(apply-mac 'def-tag tag-list)
Second, there's a little side issue brought up by your definition of 'apply-mac. It binds the variable 'arg, which... turns out to be harmless, but in general I would use a unique name for 'arg:
(Oh man, nested backquotes... I haven't dealt with those for a while. Last time was probably when I wrote 'abbrev.) Now that I've given you a link to On Lisp, I'm going to cut myself off and point you to chapter 9 ("Variable Capture") for further discussion. If I said more, I'd just be telling you what you could read about in On Lisp.
Regarding general practices in Lisp (you ask about how you're approaching macros)... I can say a few things. First, know that eval is slow (it performs full syntactic analysis), and you most likely should not be using it at runtime. Second, using eval is almost always unnecessary; you could use a macro or something else, and if you can't see how, then that's probably due to insufficient macro-fu. Exceptions are when the code you want to generate and evaluate actually depends on stuff that happens at runtime (e.g. your program reads Arc expressions, evaluates them, and prints the results).
In this case, this code just sets up a bunch of definitions; it's run once and it takes an imperceptible amount of time anyway. Performance isn't an issue. I would probably write it as the one-liner "(each x tag-list (eval `(def-tag ,x)))" when I was sitting at the REPL just wanting to get code working; then I'd probably write up the 'def-tags macro in my actual code file. Here's my preferred version (paraphrasing aw):
I think it's overkill to create "apply-mac". The concept is complicated enough and rare enough that giving it a name doesn't do much for me. I think it's best to just write def-tags using basic Arc operators.
Finally, you ask whether this sort of thing is common. The thing you said before that was "It seems to work but I have no idea why", so I'll assume that's what "that sort of thing" refers to. My answer: Generally, no. Sometimes I'll get something working by brute force search, as you seem to have done; and sometimes I'll get something working by pasting it in from someone else's code; but in either case, I will think about it and verify that it is correct and should work, and usually I'll come away satisfied in my understanding.
The innermost ",name" gets replaced with the value of the variable 'name; the other comma still exists, and without the quote, you would get "(meh `(ach ,BOB))", which would try to evaluate BOB. That is just how nested backquotes are defined to work; an informal introduction to them usually doesn't cover nested cases. Then, the second time around, the expression 'BOB within the quasiquote gets evaluated (because it's unquoted by the comma), and it evaluates to the symbol BOB.
> I see you've encountered the strange ,',name idiom. I congratulate you for writing it yourself
cool :D
What I meant by whether "things like this" are common, I was referring to confusing (and seemingly arbitrary) pattern of weird symbols that's not in line with the spirit of clean, readable code that's meant for humans to read instead of being meant for machines to execute.
I know about html.arc, the point of the exercise is not to replace the builtin html macros, but to learn how to write macros (and how to think in Arc).
I have a couple of questions now:
1. Are macros defined in terms of 'eval'?
If there's such a thing as an anonymous function, why shouldn't there be an anonymous macro? Is eval the closest thing to anonymous macros?
2. Why would I want to my def-tags macro to be defined in terms of 'do'?
More generally, what the hell is the point of 'do' anyway? I would never have thought of doing anything inside a 'do' block, it seems rather redundant.
Note: I'm not trying to ridicule 'do', I'm just expressing my utter lack of understanding.
Btw, your guess of my code is pretty accurate, except I used string instead of +
(def tag (name body)
(string "<" name ">" body "</" name ">"))
Notice how the argument 'abcdef was quoted. Macros don't evaluate their arguments, but the code they generate might (e.g., if bar was a function, it'd try to evaluate abcdef as a variable).
They aren't actually implemented that way. eval operates at run-time and doesn't have access to lexical variables. Macros expand at compile-time, so it's as if you had the expansion there to begin with. E.g.,
arc> (let y 10
(eval 'y))
Error: "reference to undefined identifier: _y"
but
arc> (mac foo (arg) arg)
#3(tagged mac #<procedure: foo>)
arc> (let y 10
(foo y))
10
because
(let y 10
(foo y))
macroexpands into
(let y 10
y)
before it ever runs.
Anonymous macros are plausible, but they might force work to be done at run-time -- essentially, you're right that eval's the closest thing to it. But since macros happen at compile-time, you can't do something like
((if (mem 'x stuff) push pull) 'y stuff)
The compiler sees the macros push and pull, but they're not applied to anything, so it doesn't expand them. Then at run-time, you get an error as it tries to evaluate each form. You have a macro trying to act like a function (i.e., at run-time).
2. do is for when you want to execute a series of expressions, but do it in just one s-expression (i.e., the thing wrapped in do). You see it a lot in macros; e.g., defs in arc.arc:
which is just one list. You couldn't return multiple values, so the macro couldn't expand into
(def f (x) (+ x 1))
(def g (y) (- y 1))
directly.
Another place you see do a lot is in if statements. Each clause is one expression, but if you want to do multiple things in one expression, you need the do. E.g.,
(if test
(do (pr #\t) (pr #\h) (pr #\e) (pr #\n))
else)
will print "then" if test is true, otherwise it'll do else. This wouldn't work without the do:
(if test ; if "test"
(pr #\t) ; print #\t
(pr #\h) ; else if (pr #\h)
(pr #\e) ; then (pr #\e)
(pr #\n) ; else if (pr #\n)
else) ; then "else"
P.S. That's the return value of the statement.
arc> (pr "a") ; prints "a" WITHOUT a newline, then returns the first thing it
; printed (the string "a")
a"a"
arc> (prn "a") ; print "a" WITH a newline, then returns the first thing it
; printed (the string "a")
a
"a"
That's not always true. If a programming system supported first class macros, then an anonymous macro could be passed in as an argument to a function, which could then apply it in a more sophisticated way at run time. I.e. apply it to a list of objects that don't even exist at compile time.
However, since arc does not support first-class macros, and neither do most compiled languages that I'm aware of, you're basically correct.
it's missing the () from the end to call the function.
You're right, thanks for catching that.
The `#` in that jQuery selector condemns the Arc version to look worse than the code it's attempting to repair. If the selector didn't have special characters, as in
jQuery('content').hide();
then you could exploit Arc ssyntax and do
(jQuery!content.`hide)
but I won't pretend like that's a great improvement either.
This code isn't necessarily shorter than the JavaScript code, but it's set up so that you can conveniently compute parameters from the Arc side via things like (jquery "#blah" .height (- full-height top-height)) while also being able to compute them naturally from the JavaScript side via things like (jquery "#nav" .height (jquery "#content" .height)).
One downside is all the parentheses this leaves. But if that turns out to be a problem, the thing I'd do is to rewrite the JavaScript-generating utilities to return meaningful AST objects rather than just tagged strings. That way, the AST can be pretty-printed in a separate step, once the context of each and every JavaScript expression is known.
An AST could also be good for displaying as debug output, minifying, verifying, compiling, translating to multiple programming languages, and doing any other kind of inspection. The format of the AST could also be set up to make it easier for different generated parts to avoid tripping over each other; for instance, there could be scopes for element IDs or a library dependency resolver.
I just finished converting my website's static site generator to Arc and Penknife (a language I've been making), and I'm using this kind of AST approach, if only 'cause HTML ASTs are so simple. :-p I haven't had the need to generate JavaScript or PHP yet--and I don't even use any PHP right now, lol--but I expect to get around to that someday, and at that point things'll get kinda interesting.