Showing posts with label arc. Show all posts
Showing posts with label arc. Show all posts

Arc gets I/O right

In any significant program, a great deal of code is spent on two activities that have very little intrinsic interest: I/O and error handling. This makes them good targets for language design. If a language can make these two areas easier, it can remove a significant part of the accidental complexity of programs. Conversely, a language in which either is difficult is a language in which effort is wasted on uninteresting problems.

This is where Arc is good. Many parts of it are hacky or just not there yet, but its I/O library is strikingly convenient. There's a lot there worth seeing and maybe stealing. In particular:

  • The whole-file I/O routines, starting with readfile, are a great idea - usually what you want in a file is to read the whole thing. There should be one (file?) for reading the whole file as a string too. (The point here is not that they read the whole file in one piece, but that they handle the opening and closing - all they need is the filename.)
  • infile and outfile are nice and simple. Opening files for reading and writing are quite different operations, and there's no reason to do both with the same function. Getting rid of the mode argument also makes the functions easier to compose. This can go further: the remaining binary, text and append arguments should be separate functions too.
  • Using tostring is much simpler than having an optional stream argument to every output routine. For instance, (tostring (prall thingies "We have ") (when (no done?) (pr " and more coming!"))). Dynamic variables are a perfectly reasonable way to pass arguments, especially those like streams that tend to be used in dynamic scope - so why don't I feel good about this? Are there any cases it handles badly?
  • Printing several values separated by spaces is a common operation (especially in throwaway programs) but it needs a good name. I like prs better than what I was using (prv, for "print values") although it might conflict with other functions like "print to stream".
  • prall is another function I often want, although it feels unwieldy to me, possibly because the second argument is printed before the first. The prefix argument also seems out of place.
  • prf is (format t ...), plus it does string interpolation in case you prefer that. Where's the non-printing format (well, probably fmt in Arc)? This is definitely common enough to deserve a function, not just (tostring (prf ...)).
  • There are some other perlesque string operations - tokens and such. You can never have enough of these (even in Perl).
  • I'm not sure if w/bars is useful (for what, debugging?) but it's an interesting kind of output wrapper - it captures write operations, not characters. How about a similar wrapper that indents every line?
  • It's not an I/O routine, but ellipsize handles a common problem (truncating long strings and adding an ellipsis) that would often go unhandled without it.

I/O is really a subset of a more general category, glue, which probably accounts for more complexity than everything else put together. And it's all accidental complexity, so it should be a target for libraries. Unfortunately I don't think there are any general ways to reduce it, because there are so many different transformations that have to be done. But simplifying I/O addresses the most common kind of glue. Remarkably, considering its age and importance, this area is not solved. There are still considerable improvements to be found, and Arc is finding some of them.

I wish I could find similar improvements in error handling.

What was the problem with internal define in Arc?

According to Paul Graham, an early version of Arc had internal define, but there was a problem:

In a language with implicit local variables and macros, you're always tripping over unexpected lexical contours. You don't want to create new lexical contours without announcing it. But a lot of macros that don't look like blocks in the call expand into blocks. So we provided a second block operator, called justdo, which was like do but didn't create a new lexical contour (i.e. it is Common Lisp progn), and this is what you were supposed to use in macroexpansions.

The trouble was, I kept forgetting and using do instead. And I was thereby writing utilities with the worst sort of bug: the kind that might not show up for years, and only then in someone else's code.

Huh? My own Lisp dialect also has internal define, and I haven't had a problem with unexpected contours. Neither have thousands of Schemers. (They might point out the disagreement over define in let-syntax, and the difficulty of writing macros that expand into multiple defines, but I don't think anyone counts these among the biggest problems with the language.) I haven't even had trouble remembering to use justdo in my Lisp, possibly because it has the more distinctive name splice. What are these problems I'm supposed to be having?

I'm also surprised by how complicated the implementation was said to be:

We wrote a hideously complicated interpreter that allowed local variables to be declared this way. The ugliness of this code worried me: ugly things are generally a bad idea.

I suspect they just implemented it the wrong way. You can implement internal define quite simply by defining begin (including implicit begin!) as a macro which transforms define to letrec*. Most Schemes do it this way, and my own Lisp does too, more consistently: basic special forms like lambda and begin are actually macros over more primitive ones. It feels a little odd at first, but once you stop expecting to write in primitives all the time, it's quite comfortable. You get internal define and other macro-based luxuries without compromising the simplicity of the language kernel.

My experience with internal define has been almost entirely positive. This is so different from Graham and Morris' that I wonder if they were really doing something else. Now, how did they describe it?

In Arc we were planning to let users declare local variables implicitly, just by assigning values to them.

Were they actually creating variables by assignment? This is well known not to work, and for reasons having nothing to do with macros — ask a Python user about global. The Arc designers couldn't be repeating this old mistake. Could they?

Experience with vapor

Lispjobs says:

Arc developer, seven+ years experience required.

How ordinary. :)

(Via Planet Lisp.)

First impressions of Arc

New Lisps seem to be thick on the ground lately. Graham and Morris have just released an implementation of theirs, Arc. My initial impressions, based on reading the tutorial and the source, not on actually using the language:

  • There are a lot of novel names for familiar operators, but many of them are good. I will be stealing best and most and several others. The bad ones are too short: I don't like mac and rem; they're not common enough to require such short names, and they risk colliding with other things. There are also some cryptic compounds like w/uniq.
  • in is very convenient. I wish I'd thought of it.
  • There's syntax for op, complement and compose. (The latter two, oddly, are implemented as magic on symbols, not forms - that is, the compiler understands some naming conventions.) Would short names (op, o not, and o) suffice?
  • There are no user-defined types. Well, there are (see tag) but they only have one slot. This separates the identification and encapsulation aspects of user-defined types from the representation aspect. I'm not sure I like it, though.
  • The first libraries are aimed at web apps - not surprising, given the authors.
  • Assignment and define are the same operator. No, they're not! assignment is set. Do we never learn? And that apparently isn't confusing enough, so it's called =. Ugh.
  • Macros are distinguished dynamically: the function, not the variable, is tagged as a macro. This means a call to a variable can be compiled differently depending on what value its global binding happens to have at macroexpand time. This will become a problem. There's a comment (warn when a fn has a parm that's already defined as a macro) that suggests the authors are aware of the problem.
  • Scheme distinguishes #f from () from nil. Arc doesn't. Since lists are often passed back and forth between Scheme and Arc, this is a significant implementation headache. I think in situations like this its's best to just let the host language's data show through, since it makes little difference to the quality of the language. (And what is nil doing in there anyway?)

I find all this rather encouraging, because I think I can do better. I'd better go work on my new Lisp.