Arc Forumnew | comments | leaders | submit | fallintothis's commentslogin
4 points by fallintothis 5749 days ago | link | parent | on: Toy lisp interpreter in lua

t is merely a global variable

Except you can't destructively update it. So it's really more of a global constant. This is an important caveat because it affects locally scoped ts, too.

  arc> (= t 1)
  Error: "Can't rebind t"
  arc> (= nil 1)
  Error: "Can't rebind nil"
  arc> (let t 4 (+ t 1))
  5
  arc> (let t 4 (++ t))
  Error: "Can't rebind t"
I imagine that's what made it seem reserved. You still probably shouldn't name variables t or nil.

-----

2 points by aw 5749 days ago | link

Except you can't destructively update it

Hmm, good point. Since it does no harm to change t when it is a local variable, I'd remove the restriction for that case.

I might even drop the restriction altogether, as it doesn't seem to have much useful purpose. I can break Arc just as badly by rebinding "car" or other variables, so why this particular effort to keep me from hurting myself?

-----

1 point by meric 5749 days ago | link

That doesn't change the fact that I shouldn't use t for anything other than `t`, but good point.

-----

2 points by fallintothis 5750 days ago | link | parent | on: Toy lisp interpreter in lua

You mentioned not knowing what sref and sig are, so FYI:

sref is "set reference". It's used for indexed assignment.

  arc> (= xs '(a b c))
  (a b c)
  arc> (sref xs 'd 0)
  d
  arc> xs
  (d b c)
sig is a global hashtable where function signatures are stored.

  arc> (sig 'map1)
  (f xs)
  arc> (def f (a b c) (+ a b c))
  #<procedure: f>
  arc> (sig 'f)
  (a b c)
There's more Arc documentation at http://arcfn.com/doc/index.html.

-----

2 points by fallintothis 5754 days ago | link | parent | on: Does Arc have (2-dim) arrays?

Arc doesn't really have arrays, so it doesn't have 2-dimensional arrays. One approach is to use a list of lists.

  arc> (= plane '((a b c) (d e f) (g h i)))
  ((a b c) (d e f) (g h i))
  arc> ((plane 0) 0)
  a
  arc> ((plane 0) 1)
  b
  arc> ((plane 0) 2)
  c
  arc> ((plane 1) 2)
  f
But indexing linked lists is inefficient, so you could similarly nest hash tables.

Granted, neither solution is pretty. I suspect they aren't there because (as with many things) pg hasn't needed them yet himself.

-----

1 point by akkartik 5753 days ago | link

Prettier syntax: arr.i.j

-----

2 points by fallintothis 5753 days ago | link

D'oh. My fingers were in pre-Arc3 mode, where that would've expanded into

  (arr i j)
But yeah, good point, that works now: http://arclanguage.org/item?id=9163. Thanks for spotting it.

-----

1 point by fallintothis 5763 days ago | link | parent | on: Symbol tables

Cool solution! Of course, handling arbitrarily-long history and globals would be more complicated. Maybe you could modify = to push old values onto a stack?

One advantage to an ac.scm approach is that you don't need to use the special ufn. If it worked on fn itself, then any local variables -- in a def, let, with, mac, etc. -- would be undoable. In an attempt to do this, I added a bit to your macro-based approach:

  (def undo-fn (symbols)
    (let backups (map1 [uniq] symbols)
      `(let ,backups (list ,@symbols)
         (fn () (= ,@(mappend list symbols backups))))))

  (mac w/undoable (parms . body)
    `(let undo ,(undo-fn parms)
       ,@body))

  (mac w/undo (expr)
    ((afn (tree)
       (let tree (macex tree)
         (if (caris tree 'fn)
              (let (lambda args . body) tree
                `(fn ,args (w/undoable ,args ,@(self body))))
             (atom tree)
              tree
             (cons (self (car tree))
                   (self (cdr tree))))))
     expr))
Certainly a hack, but it gives us:

  arc> (w/undo (let x 5
                 (prn "original: " x)
                 (++ x)
                 (prn "new: " x)
                 (undo)
                 (prn "undone: " x)
                 nil))
  original: 5
  new: 6
  undone: 5
  nil

  arc> (w/undo
         (def f (x)
           (prn "original: " x)
           (++ x)
           (prn "new: " x)
           (undo)
           (prn "undone: " x)
           nil))
  #<procedure: f>
  arc> (f 5)
  original: 5
  new: 6
  undone: 5
  nil
  arc> (f 10)
  original: 10
  new: 11
  undone: 10
  nil

  arc> (w/undo
         (def f (x)
           (prn "=== OUTSIDE LET ===")
           (prn "original: " x)
           (= x (let x (+ x 1)
                  (prn "=== INSIDE LET ===")
                  (prn "    original: " x)
                  (++ x)
                  (prn "    new: " x)
                  (undo)
                  (prn "    undone: " x)
                  x))
           (prn "new: " x)
           (undo)
           (prn "undone: " x)
           nil))
  *** redefining f
  #<procedure: f>
  arc> (f 5)
  === OUTSIDE LET ===
  original: 5
  === INSIDE LET ===
      original: 6
      new: 7
      undone: 6
  new: 6
  undone: 5
  nil

-----

1 point by shader 5746 days ago | link

All of the solutions so far have been interesting, but I was thinking more of being able to undo any change to any variable, instead of just the few variables declared as parameters.

The idea would be something like this:

  (with x 5 y 4
    (w/undo
      (++ x)           -> x=6
      (undo x)         -> x returns to 5
      (= y (++ x))     -> x = y = 6
      (side-efn x y)   -> x and y are modified
      (undo)           -> x = y = 6 again
      (reset)))         -> x = 5, y = 4
It seems like being able to undo side effects caused by function calls to arbitrary variables would require overloading = (or more likely sref).

Maybe if you did that you could replace variables that are modified with objects that when evaluated return their current value, but which can undo and maybe even redo by changing the return value to an earlier or later value on the stack. They should also be added to a list of modified variables owned by the w/undo macro, so that it can reset all of their values, and also commit their final value when the block is completed.

Would this even be possible? I'm not sure that it would be, since I think arc uses lexical scoping, which means that an arbitrary function call would use its own context's definition of sref, and thus wouldn't pay attention if we redefined it. Maybe since sref is a global variable, it could be replaced and then reset by w/undo? Meta w/undo!

Anyway, this concept of making variables able to remember their past and return previous values reminds me of lazy evaluation and memoization. Maybe their's some sort of connection that could be used to unify them simply, and add them to arc as a unit?

-----

2 points by rocketnia 5746 days ago | link

What happens if side-efn is this?

  (mac side-efn (x y)
     ; NOTE: This form is only intended to be used in the case where x and y are
     ; raw symbols, as they are in forms like (side-efn foo bar).
     `(= ,x (- ,y ,x)    ; temp = old y - old x
         ,y (- ,y ,x)    ; new y = old x (calculated as old y - temp)
         ,x (+ ,y ,x)))  ; new x = old y (calculated as old x + temp)
Should 'undo reverse just one of these assignments or all three? Should it make any difference if these assignments are three separate = forms within a (do ...) block?

A top-level undo could be nifty, but on the Arc top level even the macro environment gets to change from expression to expression, so it can be a bit different. To allow undo on a finer scale raises the question of just how to measure the previous transaction. How many assignments should be undone, and what if they're hidden away in a call to a function nobody likes to read? What about assignments for scopes whose dynamic extent has ended?

-----

1 point by shader 5742 days ago | link

I'm not entirely sure. Now that I think about it, fine grained undo is really a different concept from global state restoration, and is commonly only done for variables that the programmer a) knows about and b) has in mind the place he would like to restore to.

This means that finer grained undo would be more accurately implemented with save and restore functions, as opposed to just an undo function.

The global undo should work the same was as previously stated, more like a reset, and going all the way back to the start point, which may or may not be the beginning of the w/undo block.

Maybe a better system would be two pairs of save and restore functions, one that works on individual symbols, and the other that redefines '= to store the old value in a table if it didn't already exist, so that reset could restore it.

-----

1 point by fallintothis 5764 days ago | link | parent | on: Symbol tables

Yeah, but env just keeps the variables around, not their values. It's used to distinguish between locals and globals when compiling down to lambda.

  > (ac '(fn (x) (+ x 1)) '())
  (lambda (x) (ar-funcall2 _+ x 1))
  > (ac '(+ x 1) '())
  (ar-funcall2 _+ _x 1)
  > (ac '(+ x 1) '(x))
  (ar-funcall2 _+ x 1)
So it's not quite what you'd want. Off the top of my head, you could hack ac.scm to treat

  (fn (x) body)
as something like

  (fn (x) (= (locals* 'x) x) body (wipe (locals* 'x)))
Since Arc does all of its local binding with fn (either directly or by macroexpansion), this would work. ac.scm won't heed Arc-side redefinitions of fn, so I can't think of a convenient vanilla Arc implementation. As for efficiency, thread safety, etc., my idea seems gross.

I know Common Lisp has environments (http://www-prod-gif.supelec.fr/docs/cltl/clm/node102.html), but I don't know how they're usually implemented.

  $ clisp -q
  [1]> (let ((x 10)) (let ((y 20)) (the-environment)))
  #(#(Y 20 #(X 10 NIL))
    NIL
    NIL
    NIL
    ((DECLARATION XLIB::CLX-VALUES VALUES OPTIMIZE DECLARATION)))

-----

1 point by fallintothis 5771 days ago | link | parent | on: Rudimentary cons counter

Rather random thing to comment on after 2 weeks, I know. I was just reading this and noticed that your redef macro isn't really doing much: safeset doesn't get called by = unless you happen to make a defset to that effect, which seems unlikely.

  arc> (def f (x) (+ x 1))
  #<procedure: f>
  arc> (= f [+ _ 2])       ; no redef warning
  #<procedure: f>
  arc> (def f (x) (+ x 3)) ; redef warning
  *** redefining f
  #<procedure: f>

  arc> (defset blah (x)
         (w/uniq g
           (list (list g x)
                 x
                 `(fn (val) (safeset ,x val)))))
  #<procedure>
  arc> (= x 100)
  100
  arc> (= x 200)        ; no redef warning
  200
  arc> (= (blah x) 300) ; redef warning
  *** redefining x
  300
Still an interesting idea to selectively silence safeset, though.

-----

1 point by akkartik 5769 days ago | link

Ah, that's embarrassing :)

So all I need is this then without any change to safeset:

  (mac redef(f args . body) `(= ,f (fn ,args ,@body)))

-----

1 point by fallintothis 5769 days ago | link

Right. So then after-exec becomes (keeping the presumably-intended variable capture)

  (mac after-exec (fnname args . body)
    `(let old ,fnname
       (redef ,fnname ,args
         (let result (old ,@args)
           ,@body
           result))))
And your uses of redef when you aren't redefining a function should be changed to = (e.g., in shadow).

It might still be cool to toggle safeset warnings. With Arc's single namespace, it's annoying to

  (load "foo.arc")
just to have it spit out redefinition warnings. But silencing safeset isn't really the answer to that problem.

-----

1 point by akkartik 5769 days ago | link

Yes. When I updated anarki I decided to just do

  (= redef =) ; how pretty!
It seems plausible to want redefs to look different from defs.

Disabling these warnings on reload is useful, but a separate use case. I tend to ignore them on reload, but I don't want to see them when I startup lest I start ignoring them altogether.

-----


You want

  (mac inittab (place . args)
    `(do (or= ,place (table))
         (init-table ,place (list ,@args))))

-----

1 point by akkartik 5775 days ago | link

Argh. Thanks!

-----

1 point by fallintothis 5777 days ago | link | parent | on: Contract ssyntax

Yeah, it's still a problem. Oddly enough, I only ever noticed it in test code that expanded ssyntax tree-wise (vs. Arc's symbol-wise ssexpand). I didn't put deliberate thought into making sscontract aware of the bug, but I think it's avoided because of careful precedence separation.

  arc> (sscontract '(andf f (complement g)))
  (andf f ~g)
  arc> (sscontract '(compose f (complement g)))
  f:~g
  arc> (sscontract '(f ~g))
  (f:complement g) ; ew...should probably return (f ~g)
  arc> (sscontract '(f '~g))
  (f (quote ~g))
Thanks for the andf case, by the way. Hadn't thought of that one. If you find any incorrect contractions, please let me know. If you want, you can use bitbucket's issue tracker: http://bitbucket.org/fallintothis/contract/issues/

-----

1 point by fallintothis 5777 days ago | link | parent | on: Contract ssyntax

What is the difference between minimal and minimum in this context?

I basically wanted to preempt complaints about

  arc> (sscontract '(no (no (no (f x)))))
  (~no:~f x) ; equivalent to (~f x)
because I didn't bother finding a clean way to compress it. Quasiquotes are a better example. Even though something like

  (mac even (x) `(no (odd ,x)))
would be fine to define as

  (mac even (x) `(~odd ,x))
sscontract is better-safe-than-sorry:

  arc> (sscontract '(mac even (x) `(no (odd ,x))))
  (mac even (x) (quasiquote (no (odd (unquote x)))))
Note that this specific behavior can be changed; see special-cases.arc.

I can imagine other code contraction facilities might be possible

Good guess: that's my next project. ;)

-----

2 points by fallintothis 5781 days ago | link | parent | on: Bug: on doesn't work for tables

For example, consider defining on without it.

  (mac wrong-on (var s . body)
    (if (is var 'index)
        (err "Can't use index as first arg to on.")
        `(forlen index ,s
           (let ,var (,s index)
             ,@body))))
Then

  arc> (wrong-on x (prn "abcd") (prs index x) (prn))
  abcd
  abcd
  0 a
  abcd
  1 b
  abcd
  2 c
  abcd
  3 d
  nil
because

  (macex1 '(wrong-on x (prn "abcd") (prs index x) (prn)))
is

  (forlen index (prn "abcd")
    (let x ((prn "abcd") index)
      (prs index x) (prn)))
Notice that (prn "abcd") is evaluated once at the start, then once on each iteration.

Versus the arc.arc on:

  arc> (on x (prn "abcd") (prs index x) (prn))
  abcd
  0 a
  1 b
  2 c
  3 d
  nil
because

  (macex1 '(on x (prn "abcd") (prs index x) (prn)))
is

  (let gs1763 (prn "abcd")
    (forlen index gs1763
      (let x (gs1763 index)
        (prs index x) (prn))))
Here, (prn "abcd") is evaluated only once. It's bound to the gensym gs1763, which won't clash with any variable names you already have (not strictly, cf. http://arclanguage.org/item?id=5104, but that's the idea).

You can automate this idiom with the once-only macro; see http://arclanguage.org/item?id=9918 or towards the end of http://gigamonkeys.com/book/macros-defining-your-own.html.

-----

1 point by akkartik 5781 days ago | link

Thanks for the pointer to once-only. Is this the version I should use? (I'm having trouble wrapping my head around the nested unquotes)

http://arclanguage.org/item?id=9939

My definition of on seems to eval the list only once anyway, but now I'm feeling paranoid about where else I've missed this and taken a performance hit.

-----

1 point by fallintothis 5781 days ago | link

Is this the version I should use?

You can if you want. It works.

  arc> (let a '(prn 5)
         (once-only (a)
           `(+ ,a 1)))
  (with (gs2595 (prn 5)) (+ gs2595 1))
Note that the parameter list doesn't work like w/uniq, where you can have a single argument with no parentheses. i.e.,

  arc> (let a '(prn 5)
         (once-only a
           `(+ ,a 1)))
  Error: "Can't take car of a"
This was fixed in the Anarki version (http://github.com/nex3/arc/blob/master/lib/util.arc#L374), but that version uses Anarki-specific utilities. The fix is still vanilla-Arc, if you want it:

  (mac once-only (names . body)
    (withs (names (check names alist (list names))
            gensyms (map1 [uniq] names))
      `(w/uniq ,gensyms
        `(with ,(list ,@(mappend list gensyms names))
          ,(with ,(mappend list names gensyms)
            ,@body)))))

  arc> (let a '(prn 5)
         (once-only a
           `(+ ,a 1)))
  (with (gs1724 (prn 5)) (+ gs1724 1))
My definition of on seems to eval the list only once anyway

Yeah, your definition's fine because each already handles the multiple-eval situation.

Side note:

  (zap [+ 1 _] index)
is equivalent to

  (zap + index 1)
which is equivalent to

  (++ index)
Just so you know.

-----

1 point by akkartik 5781 days ago | link

D'oh. You know, I actually tried (zap ++ index) first. Don't know what I was thinking.

Thanks for the (zap + index 1) trick.

-----

More