Arc Forumnew | comments | leaders | submit | waterhouse's commentslogin
2 points by waterhouse 5702 days ago | link | parent | on: Environments for Arc programming?

DrScheme to edit Arc files, rlwrap + mzscheme REPL. Note that in DrScheme you can do a certain amount of customization of indentation patterns: Preferences -> Editing -> Indenting.

-----

1 point by evanrmurphy 5702 days ago | link

I haven't spent much time with DrScheme yet, could you talk about why you like it?

-----

3 points by waterhouse 5701 days ago | link

Partially because it was what I used when I was first introduced to Scheme. It has the basic Lisp-editing features (paren-matching, indentation); it colors text (e.g. comments are orange, literals are green, normal code is blue); it has a nice GUI for customizing much of the above (for indenting, you can give it specific words or regular expressions to determine whether to indent something 'define-style, 'lambda-style, or 'begin-style; you can also pick your own colors instead of the above, though I don't do that). I also like that cmd-I reindents everything.

Drawbacks: Indentation can't be customized as much as I'd like[1], at least as far as I know. Tendency to freeze up for several seconds (maybe because I tend to leave several files open for days at a time, but I'm not changing that).

I've experimented with Emacs too, and they are pretty comparable. I use DrScheme these days mainly out of habit, but one obstacle in Emacs is that I don't know how to make it do Arc-specific indentation, whereas I can do that pretty well in DrScheme.

[1] For example:

  (with (this (blah)
              that (thing)) ;this line is too far right
    (do-stuff) ;this line isn't, because I told it
    (do-more)) ; to indent "with" lambda-style

-----

1 point by garply 5701 days ago | link

The with macro indentation drives me nuts too, but I'm sure it's fixable.

-----

5 points by waterhouse 5725 days ago | link | parent | on: What does ac buy us?

I myself have implemented large amounts of Arc in PLT Scheme, hitting some roadblocks, passing some of them and giving up on others. I've done the same thing, more recently and extensively, in Common Lisp. I can show you what I've done if you are interested.

Note that one could write an 'fn macro that, depending on what's in its parameter list (which is certainly available at macroexpansion time), might expand directly to a 'lambda form, or might expand to (lambda args (destructuring-bind <parameter list> args <body ...>)). And Scheme does have something called 'case-lambda, which looks like it can be used to make functions with optional parameters. I have implemented a pretty good 'fn in Common Lisp (which supports optional parameters):

  (mac fn (args . body)
    (with args (restify args)
      (if (flatp (cut args 0
                      (or (position '&optional args) (len args))))
          `(lambda ,args ,@body)
          (w/uniq gargs
            `(lambda (&rest ,gargs)
               (dsb ,args ,gargs
                 ,@body))))))
(Note some things I've done: I wrote 'mac so it supports the . notation for rest parameters--which is what 'restify deals with, by the way; I wrote 'with so that you can either go (with (x 1 y 2) ...) or (with x 1 ...), since 'let is already defined; and I wrote 'dsb, for "destructuring bind".)

Also... with PLT's "Pretty Big" language, in the Languages menu (click Show Details), you can uncheck a box called "Disallow redefinition of initial bindings", and then you are free to redefine 'let and 'if and so on.

And it would, in fact, be possible to make = reach inside structures. You would just have to tell it how to do so--give it a bunch of patterns, like (= (car x) y) --> (set-car! x y). Hmm, also, it turns out there's a procedure named 'make-set!-transformer, which sounds like something we could use.

But I didn't figure out how to make = work well for assigning to variables that don't exist yet. The general variable assignment procedure in Scheme is set!, as in (set! x 3). But this is an error when x is undefined. To assign to an undefined variable, it seems you must use either namespace-set-variable-value! or define; but 'define can't be used in the middle of an expression (to allow for "block structure", so you can use 'define to make local recursive functions and stuff), and if we simply make (= a b) expand to (namespace-set-variable-value! 'a b), then that won't work when we want to modify a local variable. Here's the best I can do, but I imagine the overhead of the 'with-handlers thing would make it take obscenely long for, say, incrementing the variable i 10000 times... having said that, I tried it out. It takes 33 msec to go (set! x (+ x 1)) 100000 times, and it takes 100 msec to go (= x (+ x 1)) 100000 times. Which is not nearly as bad as I feared--likely any other code your loop executes will take much longer than the difference this makes. I guess this actually isn't a problem. Anyway, my code:

  (define-macro (= a b)
    (let ((gb (gensym)))
      `(let ((,gb ,b))
         (with-handlers
             ((exn:fail? (lambda (x)
                           (namespace-set-variable-value! ',a ,gb))))
           (set! ,a ,gb))
         ,a)))

And now here is what I find to be the biggest problem in Scheme: making macros work well. In Arc, you can go:

  (def expand-thing (x y)
    ...)
  
  (mac thing (x y)
    (expand-thing x y))
Here, the body of 'thing contains a call to 'expand-thing, which is a function that you just defined. In Scheme, if you define a function and then try to use it in the body of a macro, it won't work--when you use the macro, it will complain of an undefined function. Now, it is possible to get around this: Scheme has a special form, 'begin-for-syntax, which means "execute this code at compile-time". We could, therefore, go

  (begin-for-syntax
    (define (expand-thing x y) ...))
  
  (define-macro (thing x y)
    (expand-thing x y))
That would work. But, now, suppose we had a macro of our own, such as our version of 'if, that we wanted to use in the body of thing or of expand-thing. This does not work:

  (define-macro (my-if . args)
    `(if ,@args))
  
  (define-macro (meh x)
    (my-if (pair? x)
           `(+ ,@x)
           x))
  
  (meh 2)
It will try to expand the call to 'meh and find that 'my-if is an undefined variable. Macros, defined at compile-time, do not get expanded in the bodies of things that are defined at compile-time. (Actually the PLT docs talk about "phases", rather than "compile-time" and "run-time"; it seems you can have arbitrarily many phases of syntax expansion before the final evaluation.) To make this happen, you would sorta have to go (begin-for-syntax (define-macro ...)), but then it doesn't recognize the word 'define-macro. I think it's possible to load mzscheme at earlier syntax phases so it recognizes 'define-macro 2 phases up, but then if you wanted to use a macro in the body of 'my-if, you'd need to go 3 phases up, and so on: it's just too terrible.

My best idea seems to be this: At compile-time, create a table (an assoc-list?) of macro names and their body-functions. Then make your 'mac and 'def and all the other things you want evaluated at compile-time, make them macroexpand their bodies. Do this by checking if it's a list: if so, check if the car is the name of a macro: if so, apply the macro's body-function to the cdr, and repeat; otherwise, leave the list itself as is, but look at its elements (recursively) and macroexpand them. But what if this macro call is inside a quote, or a quasiquote? Oh god. You have to write a whole goddamn code-walker to make this work. I stopped at that point.

Common Lisp works fine on the macros issue. It also agrees with Arc about nil. And SBCL is pretty damn fast. But it has a separate namespace for functions, which is pretty non-foolable with, and it seems rather unwilling to allow redefinition of 'if and 'let and 'map (they call it 'mapcar) and so on. I tend to switch between Common Lisp and Arc these days.

-----

3 points by aw 5724 days ago | link

The general variable assignment procedure in Scheme is set!, as in (set! x 3). But this is an error when x is undefined.

I just noticed compile-allow-set!-undefined the other day:

  Welcome to MzScheme v4.2.1 [3m], Copyright (c) 2004-2009 PLT Scheme Inc.
  > (set! a 3)
  set!: cannot set undefined identifier: a
  
  > (compile-allow-set!-undefined #t)
  > (set! a 3)
  >

-----

2 points by waterhouse 5724 days ago | link

Here is my arc.lisp file, for everyone who is interested. Tell me if the link works, I've never shared anything with Dropbox before:

http://dl.dropbox.com/u/3678008/arc.lisp

aw: Nice find. I could use that.

fallintothis: I'm aware of the destructuring use of the Arc 'let. But I find that when I want destructuring, I usually want to go (mapcar (fn ((a b)) ...) (tuples 2 xs)), and I rarely use an explicit 'dsb; if I want it, though, it's there. My version of 'with is all I need most of the time.

-----

1 point by evanrmurphy 5695 days ago | link

I am trying out your "arc.lisp" now. Seems great so far.

There is much exploring to do, but my initial confusion with =. Seems it's not working and maybe not even defined. I read your discussion above of the problems with =, but I thought that was about your Arc in PLT, not CL. I haven't looked closely at the guts of "arc.lisp" yet, so maybe that will answer my question.

-----

2 points by waterhouse 5694 days ago | link

Oh, I forgot to include that in the list of idiosyncrasies. Basically, use setf wherever you would use = in Arc. The reason, in short, is that = is already the numerical-equality function, and CL doesn't like people to redefine things.

In PLT Scheme, you just have to uncheck a box that says "Disallow redefinition of initial bindings" and then you can redefine almost everything, but in CL, it will complain about breaking a package lock and go into the debugger whenever you try to redefine a built-in function, of which = is one. It's possible to tell it "Yes, ignore package lock", but I don't want to do that for every single function every time I start up SBCL. I think it is possible to tell it to automatically ignore the lock... But this is the way it is right now. Also, when you try to redefine 'if, you just run into a brick wall:

  The special operator IF can't be redefined as a macro.
I stumbled on one other workaround, too... you can create a new package and choose not to import (all of) the stuff from CL-USER. So far, though, this doesn't even import the symbol nil, and when I do import it, it gets printed with a "COMMON-LISP:" prefix.

  (make-package 'arc-user)
  (in-package 'arc-user)
  (cl-user::defvar nil cl-user::nil)
  * nil
  COMMON-LISP:NIL
  * (cl-user::evenp 2)
  COMMON-LISP:T
I guess one might be able to get used to that. Might try it out if I feel adventurous. For now, put up with using setf, mapcar, iff, and my version of with.

-----

1 point by akkartik 5724 days ago | link

Works great, thanks. (No other comments/questions yet.)

-----

1 point by fallintothis 5724 days ago | link

I wrote 'with so that you can either go (with (x 1 y 2) ...) or (with x 1 ...), since 'let is already defined

There's a reason for the with / let distinction: parenthesized arguments to let get destructured.

  arc> (let (de struct uring) '(a b c)
         (prs de struct uring)
         (prn))
  a b c
  nil
Did you account for this in your implementation?

(Thanks for the rant about Scheme, by the way. It was informative and entertaining. I just don't have any useful input on it.)

-----

1 point by akkartik 5724 days ago | link

Gahd, that was a fun read. I'd love to hear about your experiences with SBCL[1]. I've been porting my app over to it for the last 24 hours, lazily taking arc functions/macros as I go. My approach so far is to put up with &rest, extra parens in let, explicit destructuring-bind[2], etc.

[1] Perhaps offline; email in my profile; did you know the email field itself is not public?

[2] I wrote a bind macro variant a few years ago (http://akkartik.name/lisp.html), I may start using that. Still a little more verbose. Hmm, if I eliminate multiple-value-bind I can get rid of :db..

-----

2 points by waterhouse 5730 days ago | link | parent | on: Apropos

I would normally use 'apropos when I'm sitting at the REPL trying to remember the name of a function (or, more rarely, a global variable bound to a hash-table or something). For 'apropos to work on local variables, I assume you would have to use 'apropos within the lexical environment in which the variable exists. I.e. you would go

  (let thingamajig 2
    (do-stuff)
    ;hmm, what was that variable again? thing-something.
    (aps thing))
Which seems a bit useless to me, as the local variables are printed right there within the block of code you're currently writing. I suppose one could have some macro that creates a huge lexical environment, or something... am I missing your meaning? At any rate, 'namespace-mapped-symbols appears to be blind to local variables:

  > (let ((ach 2))
      (member 'ach (namespace-mapped-symbols)))
  #f
To obtain a list of local variables, I think you would have to hack with ac.scm. Or write a macro that searches/walks your code for lexical variables.

-----

2 points by waterhouse 5731 days ago | link | parent | on: Check function or macro definition?

I, my curiosity provoked, looked through Anarki, and I can answer your question: t is the value returned by 'src, by virtue of being the value returned by 'ppr. (A call to 'src expands to a call to 'ppr-source, and the last expression in 'ppr-source is (ppr source.name). (Actually it's source* but putting that in italicizes the rest of this message.)) It is 'ppr by itself that has this annoying property.

  arc> (ppr '(def meh (x) (list (+ x 2) (+ x 3))))
  (def meh (x)
    (list (+ x 2) (+ x 3)))t
It would be nice if ppr printed a newline at the end. In fact, I'm almost certain it should, because a) it seems one would use it to print code for human eyes, and nothing else, b) readable code does not generally have separate expressions on the same line, and c) anything printed on one line is by definition not prettily formatted and can be pr'd instead of ppr'd.

I would like to simply redefine ppr, thus:

  (let old-ppr ppr (def ppr args (apply old-ppr args) (prn))
Unfortunately, ppr is apparently defined in terms of itself. And when recursive functions get redefined, the calls to what used to be themselves get redefined too. Using the above makes ppr print out way too many newlines:

  arc> (src defop)
  (from "srv.arc")
  (mac defop
  
       (name parm . body)
  
       (w/uniq gs
  
               (quasiquote (do (wipe (redirector* '(unquote name)
  ...
It's just horrible. Simple hack fix: go to pprint.arc, replace all instances of "ppr" with "ppr-fn", and then add (def ppr args (apply ppr-fn args) (prn)). Or just put up with the t.

-----

3 points by shader 5730 days ago | link

Yep, the trailing t and lack of newline are due to ppr.

Fortunately, I also happen to have an alternative version of ppr on anarki, under the lib folder, which has just been updated to handle multiple expressions and print newlines.

Just pull anarki again, and use

  (load "lib/ppr.arc")
It should redefine sp, len, and ppr, and make source code printing much more readable than the pprint.arc version ;)

-----

3 points by waterhouse 5748 days ago | link | parent | on: Why does pr return it's first arg?

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

-----


Ran for a very long time, but finally erred. Mac OS X 10.6, mzscheme 4.2.4.

  iter
  (nil (0) 0)
  ((0) (0) 1)
  ((0 0) (0) 2)
  ((0 0) nil 3)
  error
  Error: "set-cdr!: expected argument of type <pair>; given nil"
Ran it a second time; it erred within three seconds. Ran it a third time; it erred after a minute or two.

-----

2 points by waterhouse 5748 days ago | link

This is curious. I've ran it with out-of-the-box arc3.1 and out-of-the-box mzscheme 4.2.4 and 4.2.2, on the same computer (with Mac OS X 10.6.2). It's erred every time with 4.2.4 (I think two or three times very quickly and three or four times after a long time), and it has not erred once with 4.2.2 (after four or five tries). I suppose it must be a problem with mzscheme.

-----

1 point by akkartik 5748 days ago | link

Thank you! I haven't managed to get the error on macos. This is good to know.

-----

1 point by waterhouse 5761 days ago | link | parent | on: Does Arc have (2-dim) arrays?

You can use lists of the 2 indices as keys to a hash-table. Thus, to make an "array" where the element at (i, j) is i^j, we could go:

  (= x (table))
  (for i 1 10
    (for j 1 10
      (= (x (list i j)) (expt i j))))
Optionally, you could define an array-lookup procedure thus:

  (def aref (a . indices)
    (a indices))
I haven't learned how to use 'defset yet, so I may be wrong, but I think you could then use it to make

  (= (aref a i j ...) value)
macroexpand into:

  (= (a (list i j ...)) value)

-----

3 points by waterhouse 5761 days ago | link

I learned to use 'defset by looking at the documentation and demonstration in arc.arc.

  (defset aref (x . indices)
   (w/uniq (gx gi)
           (list `(,gx ,x ,gi (list ,@indices))
                 `(,gx ,gi)
                 `(fn (val) (= (,gx ,gi) val)))))
And this works fine:

  arc> (= x (table))
  #hash()
  arc> (= (aref x 1 2) 3)
  3
  arc> x
  #hash(((1 2 . nil) . 3))
  arc> (x '(1 2))
  3
However, strangely, my definition of aref, as:

  (def aref (x . indices)
    (x indices))
does not work as intended. If x, a table, has a value for the key (list 1 2), then (aref x 1 2) will not find that value. I investigated and, basically, it seems that the list created by (list 1 2) is nil-terminated, while the list constructed from a rest parameter is ()-terminated:

  arc> (def ach (x . rest) rest)
  #<procedure: ach>
  arc> (ach x 1 2)
  (1 2)
  arc> (list 1 2)
  (1 2)
  arc> (cddr (ach x 1 2))
  ()
  arc> (cddr (list 1 2))
  nil
I imagine that's why the hash-table doesn't recognize the 'indices rest parameter as the key (1 2 . nil). Oh well, here's a stupid hack that makes 'aref work properly by constructing an Arc nil-terminated list:

  (def aref (x . indices)
    (x (apply list indices)))

-----


It does depend on the problem. For the pidigits one:

  1.1	Scheme PLT #4
For "chamenos-redux" (whatever the hell that is) (ok, apparently it's to do with running concurrent threads):

  63	Scheme PLT

-----


I would say, the code is put into lines naturally--the Arc edits2 definition is short enough that it fits comfortably on one line, while the Python known_edits2 definition probably does not--and then you count up the lines. (I say "probably" because perhaps some people would tolerate lines that long in their code. But I wouldn't, and it seems that neither did the person who wrote it.) What stops you from putting it all on one line and declaring victory? The fact that that's not how humans naturally read or write code; the first thing I'd do when reading such a program would be to separate it back into lines.

Anyway, if you want a more precisely standardizable measure of program length, try token count. That can't be fooled with, either by deleting whitespace or by giving things shorter names. (Though, if you wanted, you could take the program as a string, replace all whitespace with a special character so it's all one token, write a macro that converts the string back into real code, and thus convert an arbitrarily long program into one with a couple dozen tokens. Hmm. Is there any standardized measure of program length that can't be fooled with?)

-----

1 point by thaddeus 5888 days ago | link

> "I would say, the code is put into lines naturally"

I beg to differ. I've being reading pg's code and feel quite comfortable saying that his natural coding would have a line break in the example above - as would any of ours (unless you are unaware of the two spaces required for code on this forum - haha :)

Not a criticism of pg or his code. I think most the submissions to Norvigs site contain code where people are removing "natural" line breaks.

As an example..... Quote from comment on page: http://www.jelovic.com/weblog/?p=201

  "Perhaps we should stop at 15 lines before we start getting unreadable code :) !"
speaks the truth.

I don't think there's any real value in creating a table ranking languages by line numbers. I do think the code pg produced does demonstrate how pleasant arc is to read while being very short.

-----

1 point by waterhouse 5986 days ago | link | parent | on: Bug in ++?

I've noticed that making names with + in them doesn't work the way it should. Including in the release of arc3 that I downloaded ten minutes ago, just to check if it was still there.

  arc> (def 1+ (x) (+ x 1))
  #<procedure: 1+>
  arc> (1+ 2)
  Error: "Function call on inappropriate object 1 (2)"
  arc> 1+
  1
  arc> (def one-plus (x) (+ x 1))
  #<procedure: one-plus>
  arc> (one-plus 2)
  3
As you can see, when I changed the name but left everything else the same, it worked. Therefore, the name is the problem. Here is another example.

  arc> (mac ++mod (n m (o d 1)) `(= ,n (mod (+ ,n ,d) ,m)))
  #3(tagged mac #<procedure>)
  arc> ++mod
  #<primitive:modulo>
  arc> (= x 1)
  1
  arc> (++mod x 5 10)
  Error: "modulo: expects 2 arguments, given 3: 1 5 10"
  arc> (mac inc-mod (n m (o d 1)) `(= ,n (mod (+ ,n ,d) ,m)))
  #3(tagged mac #<procedure>)
  arc> inc-mod
  #3(tagged mac #<procedure>)
  arc> (inc-mod x 5 10)
  1

-----

1 point by thaddeus 5985 days ago | link

I don't think this is a bug, I think it's a sacrifice pg is making for the sake of using the '+' as a special operator.

Only he can say for sure.

I've just substituted all my '+' names to 'up' and, in my mind, it's just as meaningful.

ie upmod or modup .....

-----

0 points by rs 5982 days ago | link

correcto!

-----

More