Cutting

I did some long overdue Chicken hackery yesterday, and by accident I found out that Chicken's (or rather, SRFI-26's) cut/cute macros are not the same as Arc's [ ] syntax after all.

Scheme/Arc aficionados already knew this, of course, but to me it was news since I've never really used cut much. Here's a short explanation.

Scheme's cut produces an anonymous function that takes as many arguments as there are <> symbols at the "top level". E.g.

> (cut + 1 <>)
#<procedure (? g3)>
; has one argument

> (cut + 1 <> <>)
#<procedure (? g4 g5)>
; has two arguments

; etc...

However, any <> that is found in a nested expression, is not considered as a parameter. Therefore:

;this works...
> (map (cut + 1 <>) '(1 2 3))
(2 3 4)

; but this does not:
> (map (cut + 1 (* 2 <>)) '(1 2 3))
Error: bad argument count - received 1 but expected 0: #<procedure (?)>

By contrast, Arc's [ ] syntax produces an anonymous function that expects one and only one argument, which is represented by a single underscore. As a result, it's simultaneously more and less limited than cut:

arc> (map [+ 1 _] '(1 2 3))
(2 3 4)
arc> (map [+ 1 (* 2 _)] '(1 2 3))
(3 5 7)
; no problem

; but two parameters doesn't work:
arc> (map [cons _ _] '(1 2 3) '(4 5 6))
Error: "#<procedure>: expects 1 argument, given 2: 1 4"

Of course, for anonymous functions that are more complex than this, it's probably better to just use lambda... :-/ Or a named function.

:: Comments (2)

Modules

I noticed there's a new version of Arc. According to the web page, "The most dramatic change is probably the ability to use x.y and x!y as abbreviations for (x y) and (x 'y) respectively."

The x.y syntax reminds me of one of the things I miss in Scheme: a "Pythonic" module system. By "Pythonic" I mean, that you can import modules, and get a module object back, that you can access using x.y syntax. I'm mostly interesting in the namespace issue here, as the convention in Scheme seems to be, to just stick everything in the global namespace.

In other words, I am somewhat uncomfortable that it's not possible to do this:

;; --- foo.scm ---

(define (bar x)
  (+ x 1))

;; --- REPL ---

> (import foo)
> foo
#<module foo>
> (foo.bar 3)
4
> foo.bar
#<procedure (foo.bar x)>

...or something to that effect.

Maybe this is an irrational "need", but I like namespaces, and have gotten used to them over the years, and loading the toplevel namespace with lots and lots of definitions just seems a bit "unsafe" to me.

I found a comparison of Python's and PLT Scheme's module systems, which explains the issue better:

Once we've done '(require (lib "math.ss"))', we have access to the internals of the math library. But there's one surprise: unlike Python, 'math' itself is not a first-class object. By default, the require form has the same semantics as Python's "from [module] import *"!

The article then mentions the following technique to add a prefix to the names imported from a module:

> (require (prefix math. (lib "math.ss" "mzlib")))
> math.e
2.718281828459045

I suppose this would not be too bad as an alternative, although you still cannot do things like inspecting a module, pass it around, etc. Chicken does not seem to support it though, and Schemers generally don't seem to miss the feature. (Or maybe there's a reason why it would be a bad idea in Scheme.) So maybe I should just learn to live without it. Thoughts welcome...

:: Comments (4)

Arc-macros, baby. Arc-macros. (part II)

More useless reader macros ahead. This time, let's implement Arc's [ ... _ ... ] lambda shortcut. In Chicken Scheme, of course. ^_^

I will be using { } rather than [ ]. Incidentally, Chicken already supports { } as an alternative for ( ):

> '{1 2 3}
1 2 3)
> {+ 1 2}
3

This reader macro will override that. Anyway, the code follows. It's clumsy (although not as clumsy as my first version), and there's probably a better/shorter way to do it, that I'm not aware of yet. In any case, it's naive; for example, this version does not allow nesting the { } constructs. Also, the replace-underscore function won't replace underscores in nested lists. 1) And so on. Remember that this is just for demonstration purposes before pointing out the zillions of flaws. :-)

(define (read-until-next-curly port)
  "clumsy way to read up until the first }."
  (read-token (lambda (c) (not (char=? c #\}))) port))

(define (replace-underscore exprlist)
  (define (replace-underscore-item y)
    (if (equal? y '_) 'x y))
  (map replace-underscore-item exprlist))

(define (make-lambda-form exprlist)
  `(lambda (x) (,@exprlist)))

(define (eval-string s)
  (with-input-from-string s read))

(define (transform-into-lambda s)
  (let* ((expr (eval-string s))
         (expr2 (replace-underscore expr)))
    (make-lambda-form expr2)))

(set-read-syntax! #\{
  (lambda (port)
    (let* ((s (read-until-next-curly port))
           (t (string-append "(" s ")")))
      (read-byte port) ; read trailing '}'
      (transform-into-lambda t))))

Testing...

(print { + _ 1 })     ; => #<procedure (? x)
(print ({+ _ 1} 44))  ; => 45

(print (map { * _ 2 } '(1 2 3 4 5 6)))
; => (2 4 6 8 10 12)

Cool. Of course, if you use a lot of small lambdas, then it's possible to write a macro that shortens things for you, rather than having to resort to syntax changes. For example:

> (define-macro (fn . body)
>   `(lambda (_) ,body))
> (fn + _ 1)
#<procedure (? _)>
> ((fn + _ 1) 44)
45

;; compare:
> ((lambda (x) (+ x 1)) 44)
45

Anyway, much like the previous post, this was just an excuse to explore reader macros a bit more. So don't shoot me...

1) On a side note, why doesn't SRFI-1 have a function to replace items in a list?

:: Comments (3)

Arc-macros, baby. Arc-macros.

OK, this is not really about macros, it's really a reader hack, but bear with me... :-)

Let's implement Arc's "negation" operator (~) in Chicken. It's only a few lines of code.

(define (negate pred)
  (lambda (x)
    (not (pred x))))

(set-read-syntax! #\~
  (lambda (port)
    (let ((expr (read port)))
      (list 'negate expr))))

That's all. Test test... (Code below uses SRFI-1 for filter.)

> (use srfi-1)
; loading library srfi-1 ...
> (filter ~odd? '(1 2 3 4 5 6 7 8))
(2 4 6 8)
> (filter ~(lambda (x) (> x 2)) '(0 1 2 3 4 5 6))
(0 1 2)

This probably falls in the category "don't try this at home". But if you're coming from languages where this just isn't possible, this kind of stuff is really cool.

Note #1: you can just use (negate odd?) and such, which is slightly longer but probably cleaner. And that kind of thing would work in Python as well. (Although function composition isn't all that popular in Python-land.)

Note #2: This implementation just reads the expression that follows ~, then feeds it to negate. So using a lambda works as well (but kind of defeats the purpose, which is conciseness).

By the way, the reader extension I showed a few days ago (to add Awk-like $N behavior) works, but I found out that it's not really how you're supposed to write it. At the point something like $3 is read, we're *reading* rather than *evaluating*, so it should really expand to something that returns the right result *when evaluated later*. So better code would be:

(set-read-syntax! #\$
  (lambda (port)
    (let* ((s (read-number port))
           (i (string->number s)))
      (list 'field i))))

In other words:

  • when the reader encounters $3, it expands it to the form (field 3) (but *does not* evaluate it at that point)
  • later on, we evaluate (field 3) and get the correct result, using whatever is in *fields* at the time of evaluation

So yeah, maybe this is like macros after all, but I don't know the correct term. :-)

There's another post like this coming up, using Arc as an excuse to tinker with the Scheme reader.

Update (2008-02-04): Here is similar code in Python, sort of. The additional "syntax" (which is actually, creating a class that defines "~", and then wrapping functions in it) seems to be more trouble than it's worth. If you do this kind of thing a lot in Python (probably not, but you never know), you might be better off using a simple negate function, e.g.

def negate(f):
    return lambda *args, **kwargs: not f(*args, **kwargs)

(I normally would not have used lambda, but after more than a month of Scheming, it's hard not to. :-)

:: Comments (3)

Arc, first impressions

I took a quick look at Arc. If nothing else, I want to see how it compares to Scheme. Here are a few remarks (in no particular order).

  • = is used for assignment rather than for equality testing. This may be confusing to people using other variants of Lisp. Possibly less so to users of languages that use the same operator. Still, inside parentheses it looks a bit odd: (= x 4)
  • Many abbrevations! Quite a few of them seem gratuitous; I'm used to def, but what about mac, rem, pr, prn, fn, o, and so on, some of which only shave off a character or two. It makes the resulting code a bit shorter, but whether it's clearer is a different issue.
  • The [ ... _ ... ] syntax seems useful. This is basically a shortcut for simple lambdas, e.g. [+ _ 1] is equivalent to (fn (x) (+ x 1)).
  • Odd assignment rules for lists and hash tables. E.g. if airports is a hash table, then (airports "Boston") looks up the key "Boston" in there. This also works in assignments: (= (airports "Orlando") 'mco). Lists work the same way, if I understand correctly. Personally, I think it's a bit confusing, and I'm not sure it's a good idea to overload this kind of syntax. (foo bar) can now mean: function application, hash table lookup, list lookup...
  • On the other hand, you get to do table lookups with map:
    (map airports '("San Francisco" "Orlando" "Paris"))
    => (sfo mco cdg)
  • On a side note, the use of hash tables for dictionaries, and the keys and vals functions to get keys and values, look suspiciously Pythonic. :-)
  • Compose functions with ":":
    odd:car is equivalent to (fn (x) (odd (car (x)))
  • "Negation" operator ~:
    ~odd? is equivalent to (fn (x) (not (odd? x)))
    N.B. It's not hard to do the same thing in Chicken... I already talked about defining custom literals in a separate post, and an implementation of ~ would use the same mechanism. More about this later.
  • if with more than 3 arguments is a nested if... e.g.
    (if a b c d e) => (if a b (if c d e))
  • Some forms were reinvented without parentheses, which is much clearer, IMHO. E.g.
    (let x 1 ...body...)
    (with (a 1 b 2) ...body...)
    Compare this to the parentheses-fest that is let in Scheme and Common Lisp. :-}

No "conclusion" at this point; I have only skimmed the tutorial and tinkered a bit with the REPL. Plus, it's likely to change anyway.

On forums, the biggest gripe people seem to have is the lack of Unicode support, and PG's apparent unwillingless to add it.

:: Comments (9)

Arc!

Who cares about the Florida primaries when there is some *real* news? Arc is out. (Took them long enough. :-) The official site is here. I will probably download it and give it a spin tonight, after wrestling through the tutorial, forum discussions, and comment threads on Reddit and Y Combinator.

More about this later, without a doubt.

(First thought: It's built on top of MzScheme. Can we do s/MzScheme/Chicken? :-)

:: Comments (1)