Arc Forumnew | comments | leaders | submit | hasenj's commentslogin
2 points by hasenj 5463 days ago | link | parent | on: Reloading .arc files inside the web server

Thanks Evan, nice trick!

Now, can we watch filesystem for changes?

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 point by bogomipz 5463 days ago | link

Just a couple of comments on the watch function:

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:

  (def watch (file-name delegate)
       (let ts mtime.file-name
         (while (is ts mtime.file-name)
                (sleep 2))
         (delegate)))

-----

1 point by hasenj 5463 days ago | link

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.

  (def srvr ()
    (if (no (bound 't*))
      (= t* (thread (asv)))))

  (srvr)

-----

1 point by bogomipz 5461 days ago | link

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.

-----

3 points by hasenj 5461 days ago | link

Well actually, I'm putting the (asv) call in a thread and making it so that reloading the file doesn't restart the server:

  (mac set-once (s v)
       `(if (no:bound ',s)
          (= ,s ,v)))

  (set-once t* (thread:asv))
Watching for changes is not running in a thread, it's just the last line in my "web.arc"

And you're right, the hash-table is not needed at all. That was a premature design.

I actually re-wrote it a bit:

  ; watch the result of (func) for change
  ; and run 'on-change' when value changes
  (def watch-fn (func on-change)
       (let init (func)
           (while (is init (func))
              (sleep 2))
       (on-change)))
  
  ; watch the value-exp expression for changes
  ; and run on-change-exp when it changes
  (mac watch (value-exp on-change-exp)
       `(watch-fn (fn() ,value-exp) (fn() ,on-change-exp)))
  
  (def watch-file (file-name deleg)
       (watch (mtime file-name) (deleg file-name)))
  
  (def auto-reload (file-name)
       (prn "w/auto-reloading " file-name)
       (watch-file file-name load))
  
  (auto-reload "web.arc")

-----


Thanks!

Interestingly, it's smaller than 'pair' even though it does more.

-----

3 points by akkartik 5481 days ago | link

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.

http://www.cs.utexas.edu/users/moore/classes/cs389r/index.ht...

-----

1 point by hasenj 5481 days ago | link

Actually I just realized, 'tuple' uses 'firstn' and 'nthcdr', where as 'pair' sort of inlines these concept and has to deal with nils.

-----

1 point by hasenj 5489 days ago | link | parent | on: 1000 Days

Not even close. The 100 years language is (supposedly) the language people will want to use even in a 100 years from now.

It's not even a 3 years language yet, because well, people are not jumping all over it.

Plus, a 3 year language implies a language with a very short life-span :p

-----

1 point by evanrmurphy 5488 days ago | link

Yes, you're right. (Although the grandfather comment didn't really mean anything. Just silliness.)

-----

2 points by hasenj 5495 days ago | link | parent | on: A better syntax for optional args

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:

  def barebones(*args, **kwargs):
      pass

  (bare-function args kwargs (body))
and, fn would be implemented as a macro on top of that.

-----

2 points by akkartik 5495 days ago | link

Yeah perhaps we should just add dictionary literals to arc: http://news.ycombinator.com/item?id=1804558

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.

Update: prior implementation of dictionary literals in arc: http://hacks.catdancer.ws/table-reader-writer.html. Discussion: http://arclanguage.org/item?id=10678

-----

2 points by aw 5495 days ago | link

Most recent implementation: http://awwx.ws/table-rw3

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.

-----

1 point by zck 5495 days ago | link

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.

-----


> I should release a new version though. News.arc is greatly improved since the last release.

Just curios, do you have a public git repo, or anything like that?

-----

1 point by hasenj 5515 days ago | link | parent | on: Ask: Macros, names, and symbols

ok, now can that be encapsulated into a macro? say a macro called apply-mac-to-list

  (= tag-list '(html title head body))
  (apply-mac-to-list def-tag tag-list)
I managed to come up with this:

  (mac apply-mac (mac-name args)
      (prn:string "applying " mac-name " to " args)
      `(each arg ,args
          (eval `(,',mac-name ,arg))))
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)

-----

1 point by waterhouse 5514 days ago | link

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:

     `(each arg ,args
         (eval `(,',mac-name ,arg))))
  ;Rewrite as:
     (w/uniq garg
        `(each ,garg ,args
            (eval `(,',mac-name ,,garg))))
(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):

  (mac def-tags tags
    `(do ,@(map (fn (name) `(def-tag ,name)) tags)))
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.

Note:

  (let name 'BOB
     `(meh `(ach ,',name)))
  -->
  (meh `(ach ,'BOB))
  -->
  (meh (ach BOB))
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.

-----

1 point by hasenj 5513 days ago | link

thanks for the elaborate explanation :)

> 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.

-----

1 point by aw 5515 days ago | link

The pattern (comma quote comma)

  ,',X
turns out to be common in nested quasiquotation expressions, such as when you have macros defining macros.

See http://repository.readscheme.org/ftp/papers/pepm99/bawden.pd... for a description:

"The value of X will appear as a constant in the intermediate quasiquotation and will thus appear unchanged in the final result."

-----

1 point by hasenj 5517 days ago | link | parent | on: Ask: Macros, names, and symbols

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 ">"))
~~~~

P.S. What's with this?

    arc> (underline:pr "contents")
      <u>contents</u>"</u>"
What's the extra "</u>" at the end? And why shouldn't I (or should I?) worry about it?

-----

3 points by fallintothis 5517 days ago | link

1. Not exactly, but it helps to think of it that way.

  (mac foo (x)
    (list 'bar x))

  (foo abcdef)
is conceptually like

  (eval ((fn (x) (list 'bar x)) 'abcdef)) ; = (eval (list 'bar 'abcdef))
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).

This topic comes up every so often: http://arclanguage.org/item?id=11517.

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:

  (mac defs args
    `(do ,@(map [cons 'def _] (tuples args 3))))
It converts

   (defs f (x) (+ x 1)
         g (y) (- y 1))
into

   (do (def f (x) (+ x 1))
       (def g (y) (- y 1)))
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"
To learn more about macros, my debugger tool might be helpful: http://arclanguage.org/item?id=11806. Let me know if it is!

-----

1 point by hasenj 5516 days ago | link

on a second thought, the anonymous macro is nothing but the manual expansion of the macro :P

-----

1 point by shader 5514 days ago | link

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.

-----

2 points by hasenj 5520 days ago | link | parent | on: Ask: html and javascript in arc

> jQuery('#content')['hide']; // same as jQuery('#content').hide();

Not really the same, it's missing the () from the end to call the function.

Adding another level of parens seems to do the trick:

    (((jQuery "#content") `hide))
results in:

    jQuery('#content')['hide']();
But that style doesn't seem like something I'd want to do.

> jQuery already takes so much of the verbosity out of writing JavaScript that it can feel a bit excessive to try and metaprogram it away any further.

Agreed.

There are still annoyances though, like the anonymous function syntax when combined with function calls and json literals:

    jQuery(".item").each(function(){
              fn_that_takes_json({ id: $(this).id, obj: $(this) }); });
Too messy, and too many places to go wrong: brace in the wrong place, missing comma, missing semi colon, etc.

-----

1 point by evanrmurphy 5520 days ago | link

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.

-----

1 point by hasenj 5519 days ago | link

perhaps a js macro could allow a better syntax?

I don't know if macros can do this,

    (js
        (jQuery "#content"
            (toggle))        ;; translates to: jQuery("#content").toggle();
        (jQuery ".cue"
            (html "done")
            (addClass "t"))) ;; translates to: jQuery(".cue").html("done").addClass("t");

-----

2 points by rocketnia 5518 days ago | link

Here's the start of an approach loosely influenced by that:

  (def jquery-dedot (symbol)
    (when (isa symbol 'sym)
      (zap string symbol)
      (when (begins symbol ".")
        (cut symbol 1))))
  
  (def parse-chain (chain)
    (whenlet (first-method-sym . rest) chain
      (iflet first-method jquery-dedot.first-method-sym
        (let numargs (or (pos jquery-dedot rest) len.rest)
          (let (first-args rest) (split rest numargs)
            (cons (cons first-method first-args) parse-chain.rest)))
        (err:+ "A chain given to 'parse-chain didn't start with a "
               "dot-prefixed method name."))))
  
  (mac jquery (selector . chain)
    (let result `(js-call js-var!jQuery ,selector)
      (each message parse-chain.chain
        (zap [do `(js-send ,_ ,@message)] result))
      result))
  
  (def js-var (name)
    (annotate 'rendered-js
      string.name))
  
  (def js-call (callee . args)
    (annotate 'rendered-js
      (+ "(" tojs.callee "(" (intersperse "," (map tojs args)) "))")))
  
  (def js-send (object method . args)
    (annotate 'rendered-js
      (+ "(" tojs.object "[" tojs.method "]("
         (intersperse "," (map tojs args)) "))")))
  
  (def tojs (value)
    (caselet type-value type.value
      rendered-js  rep.value
      int          string.value
      string       (do (zap [subst "\\\\" "\\" _] value)
                       (zap [subst "\\'" "'" _] value)
                       (zap [subst "<'+'/" "</" _] value)
                       (zap [subst "]]'+'>" "]]>" _] value)
                       (zap [+ "('" _ "')"] value)
                       (tostring:w/instring stream value
                         (whilet char readc.stream
                           (let code int.char
                             (if (<= 32 code 127)
                               pr.char
                               (let hex (coerce code 'string 16)
                                 (pr "\\u")
                                 (repeat (- 4 len.hex) (pr #\0))
                                 pr.hex))))))
        (err:+ "A value of type \"" type-value "\" can't be "
               "translated by 'tojs (yet).")))
In action:

  arc> (jquery "#content" .toggle)
  #(tagged rendered-js "((jQuery(('#content')))[('toggle')]())")
  arc> (jquery ".cue" .html "done" .addClass "t")
  #(tagged rendered-js "(((jQuery(('.cue')))[('html')](('done')))[('addClass')](('t')))")
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.

-----