Arc Forumnew | comments | leaders | submitlogin
2 points by mpr 3734 days ago | link | parent | on: Execute code in the scope of a hash table

Oh I see now. That is pretty cool
3 points by jsgrahamus 3734 days ago | link | parent | on: ASK: How to read user input?

Synchronicity at work. I was just wondering the same thing and have been working with tryarc.org

When I try the above I get:

  arc> (def prompt (msg)
        (pr msg)
        (readc)   ; grabs the newline
        (readline))
  #<procedure: prompt>
  arc> (prompt "Ask anything: ")
  Ask anything: nil
  arc>  
Not going to be changing tryarc.org. Any suggestions?

Steve


Yeah it lets you decide what to do when getting or setting a variable. The original link has an example at the bottom, but here's another one kinda related to what you seem to be trying to do:

  arc> (= h (obj a 1 b 2))
  #hash((a . 1) (b . 2))
  arc> (defvar a
               (fn args
                 (if args
                   ; write
                   (= h!a car.args)
                   ; read
                   h!a)))
  arc> a
  1
  arc> (= a 3)
  3
  arc> a
  3
  arc> h
  #hash((a . 3) (b . 2))
2 points by mpr 3734 days ago | link | parent | on: Execute code in the scope of a hash table

I read the link you suggested in my previous post about defvar, and I see that it can be used to set dynamic behavior when a variable is referenced (??). Is this the correct interpretation, and what might be some uses of that?
4 points by mpr 3734 days ago | link | parent | on: Execute code in the scope of a hash table

Wow great post, thanks for the reply. I didn't know eval was always executed at global scope, and I was not thinking about the ht variable being executed multiple times at different scope, so thanks for pointing that out. The rest of your explanation about how to get it working is very clear and I will be a better arc hacker for it!

The occasion for this macro is an object system built around closures and hash tables. I will post more about it when I've made more progress.

4 points by ca 3734 days ago | link | parent | on: Can't access my server

It's up!

The issue was network (firewall) related. I simply destroyed the firewall (it's on VM so am not concerned about security on this one). On ubuntu that amounted to removing the IPtable rules.

Am looking forward to putting this behind Nginx and seeing how that goes.

Thanks for sticking with me kinnard. It's very much appreciated. Have a great week.


The essential trouble you'll have with this macro is that Arc macros don't know what variables are in their caller's lexical environment.

  (let a 1
    (w/tab (obj b 2)
      (+ a b)))
Since the place `b` will be looked up isn't determined unil run time, it has to use `eval`. But a call to `eval` always uses the global scope, so we lose `a`.

We could work around this by changing the way Arc's macroexpander worked. For now, let's not worry about that. I'll return to this later.

---

I see some particular bugs that can be fixed in your code.

First, you're inserting the bound values as flattened expressions in your generated code, so they'll actually be flattened executed. That is, if you write (w/tab (obj x '(+ (+ 2 y) 4)) ...), then I think `x` will be bound to the function `+`, `+` will be bound to 2, and `y` will be bound to 4.

If that's not what you want, we can make this change:

  -    `(let ,bindings (flat (tablist ,ht))
  +    `(let ,bindings (mappend [let (k v) _ `(,k ',v)] (tablist ,ht))
Second, you're assigning a nonlocal variable there, `res`. I don't know if you were doing that on purpose, but here's a version that avoids doing that:

   (mac w/tab (ht . body)
  -  (w/uniq (bindings k)
  +  (w/uniq (bindings res k)
       `(let ,bindings (mappend [let (k v) _ `(,k ',v)] (tablist ,ht))
          (eval `(with ,,bindings
  -                (= res (do ,@',body))
  -                (each ,',k (keys ,',ht)
  -                  (= (,',ht ,',k) (eval ,',k)))
  -               res)))))
  +                (let ,',res (do ,@',body)
  +                  (each ,',k (keys ,',ht)
  +                    (= (,',ht ,',k) (eval ,',k)))
  +                  ,',res))))))
Third, as you observed, the (eval ...) inside the (each ...) throws an error. What's going on is that `eval` evaluates things in the global scope, and you're trying to get a local variable. I recommend taking out the (each ...) loop and replacing it with a sequence of assignments. This means the keys you're assigning to will be determined based on the initial state of the table, rather than the final state, but that corresponds to the local variables that have been made anyway.

   (mac w/tab (ht . body)
  -  (w/uniq (bindings res k)
  +  (w/uniq (ht-list res)
  -    `(let ,bindings (mappend [let (k v) _ `(,k ',v)] (tablist ,ht))
  +    `(let ,ht-list (tablist ,ht)
  -       (eval `(with ,,bindings
  +       (eval `(with ,(mappend [let (k v) _ `(,k ',v)] ,ht-list)
                   (let ,',res (do ,@',body)
  -                  (each ,',k (keys ,',ht)
  -                    (= (,',ht ,',k) (eval ,',k)))
  +                  ,@(map [let (k v) _ `(= (,',ht ',k) ,k)] ,ht-list)
                     ,',res))))))
Fourth, this code evaluates the `ht` expression each and every time it does an assignment. This might be okay, except it's evaluating the expression in the local scope the first time and the global scope all the other times, which will probably lead to annoying errors. Let's evaluate it only once, in the local scope:

   (mac w/tab (ht . body)
  -  (w/uniq (ht-list res)
  +  (w/uniq (ght ht-list res)
  -    `(let ,ht-list (tablist ,ht)
  +    `(withs (,ght ,ht ,ht-list (tablist ,ght))
          (eval `(with ,(mappend [let (k v) _ `(,k ',v)] ,ht-list)
                   (let ,',res (do ,@',body)
  -                  ,@(map [let (k v) _ `(= (,',ht ',k) ,k)] ,ht-list)
  +                  ,@(map [let (k v) _ `(= (',,ght ',k) ,k)] ,ht-list)
                     ,',res))))))
I'm doing something tricky there: I'm inserting the table itself as a quoted value. It's possible to avoid this with a pattern like ((eval `(fn (x) (... x ...))) x), but Arc lets us just do (eval `(... ',x ...)).

Er, actually, something's making that code not work on Anarki, even though it does work on Arc 3.1. Embedding a function (instead of a table) works on Anarki, so let's do that:

  -                  ,@(map [let (k v) _ `(= (',,ght ',k) ,k)] ,ht-list)
  +                  ,@(map [let (k v) _ `(',[= (,ght k) _] ,k)] ,ht-list)
Here's the resulting code:

  (mac w/tab (ht . body)
    (w/uniq (ght ht-list res)
      `(withs (,ght ,ht ,ht-list (tablist ,ght))
         (eval `(with ,(mappend [let (k v) _ `(,k ',v)] ,ht-list)
                  (let ,',res (do ,@',body)
                    ,@(map [let (k v) _ `(',[= (,ght k) _] ,k)] ,ht-list)
                    ,',res))))))
As akkartik was saying, nested quasiquotation can be hard to get right. We can avoid nested quasiquotation if we factor the code a bit differently:

  (def w/tab-fn (ht body)
    (let ht-list tablist.ht
      (eval:w/uniq res
        `(with ,(mappend [let (k v) _ `(,k ',v)] ht-list)
           (let ,res (do ,@body)
             ,@(map [let (k v) _ `(',[= ht.k _] ,k)] ht-list)
             ,res)))))
  
  (mac w/tab (ht . body)
    `(w/tab-fn ,ht ',body))
Here it is in action:

  arc> (let a 1 (w/tab (obj b 2) (+ b b)))
  4
  arc> (let a (obj b 2) (w/tab a (= b 4)) a)
  #hash((b . 4))
  arc> (let a 1 (w/tab (obj b '(+ (+ 2 y) 3)) (join b b)))
  (+ (+ 2 y) 3 + (+ 2 y) 3)
---

As I mentioned above, there's one case that's sadly impossible to support without hacking on the language a bit:

  arc> (let a 1 (w/tab (obj b 2) (+ a b)))
  Error: "reference to undefined identifier: _a"
Okay, here's a really hackish way to add this capability to the language.

We can make this six-line modification to ac.scm:

  +(define latest-macex-env* 'nil)
  +
  +(xdef get-latest-macex-env (lambda () latest-macex-env*))
  +
   (define (ac-call fn args env)
  +  (set! latest-macex-env* env)
     (let ((macfn (ac-macro? fn)))
       ...))
  
   ...
  
   (define (ac-macex e . once)
  +  (set! latest-macex-env* 'nil)
     (if (pair? e)
       ...))
Now we can update the macro:

  (def w/tab-fn (caller-env ht body)
    (let ht-list tablist.ht
      (eval:w/uniq res
        `(with ,(mappend [let (k v) _ `(,k ',v)] tablist.caller-env)
           (with ,(mappend [let (k v) _ `(,k ',v)] ht-list)
             (let ,res (do ,@body)
               ,@(map [let (k v) _ `(',[= ht.k _] ,k)] ht-list)
               ,res))))))
  
  (mac w/tab (ht . body)
    `(w/tab-fn (obj ,@(mappend [do `(,_ ,_)] (get-latest-macex-env)))
       ,ht ',body))
Ta-da!

  arc> (let a 1 (w/tab (obj b 2) (+ a b)))
  3
We have effectively called `eval` in a local scope. We constructed the local scope table explicitly and then explicitly set it up again in the generated code.

---

If anyone would like to incorporate that six-line hack I demonstrated into Anarki, I think it will be a bit nicer if (get-latest-macex-env) were dynamically scoped rather than permanently mutated on each call. This will only ever matter if a macro invokes `eval`, `macex`, or `macex1` during its own macroexpansion, but hey, it could happen!

(This is effectively very similar to Kernel fexprs. The similarity would be even stronger if the environment were a table that mapped each variable to its local macroexpander. Arc doesn't have locally scoped macros, but I think this is a good way to go if we want them.)


I'm on the move at the moment, but since you brought up defvar yesterday I wonder if that would be a simpler way to go..

Also, it's not clear why you're using the top-level eval. I think it might be unnecessary. Double unquoting is hard to get right so worth avoiding if at all possible. Edit 2 hours later: oh, I see the reason for the eval.

3 points by mpr 3735 days ago | link | parent | on: ASK: How to read user input?

Pull request submitted
2 points by mpr 3735 days ago | link | parent | on: ASK: How to read user input?

Thanks! Yeah, I am aware of that bug, and was kind of ignoring it ;) I'll implement your suggested fix then send a pull request.
2 points by akkartik 3735 days ago | link | parent | on: ASK: How to read user input?

I like it! I don't think it'll break anything; can you send a pull request? Then we'll be able to run such code reliably at the repl! That would be sweet.

Edit 38 minutes later: hmm, there's one issue. Right now you can type multiple expressions in on a single line, but this change would drop everything after the end of the first expression. A better approach would be to drop only whitespace and stop at the very first non-whitespace character.

3 points by mpr 3735 days ago | link | parent | on: ASK: How to read user input?

Yep, thats the idea. Anyway, here is my hack, in all its hackish glory:

    (define (trash-line c)
      (if (equal? c #\newline)
        '()
        (trash-line (read-char))))

    (define (tl2 interactive?)
      (when interactive? (display "arc> "))
      (on-err (lambda (c)
                (set! last-condition* c)
                (parameterize ((current-output-port (current-error-port)))
                  ((error-display-handler) (exn-message c) c)
                  (newline))
                (tl2 interactive?))
        (lambda ()
          (let ((expr (read)))

            ;; HACK located here
            (trash-line (read-char)) ; throw away until we hit the newline


            (if (eof-object? expr)
                 (begin (when interactive? (newline))
                        (exit)))
            (if (eqv? expr ':a)
                'done
                (let ((val (arc-eval expr)))
                  (when interactive?
                    (write (ac-denil val))
                    (newline))
                  (parameterize ((current-namespace (main-namespace)))
                    (namespace-set-variable-value! '_that val)
                    (namespace-set-variable-value! '_thatexpr expr))
                  (tl2 interactive?)))))))
So I call the trash-line function after the expr is read, but before it is eval'd by arc, so that there is not leading #\newline in the input buffer.

This does seem to work for the readline'ing I was doing yesterday. Probably doesn't handle all cases.

As akkartik mentioned above, this hack is obviated by running scripts in batch mode.

-mpr

Edit: this change is in my ac.scm file around line 1250

2 points by mpr 3735 days ago | link | parent | on: ASK: How to read user input?

I was using defvar where I should have been using =. But the idea in my post was just to store the result of (prompt) in the variable x, which is meant to be a string.

And yes, now that I am moving past the experimental phase of my script, I will be running things in batch mode.

Thanks for the advice.

3 points by akkartik 3735 days ago | link | parent | on: ASK: How to read user input?

I think you're misusing defvar. You can read about defvar at https://awwx.ws/defvar2. In short, to invoke your prompt function everytime you refer to x, say this:

  arc> (defvar x (fn() (prompt "> ")))
For example:

  arc> (len x)
  > abc
  3
BTW, I tend to avoid using the interactive repl when I'm performing raw I/O because of the need for the (readc) hack you mentioned. I'd rather just write my code in a script and run it in batch mode.
2 points by kinnard 3735 days ago | link | parent | on: ASK: How to read user input?

I think I understand. You want a function that prints arbitrary user prompts and then takes in user inputs?

You should share your hack!

1 point by mpr 3735 days ago | link | parent | on: ASK: How to read user input?

Yes, but I'm using the msg arg as the prompt text. Example:

    arc> (= x (prompt "> "))
    > this is the user text
    arc> x
    "this is the user text"
I ended up hacking the ac.scm file to throw away the first newline after an expr is (read). It works for now.
1 point by kinnard 3735 days ago | link | parent | on: ASK: How to read user input?

I believe what you're looking to do is assign what's "read" in to a variable e.g.:

````

(def getUserPrompt ()

    (= msg readline)

    (pr msg)
)

````

1 point by kinnard 3735 days ago | link | parent | on: ASK: What is Lisp Enlightment?

I now disagree: I realized my cognitive process might actually be the lambda calculus.
2 points by mpr 3736 days ago | link | parent | on: ASK: Arc Language Slack?

Yes, count my vote in favor of an arclang slack

Seemed like PR: http://paulgraham.com/submarine.html

Surprised this story got no love. It's providing a powerful new medium/platform for lisping . . . I expect it to have a big impact on the size and character of the lisp community.
4 points by kinnard 3737 days ago | link | parent | on: Can't access my server

I believe that error occurs because the news app is attempting to `rm` a temporary dir that hasn't been created yet: https://github.com/arclanguage/anarki/blob/6d28039a69a9afa56...

It may be related to what's being discussed here: https://github.com/arclanguage/anarki

But I am able to successfully visit http://localhost:8080/ after running `./run-news`. Alternatively the (nsv) command also works for me.

So I'm not able to reproduce the error that you're getting.

Any luck investigating your firewall?

2 points by ca 3737 days ago | link | parent | on: Can't access my server

Many thanks for helping with this.

This is what the terminal looks like when I run ./run-news

$ ./run-news rm: cannot remove 'www/news/story/*.tmp': No such file or directory load items: ranking stories. ready to serve port 8080

To quit: arc> (quit)

  (or press ctrl and 'd' at once)
For help on say 'string':

  arc> (help string)
For a list of differences with arc 3.1:

  arc> (incompatibilities)
To run all automatic tests:

  $ hg clone https://bitbucket.org/zck/unit-test.arc

  $ ./arc

  arc> (load "tests.arc")
If you have questions or get stuck, come to

http://arclanguage.com/forum.

Arc 3.1 documentation: https://arclanguage.github.io/ref.

arc>

5 points by kinnard 3738 days ago | link | parent | on: Can't access my server

This is a stab in the dark but have you opened the port to receive connections from other "machines". Maybe the firewall is blocking incoming connections.

Also, it might help if you copy exactly your terminal input.

Additionally, the github is another resource though I'm not sure this is an arc issue: https://github.com/arclanguage/anarki


Ack yes I was thinking about downvoting stories, but that's not on HN either.

I have the ability to downvote right now, but it comes and goes. I don't know what causes it.
2 points by akkartik 3738 days ago | link | parent | on: The Theory of Concatenative Combinators

Yes, my impression is that "stack-based lisp" describes http://factorcode.org to a 't'. In Factor you can throw code on the stack by a process called.. quoting. And then invoke it inside some other higher-order function.

No I don't believe so. I think downvotes came to HN late in life, and this forum hasn't gotten that update (and probably others)

Anyone down to develop an arc scripting language for VR?
1 point by kinnard 3739 days ago | link | parent | on: Why Lisp?

Analysis of homoiconicity by comparing S-expressions, json, and xml is pretty compelling compared to all the other lisp evangelism I've read.
More