Arc Forumnew | comments | leaders | submit | palsecam's commentslogin
1 point by palsecam 5412 days ago | link | parent | on: Why does pr return it's first arg?

> Hmm, perhaps it should return the last arg instead, that way I can add strings in front, which seems more common than at the end.

yeah but that would be more costly. Getting the 'car of a linked list is cheap, getting the last element isn't (O(n)).

I think that is reason why it returns the car.

Instead of not returning anything, it returns something. I think this is good. this can be useful sometimes. For instance, (good) C code does that. It's rare to see a "void" function in C, better to instead return some infos, even if you don't see an obvious use of it for the moment.

But returning something should not be expensive. Therefore the 'car.

---

Jazzdev, where is the 'pr behaviour problematic? In my view, if you want it to return 'nil instead, it's basically that you don't care about, that you discard the result. Then getting the 'car changes nothing.

Anyway, akkartik is right, you can use 'disp. But be careful it can only prints one argument, and it takes an optional second one, the stream where to print the value (stdout by default), where 'pr takes any nb of args (and displays them to stdout).

Or I suppose you know this, you can do: (do (pr arg1...argN) nil).

Or even, if you're going to use it a lot: (def prnil args (apply pr args) nil)

---

In the same "please return something" philosophy, it could be good if 'each and co actually return something else than 'nil. Sometimes it was a problem for me, I used a 'each construct in a 'if test or something, and the test (obviously) failed.

'each returns the iterated table if it is called with "expr" being a table. Maybe it should return the iterated list when called with a list. (Or at least there should be coherence: the other solution is to make 'each always nil. Currently, the situation is confusing.)

Another idea is to make the iteration constructs ('for and co, also) return t. Then they could be used w/out problem in 'if. It makes sense: "the job is done" => t. But for 'each, returning "expr" makes more sense.

Another idea is to keep returning nil, but I find this poor. That's basically killing the "everything is an expression that returns something [implicitely: useful]". That's just a bit better than Scheme #<void> (which is a pure abomination).

Similarly, Anarki does this I think, and so does my ac.scm, but a minimum is to make 'system returns t if success, nil otherwise (and a better solution might be to return the actual exit code of the proc). Don't make nil the new #<void>.

---

BTW, the other day I cried when I typed "var a = 42" in my browser Javascript console, and saw no result line. An assignement is "undefined" in Javascript. No result. It's a statement, not an expression. This is just stupid. I really was disappointed. I lose some time because I wonder why there were no result line, this is stunning in a REPL. They told me Javascript is Lisp in C clothing, my ass.

-----

3 points by waterhouse 5410 days ago | link

Given that you already iterate through the list, it can be made cheap to get the last item:

  (def prl args ;returns last arg
    (let u args
      (while cdr.u
        (pr car.u)
        (zap cdr u))
      (pr car.u)))
  arc> (prl 1 2 3)
  1233
Even though 'u points to the list 'args, I can modify 'u to point to another part of the list, and the list itself is not modified. I thus traverse the list only once here.

Also, note that the REPL prints the return value on the same line as whatever other output. I think this is annoying. Common Lisp has a 'fresh-line procedure that prints a newline to an output-stream if and only if at least one character has been printed to that stream and the last character printed was not a newline. It would be nice to use that in the toplevel procedure.

Having written that, I looked at the PLT docs and figured out how to at least tell whether nothing has been written to an output-port since the last time you checked, and I hacked the toplevel function in ac.scm to print a newline when the expression printed something, whether or not that something ended with a newline. I'm not sure whether I like this better:

  arc> "ach"
  "ach"
  arc> (pr "ach")
  ach
  "ach"
  arc> (prn "ach")
  ach
  
  "ach"
Changes in ac.scm (I haven't learned to use diff, so I'll record them like this):

  ;Relevant part of resulting definition of tl2 in ac.scm:
        (if (eqv? expr ':a)
            'done
            (let ((n (next-char-place))
                  (val (arc-eval expr)))
              (if (< n (next-char-place))
                  (newline))
              (write (ac-denil val))

  ;Then add this:
  (define (next-char-place)
    (let-values (((a b n) (port-next-location (current-output-port))))
      n))

-----

1 point by jazzdev 5411 days ago | link

> Jazzdev, where is the 'pr behaviour problematic?

I'm writing an HTML package. I'd like it to work as follows:

  (html
    (body
      (somesetup)
      "Some text"
      (pr "Some more stuff")
  ))
I'm experimenting with the idea that calling pr shouldn't be necessary, so the tag expansion eval's everything and if it gets back something non-nil then it pr's it. I'd like to still allow pr to work also, but since it returns non-nil then some things get output twice.

I want the user of the HTML package to use standard stuff, so having prnil is awkward. Allowing pr inside these tags isn't necessary. I'm just playing around and trying to see what feels natural to use.

Using pr to print something without changing the semantics of a functional program makes sense in theory, but in practice it doesn't see useful. Have you ever actually done it?

-----

1 point by conanite 5410 days ago | link

One way to make this possible is to have your 'html macro (I'm assuming it's a macro) unhygienically provide a lexical binding for 'pr for its body

  (mac html body
    `(do (pr "<html>")
         (let pr prnil ,@body)
         (pr "</html>")))
That way your callers can use 'pr like they're used to, but secretly and subversively you have them calling your own private pr function.

-----

1 point by jazzdev 5410 days ago | link

That's a clever way to do it. Thanks.

-----

1 point by palsecam 5410 days ago | link

> I'm experimenting with the idea that calling pr shouldn't be necessary

You may like that idea: http://arclanguage.org/item?id=11048

-----

1 point by jazzdev 5410 days ago | link

Yep. I'm trying to do the same thing. I need to follow this forum more regularly.

-----

1 point by akkartik 5412 days ago | link

Hmm, doesn't each return the entire list that was being iterated over? I'll look out for an example, but one of the ways I iterate over stuff often ends up stuffing the repl terminal with huge screenfuls of output if I forget my do1 nil..

-----

2 points by palsecam 5412 days ago | link

> Hmm, doesn't each return the entire list that was being iterated over?

No:

  arc> (each x '(1 2 3) (prn x))
  1
  2
  3
  nil
But you're right, the iteration macros are mainly used while testing in the REPL, while it's often a code smell to see 'each or 'for in the final file, in my XP.

So yes maybe the best solution is to keep them returning nil, to not clutter the screen.

But a better one would be a better REPL that collapse the output if it gets too big. This would protect from a whole class of "mistakes". I use a web-based REPL for administrating Arc powered websites that does this (like the one at dabuttonfactory.com:8080, only better). There were a thread on HN lately about a Python shell that does this too (collapsing output), unfortunately I don't remember its name.

"Improve the tools, improve the language", and not "keep the tools crappy, make the language crappier to fit the existing tools".

Current REPLs are very stupid when you think about it, and lots could be made to improve them. I mean, my Arc REPL in a terminal is not even as good as my Unix shell REPL. And I don't consider my Unix shell REPL awesome anyway.

Rah I should find you the link about the Python shell or clean the code (but it's JS anyway :-/) and show you my web REPL. Both do a bunch of trivial things that greatly improves the usability. For instance, they work on an "expression" level, not a "line" one. If you enter a multi-lines fn definition, then hit the up arrow, the whole definition is copied back in the input area, not just the last line. Mine also works with the mouse: I can click on a previous definition to copy it back in the input area, and this is much better for some cases that having to hit <UP> 20 times. Etc, etc.

Frankly, if "dynamic development with a REPL" means fighting with rlwrap, then I can be way more productive with editing a file in Emacs and a compile stage. Compiling is not that slow nowadays. Or have a look at Golang: it can even be blazing fast. Even Slime (for Emacs) is not really that better IMO.

There are even drawbacks with a REPL: you try things in it, but when you're done trying, you have to copy/paste the code to the editor window: you lose some time you would not have wasted with a static language! Big big hacks should be done in this area. Current REPLs are not good enough. That's the reason I know no Python guys that actually use `python' as a REPL. Us Lispers use one, but I guess it's mainly because we are used to it (i.e: Lisps tutorial begin by telling you to start a REPL, Python ones to start a text editor).

The ideas of making them less awkward (i.e: don't be terminal/line-oriented) and/or usable to some extend with a mouse are just little steps (but a giant leap in usability IMO). I hope some hero will come one day and will find a way to reunify the REPL and the editor window or something. Currently it's too much of a pain. How can I say, for instance, "OK, this redef I tried here in the REPL is better than the one in the source file, replace it"?

-----

2 points by shader 5411 days ago | link

Getting better repl tools, and language integration with the repl, are two of the main things I'm interested with in arc.

I've only gotten as far as 'src and 'ppr, but I was hoping to get to the point where arc automatically documented it's current code (i.e. the stuff actually running) and made it visible through both web and repl interfaces. In theory, 'src could be used to solve the repl -> editor -> file problem. If arc kept track of the original source files, and the current 'src associated with each of those functions, then it could possibly display the diff and even update the files if you wanted it to. Heck, why not give it a git interface, so that you can add and commit changes from the repl as well.

The reason I like arc is that the language is very easy to change, and I'm hoping to change it to make it the language that is most "aware" of itself, and its current source code.

Making a better repl is certainly something I'm interested in.

-----

2 points by akkartik 5411 days ago | link

Yeah the python shell was dreampie: http://akkartik.name/blog/2010-02-21-19-21-42-soc

-----

1 point by palsecam 5410 days ago | link

Yes this is it, thank you.

-----

1 point by akkartik 5402 days ago | link

"Hmm, doesn't each return the entire list that was being iterated over?"

"No:"

Ah, I wasn't going crazy after all!

  arc> (each (a b) (obj 1 2 3 4) a)
  #hash((3 . 4) (1 . 2))
So each returns the iteratee when it's a table. Looking at the source it's just outsourcing tables to maptable. I'm going to change it to return nil.

-----

1 point by pmarin 5411 days ago | link

Try 9term. you can edit the text buffer and resend it to arc. You will need to read carefully the man page if you are not familiar with Plan 9 tools.

-----

1 point by gus_massa 5409 days ago | link

I think that pr should return the last value, because to be consistent with other operators for multiple expressions. For example:

  (do 1 2 3) => 3
  (pr 1 2 3) => 1
  ((fn() 1 2 3)) => 3

-----


No problem here. I repeated the 'repeat loop 3 times.

  $ lsb_release -a
  [...]
  Description:	Ubuntu 9.10
  Release:	9.10
  Codename:	karmic
  $ mzscheme -v
  Welcome to MzScheme v4.2.1 [3m], Copyright (c) 2004-2009 PLT Scheme Inc.
But bugs related to atomicity exist anyway, this is certain. The thread/atomicity stuff is a subtle mess.

Threads are never a solution. Message-passing / shared-nothing threads maybe, event-based maybe, something else maybe. But traditional "à la Java" threads have prouved to be a bad idea.

Why are threads in Arc, after all? For srv.arc. Which would do better w/ an event-based architecture.

And the GIL. Gosh. Python is learning the hard way how a GIL is painful and should probably be avoided from the start.

BTW:

  arc> (= var 0  tbl (obj var 0))
  #hash((var . 0))
  arc> (repeat 50000 (thread (++ var) (++ tbl!var)))
  nil
  arc> var
  50000
  arc> tbl!var
  50000
  ; OK, the above is normal and expected
  ; Now, let's sleep for a random time in each thread before to '++
  arc> (= var 0  tbl (obj var 0))
  #hash((var . 0))
  arc> (repeat 50000 (thread (sleep:/ (rand 40) (inc:rand 50)) (++ var) (++ tbl!var)))
  nil
  arc> var
  49817
  arc> tbl!var
  50047
  ; WTF?!
Huh I'm surprised, I knew 'assign wasn't atomic, but I thought it was OK for 'sref ('++ expands to '= which expands to a call to 'sref but in a 'atwith expression). Seems not. Is my test bogus somehow?

----

Some people, when confronted with a problem, think "I know, I will use threads." Now they have two problems.

-----

1 point by garply 5409 days ago | link

Died for me on Arch Linux too:

$ mzscheme --version Welcome to MzScheme v4.2 [3m], Copyright (c) 2004-2009 PLT Scheme Inc.

Also, I second the notion that shared-memory threads are a bad idea. I really like Termite's message-passing model.

-----

1 point by aw 5409 days ago | link

Died for me on Arch Linux too

Which test were you running, akkartik's or palsecam's?

-----

1 point by garply 5409 days ago | link

akkartik's code died on me with the first time I ran it, but I haven't been able to reproduce it.

This is what happens with palsecam's code:

arc> (= var 0 tbl (obj var 0))

#hash((var . 0))

arc> (repeat 50000 (thread (++ var) (++ tbl!var)))

nil

arc> var

49999

arc> tbl!var

50000

-----

1 point by palsecam 5409 days ago | link

  arc> var
  49999
is strange, but the code you tried is not the one demonstarted a bug. You should 'sleep in the threads (i.e: the second example in my comment).

-----

1 point by garply 5408 days ago | link

When I do the second example, I get results similar to yours.

-----

1 point by akkartik 5410 days ago | link

Did you really run that over 10 hours?

Update: I tried your test with lower sleep intervals and didn't see the error (sleep:/ (rand 40) (inc:rand 5000))

-----

2 points by palsecam 5410 days ago | link

> Did you really run that over 10 hours?

I never say I did.

  arc> (= var 0  tbl (obj var 0))
  #hash((var . 0))
  arc> (time:repeat 50000 (thread (sleep:/ (rand 40) (inc:rand 50)) (++ var) (++ tbl!var)))
  time: 9173 msec.  ; <-- not 10 hours...
  nil
  arc> var
  48152
  arc> tbl!var
  32039
> I tried your test with lower sleep intervals and didn't see the error

Even w/ lower intervals, it is buggy on my computer:

  arc> (= var 0  tbl (obj var 0))
  #hash((var . 0))
  arc> (time:repeat 50000 (thread (sleep:/ (rand 40) (inc:rand 5000)) (++ var) (++ tbl!var)))
  time: 5391 msec.
  nil
  arc> tbl!var
  49849
  arc> var
  49859
(Running on the Ubuntu/MzScheme combo described in previous comment, and on plain vanilla Arc 3.1 (ycombinator.com/arc/arc3.1.tar). Runned ~10 times, each time the results are different but never 50000)

-----

1 point by akkartik 5410 days ago | link

Yes, reproduced on ubuntu/mz4.2.4 (sorry I was making stupid mistakes last night when I tried it out). On snow leopard/mz4.2.2 the tbl!var is always at 50k, but the global var is always lower. I've seen it as low as 43866.

-----

1 point by akkartik 5412 days ago | link

Interesting. But there's no threads in the queue issue :(

-----

1 point by palsecam 5412 days ago | link

yes I know, my reply was actually more about the comment in arc.arc, sorry :-)

-----


Here is a "plugin" example to handle the kind of "web best practises" I respect, those that make it faster for my customers.

  ;;; web-static.arc: module to deal with static files (CSS/JS)
  ; designed to leverage a reverse proxy to serve the files when in production

  ; currently specific to my own needs, will certainly always be.
  ; main requirements are: eternal caching when possible, CSS/JS 
  ; minification, ability to correctly handle external files we can't
  ; easily control (think images url in CSS), ** minimal overhead **
  ; (i.e: code as fucking simple as fucking possible)

  ; CSS/JS minification: we use the YUI compressor and put the
  ; files in minified-dir*.  nginx is told to look first
  ; in this directory and fallback to static-dir* if not found 
  ; (for the files "out of control")
  ;
  ; eternal caching: we set a query string "?<mtime_of_file>", and told
  ; nginx to inform the client to cache this URL for 1 year
  ; (1 year is max allowed by RFC and anyway sufficient)
  ;
  ; (wipe testing*) to activate minification and query string.
  ; doesn't matter if you do this while not being actually behind nginx,
  ; nothing will break, httpd.arc is still serving the static files correctly

  (= static-dir*          "res/static/"  
     minified-dir*  	"res/minified/"
     static-path*      	"/static/"     ; URL (not filesystem) path
     code-compress-prog*  "yuicompress"  ; sh wrapper around yuicompressor.jar
     testing*	        t)

  (def sendfile (fname (o mt (mimetype fname)))
    (resphead http-ok+ (copy httpd-hds* 'Content-Type mt))  
    (prfile fname))

  (register-path (string "/" static-path* "/*")  ; never reached in production
     (fn (req file)
       (aif (file-exists (string static-dir* "/" file))
            (sendfile it)
            (resp-err))))

  (def static-url (fname) 
    (string static-path* fname
      (when no.testing*
        (+ "?" (mtime (compress-ifstale (+ static-dir* "/" fname)
	      	    		        (+ minified-dir* "/" fname)))))))


  (defs csss (fname)  (css:static-url fname)
        jss  (fname)  (js:static-url fname))


  (def compress-codefile (fsrc fdest)
    (ensure-dir:dirname fdest)
    (system (+ code-compress-prog* " " fsrc ">" fdest)))

  (def compress-ifstale (fsrc fdest)
    (when (and (in (file-ext fsrc) 'js 'css)
    	       (or (~file-exists fdest) (> (mtime fsrc) (mtime fdest))))
      (compress-codefile fsrc fdest))
    fdest)

  (defmemo compress-code (str (o type 'js))
    (w/tmpname tmpf
      (w/outfile s tmpf (disp str s))
      (out-from code-compress-prog* " --type " type " < " tmpf)))

  (with (_ijs ijs  _icss icss)  ; redef web.arc ones
    (defs ijs  (str)  (_ijs (if testing* str (compress-code str)))
    	  icss (str)  (_icss (if testing* str (compress-code str 'css))))
  )


  ;; todo: 
  ; * X-Accel-Redir in 'sendfile if behind nginx.
  ;   heuristic: look if X-Real-IP present.  or make the proxy pass
  ;   a header with its name to be more correct (X-Forwarded-By)
  ;
  ; * img-compress-prog* (`optipng')?
  ;
  ; * a clean way to do the call to `yuicompress' asynchronously
  ;
  ; * gzip here to not have nginx do it on-the-fly each time?  not sure
  ;   if the gain is that valuable
  ;
  ; * 'compress-code[...] bad names?
  ;
  ; * use GG Closure compiler and not YUI, use its REST API, and therefore
  ; be obliged to make it asynchronous
  ; 
  ; * hash instead of mtime maybe.
  ; 
  ; * like for web.arc, '=once macro or init procedure so that one can use a !=
  ; path without having to change the file.

'mtime, 'file-ext 'mimetype are defined somewhere else. 'mtime is just calling the `stat' program via 'system. I don't have access to the file they're defined in right now (they are in a "files.arc" file) but I'll post it next week.

Nginx config sample to use this with:

  root	/home/<user>/res/;

  rewrite "/static/(.*)" "/minified/$1" break;

  location /minified/ {
     internal;

     if (!-f $request_filename) {
        rewrite "/minified/(.*)" "/static/$1" break;
     }

     if ($query_string) {
     	expires	+1y;
     }	   
  }

  location /static/ {
     internal;
  }
----

Obviously, using a reverse proxy makes the need of 'setuid irrelevant (it is such a low-level syscall anyway. even plain old unix daemons should use the daemontools and don't do this by themselves). Nginx could be made to keep-alive and gzip, which are huge perf wins. Not serving the static files by the app server is so obvious, even news.ycombinator.com does this know.

The manual "wait 30 seconds, then kill the 'slow' client" handling of srv.arc is a crappy solution (but the crappy threading model asks for it): sometimes my wifi connection is so slow, I couldn't finish a POST to this forum (yes it happened for real, I should retried each POST several times). A reverse-proxy, by buffering and handling slow clients in the good manner (i.e: not killing them brutally: if they don't write for some time, it's OK it's just an idle fd in the select() poll) removes this problem.

----

Old version of web.arc (then called wf.arc) that does session and login handling: http://pastebin.com/3amqH2h8

I'll try to post an example of a login procedure as I do it with this combo next week, but like for "files.arc", I don't have access to it right now.

----

Clickable links: http.arc: http://pastebin.com/jiXSX8yV , web.arc: http://pastebin.com/9GmhRWqc

----

An nginx basic config file for proxying to an http/web.arc powered app server (add the previous sample in the server block if you use web-static.arc):

  server {
    listen           example.com:80;

    location / {
      access_log  /var/log/nginx/examplecom-access.log;
      proxy_pass  http://localhost:8080;
      proxy_set_header        X-Real-IP  $remote_addr;
      proxy_pass_header       Server;
    }
  }

-----

4 points by palsecam 5414 days ago | link

files.arc (where 'mtime and co are defined ; web-static.arc depends on it) is available at http://pastebin.com/YGNZA6SG

----

An example of a login procedure, taken from one of my websites:

  (def new-login-handler ((o redir "/"))
    [let user (arg _ "user")
      (redirect 
        (if (login user (arg _ "pwd"))
    	    redir
	    (opurl:new-login-op redir "Bad credentials" user)))])

  (def login-page (req (o redir "/") (o msg) (o userval))
    (page req "Login"
      (tag (p class "err") (prt msg))
      (fnform (new-login-handler redir)
        (lblinp "Username: " "user" "text" userval)
        (br)
        (lblinp "Password: " "pwd" "password")
        (br2)
        (but "login"))
      (ijs "document.getElementById('user').focus();")))

  (def new-login-op ((o redir "/") (o msg) (o userval))
    (newop [login-page _ redir msg userval]))

  (defpath /login (req)  (login-page req))
'page is a macro on top of 'htmlpage to create a page with the look&feel of the project site.

'login is something like: (goodcrypt pwd (get-passwd-of-user user)) ('goodcrypt is in files.arc).

"op" means "operation" in my lexicon, and is for /x/... paths, stateful actions. I know, this is confusing, it's not the same notion than in srv.arc ('defop). But in my mind, even when it comes to webapps, the default is statelessness and resources-oriented, not stateful operations. People (me the first) basically only care about "resources" (informations, content), be it a rich Ajax-full webapp or a basic HTML page, anyway.

In srv.arc, the "operations" system is +/- the 'fns* / 'fnids* / 'flink / etc. stuff.

----

The complete nginx config file for the above project site:

  ##
  # Nginx configuration file for <proj> on localhost.
  #
  # Install with:
  # ln -s /home/<PROJ>/res/nginx-localsite.conf /etc/nginx/sites-enabled/<PROJ>
  ##

  server {
       listen		8030;

       root		/home/<PROJ>/res/;
       error_log	/var/log/nginx/<PROJ>-error.log;
       access_log	off;

       
       rewrite "/static/(.*)" "/minified/$1" break;

       location /minified/ {
       		internal;

		if (!-f $request_filename) {
		   rewrite "/minified/(.*)" "/static/$1" break;
		}

		if ($query_string) {
             	   expires		+1y;
		}
       }

       location /static/ {
             	internal;
       }

       location ~ "^/(favicon\.ico|robots\.txt)$" {
		expires	       		+2M;
       }


       location / {
		access_log		/var/log/nginx/<PROJ>-access.log;
       		proxy_pass		http://localhost:8020;
		proxy_set_header	X-Real-IP  $remote_addr;
		proxy_pass_header	Server;
       }
  }
Gzipping and other general config directives are defined in the main /etc/nginx/nginx.conf file and are invisibly "inherited" here.

I don't log accesses to /static/* and /favicon.ico / /robots.txt, but I do log accesses to the rest of the website (access_log directive in the "location /" block).

Nginx doc @ http://wiki.nginx.org/NginxModules

----

A comment in web.arc mentions scheme2js, more infos here: http://www-sop.inria.fr/mimosa/scheme2js/ It's a scheme to javascript compiler, which is smart enough to substitute the TCO with the use of a while loop or a trampoline (depending of the case), and that can do a bunch of other optimizations too (like inlining calls to +).

It is used in the HOP project (http://hop.inria.fr/) which is a framework to develop rich webapps (i.e: w/ a rich javascript-backed client GUI and w/ Ajax) using Scheme for the server and the client. It is quite impressive (the website is a demo). To Thaddeus: wtf would you want to compile Arc to CoffeeScript and not to raw Javascript directly?! If you decide to write an Arc to JS compiler, be sure to check out scheme2js!

-----

2 points by thaddeus 5414 days ago | link

-> why compile Arc to CoffeeScript?

good question. scheme 2js looks better.

BTW Thanks for posting all this information! This gives me more to sink my teeth into. :)

-----


Did you also apply the patch? (http://arclanguage.org/item?id=10345)

Because I just tried, and something like:

  [script launch-asv.arc]
  #!/usr/bin/env arc
  (asv)
  [script end] 

  $ chmod +x launch-asv.arc && ./launch-asv.arc &
  [1] 2439
  $  # there is no "serving on 8080" message, but...
... go to http://localhost:8080/: "it's alive". So it's OK.

Anyway yes, Lisp is special, and the use of `screen' or the like may be better.

(n.b: if you don't launch the script in the background (no '&'), you see the message, and get no prompt. But you need to keep the terminal open.)

-----

1 point by user2 5488 days ago | link

No I hadn't applied the patch. I did the following:

[cmdline start]

linuxbox arc3 # patch -p1 < ../arc-unix.patch (Stripping trailing CRs from patch.) patching file ac.scm Hunk #1 succeeded at 1112 (offset -15 lines). (Stripping trailing CRs from patch.) patching file as.scm [cmdline end]

The patch did seem to apply but not perfectly.

Now I get:

[cmdline start]

linuxbox arc3 # ./run-hacker-news.arc reference to undefined identifier: empty?

[cmdline end]

which has (asv) in it just like your example script. If I call arc command script I get the same error. This is arc3.1.tar from ycombinator.com

Also what does (asv) do instead of (load "news.arc") (nsv)? I don't know lisp but will surely learn if I can get this running for starters.

How are generally other people running it detached? I didn't think it would require this many steps.

-----

1 point by palsecam 5488 days ago | link

> reference to undefined identifier: empty?

Strange, which version of mzscheme do you use (`mzscheme -v')? 'empty? is in the MzScheme base "scheme" module for me (MzScheme 4.2.1)!

Anyway, 'empty? is called only in as.scm, and you can actually leave this file unchanged for your case (I think). Just patch ac.scm.

> Also what does (asv) do instead of (load "news.arc") (nsv)?

'asv is used to start the "Appplication server" defined in "app.arc". App.arc is generic, and news.arc use/require it. I use it in the example to see if maybe it was a problem w/ news.arc specifically (and because I feel lazy about typing "(load ...)": app.arc is in the Arc "stdlib" (see the file "libs.arc"), and so is loaded by default).

'nsv is defined like this, in "news.arc":

   (def nsv ((o port 8080))
     [...]        ; do news-specific init stuff...
     (asv port))  ; relay to 'asv, which itself relays to 'serve (defined in srv.arc)
The "sv" in "asv" / "nsv" is for "serve".

> How are generally other people running it detached?

They don't: Arc is different :-D

---

Ah also, to start the news server in the background, no need of a script, you could use:

  $ echo '(nsv)' | arc news.arc - &
But assuming you use a slightly improved version of the (full) patch this time:

  in as.scm:

  -      (for-each (lambda (f) (aload f)) args)  ; ...execute them!
  +	 (for-each (lambda (f) (if (string=? f "-") (tl) (aload f))) args)
But you still need to leave the terminal opened. To fix that, I don't know you may use some Unix daemon-ization magic. It becomes specific to your needs. Are you trying to use the news.arc forum on a (distant) server?

Once again, consider `screen'. Arc has no Unix signal handling for instance, so an "Arc daemon" would not be "powerful". The only possible interaction would be to kill it (via `kill'). Better keep an eye on it, i.e: keep a REPL opened to manage your forum.

---

Have a look at http://arclanguage.org/formatdoc

-----

2 points by user2 5488 days ago | link

Thanks for the info.

I am using mzscheme 372 as described here: http://arclanguage.org/install

I tried:

  $ echo '(nsv)' | arc news.arc - &
I got repetitively:

  arc> Error: "Bad object in expression #<eof>"
  arc> Error: "Bad object in expression #<eof>"
  ...
I am using a remote vps, hence I would like to start the service and quit the server. To be honest, a web service without daemonizing does not sound very intuitive. When I say I need to stay connected to the server to keep the service alive, forums users say "Ah man, what kind of a computer engineer are you?" ;-) I think something like this must be done: http://www.itp.uzh.ch/~dpotter/howto/daemonize

Do you think if I get the latest version of mzscheme your patch it will work? What about the 15 lines offset hunk? Patch didn't seem to apply perfectly.

-----

2 points by palsecam 5488 days ago | link

Yes, try the latest version of MzScheme. Seeing "bad object in expression #<eof>", I'm not even sure ac.scm is patched correctly.

http://arclanguage.org/install has not been updated, but the big feature of the 3.1 version is that it can runs on recent (> 4.1) MzScheme. See http://arclanguage.org/item?id=10254.

Edit: oh yes, maybe you use Arc 3! The current version is Arc 3.1: see the previous link. Direct download: http://ycombinator.com/arc/arc3.1.tar. The patch is to be used with the 3.1!

> To be honest, a web service without daemonizing does not sound very intuitive. When I say I need to stay connected to the server to keep the service alive, forums users say "Ah man, what kind of a computer engineer are you?"

I agree it's not intuitive, but the use of `screen' + long-lived REPL to manage the forum can actually be seen as quite smart:

  $ screen arc
  arc> (load "news.arc")
  arc> (thread:nsv)   ; start it in a new thread, to not block the REPL
  arc> ^A d  ; CTRL-A d, to detach the `screen'
  [detached]
Your forum is started and your screen session detached. You can safely logout and exit your SSH session at this point.

At any time, you can login back to the server and do:

  $ screen -r   # resume
  arc> ; you're in front of the forum REPL
  arc> requests*  ; ultra basic analytics: show the number of requests served
  161803
  arc> (change-some-settings)  ; you get the idea
And if you need to stop the server and exit Arc:

  arc> (exit)  ; or just ^D if you have applied the patch
  [screen is terminating]
  $ 
One thing that is missing is there is no watchdog, no automatic restart in case of failure (I mean big failure, like: the Arc process died). Sure, it's not as solid as the daemontools (http://cr.yp.to/daemontools.html), but it should suffice. srv.arc has some kind of protection against flood, handler threads that would get mad, etc.

If you really want to just echo '(nsv)' | arc news.arc -, then use version 3.1, the patch and a small shell or Perl wrapper script to daemonize the Arc process. Some fork + redirect standard ports + setsid magic, then exec the Arc process.

In Perl, from `perldoc perlipc':

      use POSIX 'setsid';

      sub daemonize {
          # chdir '/'               or die "Can't chdir to /: $!";
          open STDIN, '/dev/null' or die "Can't read /dev/null: $!";
          open STDOUT, '>/dev/null'
                                  or die "Can't write to /dev/null: $!";
          defined(my $pid = fork) or die "Can't fork: $!";
          exit if $pid;
          setsid                  or die "Can't start a new session: $!";
          open STDERR, '>&STDOUT' or die "Can't dup stdout: $!";
      }
Or play it hardcore and use the daemontools :-P!

-----

1 point by user2 5483 days ago | link

Thanks, I got it working. For reference, particularly your screen trick worked very well over ssh. -L option also helps if screen dies on you, it creates a log file in the current dir that you can check and see what went wrong.

Next, I believe I need to learn a bit of arc to see how I can make changes on the website.

-----

1 point by user2 5488 days ago | link

OK thanks, let me try these.

By the way, somehow I managed to log out and the server still seems to be up. I didn't use screen or anything. I will investigate how it worked.

-----

1 point by akkartik 5488 days ago | link

Sometimes interactive processes will 'go rogue' when you ctrl-c them. Run ps and kill to get rid of it.

-----


Disclaimer: self-plug.

You may be interested by this thread: http://arclanguage.org/item?id=10344 - "Arc usable on Unix (include ability to run scripts!)"

-----

1 point by mk 5514 days ago | link

Cool. It looks like they pulled some of your patch into anarki, so maybe I should take a look at it.

-----

1 point by palsecam 5516 days ago | link | parent | on: "as" macro for a more readable "coerce"

Very good idea. Thanks. It's in the spirit of 'in, which I like a lot, too.

-----


Super nice post! Detailed and well-written.

eight looks interesting. But it took me 2 minutes to understand it was the name of the language. Maybe write it capitalized, "Eight". "eight" is quite confusing.

"Everything" is a closure, the 'leak idea, etc.: cool, interesting stuff!

-----

1 point by diiq 5521 days ago | link

Thank you.

I'm still waffling on 'Eight' or 'eight' --- in my notes I write it '8', which is even less clear ;)

-----

1 point by rocketnia 5521 days ago | link

Whatever you call the language, there should be an editor scriptable in it called ACHT: Another Curvy Hacking Tool. XD

-----

2 points by diiq 5521 days ago | link

Hah! Then code snippets can be 'Pieces of Eight' and libraries must be Reales.

And the qualitative feeling of programming in this language? Fullness. 'Cause you just Eight.

-----

3 points by palsecam 5523 days ago | link | parent | on: Macros are a menace

Very interesting points about macros drawbacks.

pg is a well-known advocate of macros, and I suppose he influenced a lot of people in this opinion. I do like macros, but it's true, too many times you're doing it wrong if you're defining a macro.

Richard Gabriel (author of Worse is better, Lucid founder, etc.: in short: Lisp expert) said:

   "Macros encourage people who are not good at
   language design to do something equivalent to language design,
   using tools that don't help, and with effects that are too
   powerful. This makes code unreadable to people joining later and
   for the authors after time has passed. Well designed macros are
   well documented, but this doesn't happen much."
Think about it.

-----

1 point by conanite 5523 days ago | link

He makes some interesting points, but macros are like everything else that's useful (fire, nuclear power ...), they can be abused. It's fair to warn people about macros, just as people are warned repeatedly about multi-threading; but it doesn't mean macros, or multi-threading, are wrong or bad. Having a few years of practicing "Don't Repeat Yourself" in java, perhaps, you have finally hit the limit, and realise that removing more duplication means a net increase in volume of code. Arc and assembly are the only non-condescending languages I know, and I love the freedom of writing unfettered code in arc.

I notice that arc's macros are mostly very short and to the point, and are strictly hygienic even though the core language doesn't impose it. As for me, I'm still learning this discipline, just as it took me a while to understand why short methods and small classes are "better".

-----

2 points by akkartik 5523 days ago | link

Interesting. I suspect I overuse macros; I try consciously to improve by constraining the surface area of the program that lies under defmacro, but there's a long way to go.

One pattern: I often used to use defmacro just to get call-by-reference. Most of the time I can get by with just primitives.

-----

2 points by palsecam 5523 days ago | link | parent | on: "Magical" on-disk persistence

(disclaimer: I'll nitpick in this post, and give some free, personal, critiscm, but akkartik asked me for comment by email, so this is what I'll do)

> My current approach is more efficient in I/O: registries for load and save functions, and a thread that periodically saves everything.

I think my improved solution (http://arclanguage.org/item?id=10696, just below) is actually the most I/O effective. Yours will rewrite the file(s) every 10 seconds, regarless of if the content has actually been modified. But I like the global thread idea.

In 'is-persisted:

  (fwrite "snapshot.tmp" ,var)
  (errsafe:mvfile "snapshot.tmp" (snapshot-name ,var)))
You're a good system programmer to write a temp file then rename it. But Arc does this for you when you call 'writefile.

This brings me to: your code is difficult to read. You're defining, IMO, way too many clones/variants of existing Arc functions ('tablist2, 'listtab2, 'fwrite, etc...). Maybe they're actually needed for your case, but maybe take the time to dive into Arc source, and see if something existing is not fitting your needs. If you really need to define them, please include a short docstring or a comment to explain why '...2 is needed.

'alist?: ahhh I'm like you, I like the '?' convention for predicates, but Arc doesn't follow it, and use the ambiguous 'a... convention instead. IMO, it's better to follow the convention, even if bad, because otherwise, again, it makes the code inconsistent, difficult to read by others. 'alist? is a name too close to the existing 'alist and this is quite confusing. fallintothis' suggestion, 'an-alist is a good one IMO. Still, I laughed reading it, because well, that's where the 'a... convention brings us. 'an-a... LOL.

Lisp is so awesome to let you use "special" characters in identifiers, and predicates are not often used, and '?' is like '=' it's easily parsable, I can't understand the use of 'a... But this is maybe just personal taste. The ...[-]p convention of CL is worse than '?' but at least less ambiguous. Why is 'afn not [isa _ 'fn]? What about the anaphoric stuff, which also use 'a... etc. And of course, english-centric convention, which is worse than latin-centric when you can choose between the two.

In general: interesting idea and implementation. Still, I prefer either 'diskvar/'disktable because it's in vanilla Arc, either my 'db/'ptable because the code is, well I wrote it so I can't objectively judge but, easier, shorter to read, and it doesn't use macros. And 'db/'ptable is transparent, where your code is like 'disktable: half-transparent (still need to 'todisk/'save-state).

Hope it was useful.

-----

2 points by akkartik 5523 days ago | link

Thanks palsecam, I found it most useful. The issues with 'a naming are compelling. Good to know writefile implicitly does write+rename.

I like your alternative; weird that our comments kinda crossed (sorry I missed it before). I don't yet understand how it avoids unnecessary writes. I didn't appreciate how foundational sref is.

I left a link to an earlier discussion about the ..2 variants :) (http://arclanguage.org/item?id=10677) I need read-nested-table and write-nested-table because read-table and write-table don't handle nested tables. fread/fwrite is my attempt at a unified interface for pickling arbitrary objects. Ideally read/write would take care of that.

-----

1 point by palsecam 5523 days ago | link

> I don't yet understand how it avoids unnecessary writes.

'ptable/'db? Because it only calls 'save-table when 'sref is called. 'sref is called when you modify/delete/create an element in the table.

  arc> (= sometbl!somekey 42)   ; 'sref is called, so is 'save-table (not immediately if using 'db)
So, if your table is not changed for 10 minutes, 'save-table is not called during these 10 minutes. See?

> http://arclanguage.org/item?id=10677 / read-nested-table and write-nested-table

OK, I re-read it and I can understand now. Thanks. An unified interface is a good idea.

I just know about it but can't remember its purpose, but 'load-tables (notice the final "s") exists. Maybe it's here for nested tables [edit after looking at http://arcfn.com/doc/table.html: no it's not].

-----

2 points by akkartik 5523 days ago | link

Ah, I don't know why it took me so long to realize how http://arclanguage.org/item?id=10696 works.

Every sref calls the corresponding buffer-exec savefn. The first call to buffer-exec in an interval spawns a thread to save after the interval.

I think I just had my head stuck in the 'iterative' way and had to twist a little to return to the event-driven approach.

Reminds me of the time I used at to simulate cron.

-----

2 points by palsecam 5523 days ago | link | parent | on: "Magical" on-disk persistence

Improved version with a 'smarter-save-table. I changed the name of 'ptable to 'db in reference to the first drafts of Arc + it's short + I like it.

  (= buffered-execs* (table))

  (def buffer-exec (f (o delay 1))
    (unless buffered-execs*.f
      (= buffered-execs*.f 
         (thread (sleep delay) (wipe buffered-execs*.f) (f)))))

  (= dbs* ())

  (def db (fname (o delay 0.5))  ; file "synced" every 0.5 sec
    (withs (tbl (safe-load-table fname)
    	    savefn (fn () (atomic:save-table tbl fname)))
      (push (list tbl (fn () (buffer-exec savefn delay))) dbs*)
      tbl))

  (let _sref sref
    (def sref (com val ind)
      (do1 (_sref com val ind)
        (awhen (and (isa com 'table) (alref dbs* com)) (it))))
  )

-----

More