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)

Defining custom literals in Chicken Scheme

In a previous post, I briefly pondered what a Scheme-based Awk-like tool would look like. Awk has a concise syntax to access fields; e.g. $1 is the first field in a line, etc. A similar tool written in Scheme would benefit from having such syntax as well.

So, I wondered how much work it would be to add it to Chicken. Let's say, something that maps $1 to (field 1) (assuming a function called field exists, of course). As it turns out, it's not much work at all, not even for someone who has never hacked the Scheme reader (that would be me :-).

First of all, we need to look at the set-read-syntax function, which is helpfully built into Chicken. It is used like this:

(set-read-syntax! <character>
  (lambda (port)
    ...read characters...
    ...return custom value...

...where <character> is the first character of the new literal. The lambda that follows can then do custom reading from port, resulting in data that can be manipulated at will. In this case, I want the literal to start with $, then read digits, and stop as soon as a non-digit is encountered. So my code should look something like this:

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

Except that Chicken doesn't have a read-number function. Fortunately, it's not hard to write one. Here's a version using read-token. (read-token reads a character at a time and tests it against a predicate, collecting characters that match the predicate, stopping as soon as one doesn't match, and returning the collected characters as a string.)

(define (number-char? c)
  (member c '(#\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9)))

(define (read-number port)
  (read-token number-char? port))

Now let's test it with a dummy implementation of field.

(define *fields* '())

(define (field n)
  (cond
    ((< n 1) "")
    ((> n (length *fields*)) "")
    (else (list-ref *fields* (- n 1)))))

(set! *fields* (string-split "the quick brown fox jumps over the lazy dog"))

(printf "~a ~a ~a~n" $1 $9 $5)

...prints "the dog jumps". :-)

(And yeah, my code isn't perfect, but it's just for demonstration purposes.)

Much like Ruby's monkeypatching, defining custom literals is probably not something that should be used in libraries a lot, but it looks like it could be very useful in DSLs or tools like the one mentioned.

:: Comments (1)

Python vs Scheme: strings

Python and Scheme have different philosophies when it comes to strings. Scheme strings are mutable and consist of characters, which are a separate type. By contrast, Python's strings are immutable, and its "characters" are really strings with a length of one.

Also, Python uses both " " and ' ' for string literals, while Scheme only uses " ".

Aside from that, strings can be used in these languages in ways that are very similar (as opposed to e.g. C's strings which tend to involve memory allocation and pointer arithmetic). So in this post, I will be focusing on common string operations, and what they look like in both Python and Scheme.

In Python, all these strings operations work out of the box. In Scheme, some are provided by R5RS, while others are found in SRFI-13 (a very useful library which has a large number of non-trivial string operations), and yet others are included by Chicken (but not necessarily part of other Scheme implementations). In the examples below, I'm assuming Chicken with SRFI-13 imported.

Joining multiple strings »

Python has the very obvious + operator to concatenate two strings, something which won't work in Scheme; (+ "a" "b") is an error. It also has the butt-ugly str.join method to join a list of strings. Scheme has string-append (R5RS) and string-join (SRFI-13).

# Python
>>> "hello" + " " + "world"
'hello world'
>>> " ".join(['my', 'name', 'is', 'poison'])
'my name is poison'

;; Scheme
> (string-append "hello" " " "world")
"hello world"
> (string-join '("my" "name" "is" "poison") " ")
"my name is poison"

Getting the length »

These functions are very simple, but I'm mentioning them anyway because there might be surprises here for people coming from other languages. Python uses the len() function rather than a method (like e.g. Ruby and Io do). Scheme uses string-length rather than length (which only works on lists).

# Python
>>> len("koyaanisqatsi")
13

;; Scheme
> (string-length "koyaanisqatsi")
13

Substrings »

Python uses the [] syntax for indexing and slicing; it also accepts negative numbers (to count from the end of the string). Scheme has string-ref and substring, which work similarly, except they don't take negative values. (Note that string-ref returns a *character* rather than a one-length string.)

# Python
>>> s = "hello"
>>> s[0]
'h'
>>> s[2]
'l'
>>> s[1:3]
'el'
>>> s[-3:]
'llo'

;; Scheme
> (define s "hello")
> (string-ref s 0)
#\h
> (string-ref s 2)
#\l
> (substring s 1 3)
"el"

Comparing strings »

In Python, strings are compared with the usual == family of operators. Case matters; "a" does not compare equal to "A". Scheme, on the other hand, has a number of functions to do the comparison; string=? and friends for case-sensitive comparing like in Python, and the string-ci=? family for case-insensitive comparing.

# Python
>>> "abc" == "abc"
True
>>> "abc" == "ABC"
False
>>> "b" > "a"
True

;; Scheme
> (string=? "abc" "ABC")
#f
> (string-ci=? "abc" "ABC")
#t
> (string>? "b" "a")
#t

SRFI-13 also provides equivalents for Python's useful startswith() and endswith() methods:

> (string-prefix? "He" "Herbert")
#t
> (string-suffix? "tt" "Abbott")
#t

Changing case »

Speaks for itself. Note that R5RS defines char-upcase and char-downcase, but not string-upcase or string-downcase (those are in SRFI-13).

>>> "kibbles and bits".upper()
'KIBBLES AND BITS'
>>> "KIBBLES AND BITS".lower()
'kibbles and bits'
>>> "kibbles and bits".capitalize()
'Kibbles and bits'
>>> "kibbles and bits".title()
'Kibbles And Bits'

;; Scheme
> (string-upcase "kibbles and bits")
"KIBBLES AND BITS"
> (string-downcase "KIBBLES AND BITS")
"kibbles and bits"
> (string-titlecase "kibbles and bits")
"Kibbles And Bits"

Splitting »

Splitting a string into a list of smaller strings is a common thing to do in high-level languages. Luckily, for common cases, we don't have to resort to regular expressions. Python uses the split() method, Scheme has string-tokenize (SRFI-13) and string-split (Chicken).

# Python
>>> "a few good men".split()
['a', 'few', 'good', 'men']
>>> "abracadabra".split("b")
['a', 'racada', 'ra']

;; Scheme
> (string-tokenize "a few good men")  ;; SRFI-13
("a" "few" "good" "men")
> (string-split "a few good men")     ;; Chicken built-in
("a" "few" "good" "men")
> (string-split "abracadabra" "b")
("a" "racada" "ra")

Trimming »

To trim characters from the left and/or right side of a string, Python uses the lstrip (from the left), rstrip (from the right) or strip (both sides) methods. Somewhat asymmetrically, in Scheme (or, more precisely, SRFI-13) these functions are called string-trim (from the left), string-trim-right (from the right) and string-trim-both (both sides).

Both Python and Scheme allow you to specify the character(s) that need to be stripped. By default, whitespace is removed, as this seems to be the most common use case.

# Python
>>> s = "  i like cookies  "
>>> s.strip()
'i like cookies'
>>> s.lstrip()
'i like cookies  '
>>> s.rstrip()
'  i like cookies'
>>> "xxxhi!xxx".strip("x")
'hi!'

;; Scheme
> (define s "  i like cookies  ")
> (string-trim s)
"i like cookies  "
> (string-trim-right s)
"  i like cookies"
> (string-trim-both s)
"i like cookies"
> (string-trim-both "xxxhi!xxx" #\x)
"hi!"

Looping »

In Python, you can loop over a string (using for) or turn it into a list, but what you get is essentially a list of strings with length one. In Scheme, you get characters. Use string->list to get a list of characters, and string-map to map one string to another (much like the regular map, but it takes and returns a string).

# Python
>>> for c in "hello": print c,
...
h e l l o
>>> list("hello")
['h', 'e', 'l', 'l', 'o']

;; Scheme
> (string->list "hello")
(#\h #\e #\l #\l #\o)
> (for-each
>   (lambda (c) (printf "~a! " c))
>   (string->list "hello"))
h! e! l! l! o!
> (string-map char-upcase "hello")
"HELLO"

Searching »

Python has several ways to search strings for contents... like the find/rfind methods (and their index/rindex counterparts) to find the index of a matching substring, and the in operator if you just want to know if a string has a certain substring, but don't need to know where exactly it starts.

You can do the same things in Scheme, assuming you use SRFI-13, as R5RS does not define any of this. string-index searches for a character (or a character set or a predicate), string-contains searches for a substring. When found, it returns the index, otherwise #f (which is useful because it allows one to write (if (string-contains s1 s2) ...)).

# Python
>>> "lemon-flavored jellibeans".find("e")
1
>>> "lemon-flavored jellibeans".rfind("e")
21
>>> "lemon-flavored jellibeans".find("el")
16
>>> "lemon-flavored jellibeans".find("xyz")
-1
>>> "el" in "lemon-flavored jellibeans"
True

;; Scheme
> (string-index "lemon-flavored jellibeans" #\e)
1
> (string-index-right "lemon-flavored jellibeans" #\e)
21
> (string-contains "lemon-flavored jellibeans" "el")
16
> (string-contains "lemon-flavored jellibeans" "xyz")
#f

Replacing »

Python's replace() method is very easy: simply specify the substring that needs to be replaced, and its replacement. By contrast, SRFI-13's string-replace is more sophisticated. It takes a string, a replacement string, and start/end indices that indicate what part of the string needs replaced. See the example below.

# Python
>>> "I like cookies".replace("cookie", "hot dog")
'I like hot dogs'

;; Scheme
> (string-replace "i like cookies" "hot dog" 7 13)
"i like hot dogs"

I don't know if there's a version that is easier to use floating around somewhere (in a SRFI or otherwise), but it's not so hard to write something that emulates the Python behavior:

(define (string-replace-v2 s before after)
  (let ((idx (string-contains s before)))
    (if idx
        (string-replace s after idx (+ idx (string-length before)))
        s)))

Also, Chicken has string-translate*, which works for our purposes, but is used with a table of elements to be replaced:

> (string-translate* "i like cookies"
>   '(("cookie" . "hot dog")))
"i like hot dogs"

:::

This has become a long post, longer than I intended, and there are still many things I haven't even touched upon yet... like Unicode, or the fact that some of the aforementioned functions have equivalents that change the string in-place, rather than returning a new string. Anyway, this wasn't meant to be a complete reference; it's more of a starting point, or a quick way to look up "I can do X in Python, how do I do it in Scheme?"

Further reading:

:: Comments (5)

Interlude: Scheme command-line tool idea

While I'm preparing a long Python-vs-Scheme post, here's a bit of filler. :-)

Check this. No, not the part about Common Lisp; what I'm interested in right now, is the snippet of Awk code, and the question whether a similar tool can be written in Scheme.

To put it more precisely, would it be possible to have a command-line tool, using Scheme as its language (and probably written in Scheme as well), that lets users write one-line queries like that?

It would be a cool thing to have, although I'm not sure if nested parentheses mix so well with the command line. Writing Scheme code in an editor is different from writing it at a shell prompt.

The Awk example could look something like this:

<name> '(begin (set-fs! ":")) (if (equal? $6 "/sbin/nologin") (print $1))' /etc/passwd

(It would have to use some reader manipulation to allow for expressions like $6 meaning (field 6), or something like that. Chicken already uses the $, so maybe a different syntax would be preferable.)

In any case, I am pondering this. The idea is to have a tool that is powerful, relatively easy to use, and still Scheme-y. Of course, the world probably doesn't need a new Awk, but I am exploring this idea as a coding/design exercise.

:: Comments (2)

Simple unit testing with Chicken Scheme

I'm currently translating a small toy project of mine to Chicken Scheme. Since the original (written in Python) has unit tests, it made sense to look for similar functionality for Chicken. As it turns out, there are several testing frameworks available. Since right now I'm still exploring, I settled for a relatively simple one, the test egg (by Alex Shinn).

Unlike Python's unittest, the test library is not object-oriented at all. Rather, it defines a small number of useful functions and macros, which can be used at will (e.g. nested, inside lets and loops, etc).

The most important interface is (test <expected-value> <expression>), which, naturally, checks if expression evaluates to expected-value. For example, (test 4 (+ 2 2)) would be a valid use of test. (Note that switching the arguments does *not* work; (test (+ 2 2) 4) is illegal.)

Here's some actual code:

(use test)
(load "tools")

(test-group "make-address"
  (test #xC000 (make-address #xC0 #x00))
  (test #x1234 (make-address #x12 #x34)))

(test-group "high"
  (test #x08 (high #x0801))
  (test #xC0 (high #xC000))
  (test #x12 (high #x1234)))

(test-group "low"
  (test #x01 (low #x0801))
  (test #x00 (low #xC000))
  (test #x34 (low #x1234)))

(test-group "signed->unsigned"
  (define s->u signed->unsigned)
  (test 0 (s->u 0))
  (test 20 (s->u 20))
  (test #xFF (s->u -1))
  (test #xF7 (s->u -9))
  (test #x80 (s->u -128))
  (test-error (s->u 200)))

(test-group "unsigned->signed"
  (define u->s unsigned->signed)
  (test 0 (u->s 0))
  (test 20 (u->s 20))
  (test -128 (u->s #x80))
  (test -1 (u->s #xFF))
  (test -9 (u->s #xF7))
  (test-error (u->s -1)))

The test-group form is a simple but effective way to group tests together; they show up in separate sections in test reports. It has its own lexical scope, so anything defined inside it is not visible outside of it.

When an expression is expected to produce an error, use test-error to catch this and test for it. To simply assert that an expression is non-false, use test-assert.

Of course, tests can be used inside loops, which makes it really easy to test each item in a list. Each of these tests will show up as a separate entry in the resulting test report. (Something which isn't so easy to accomplish with Python's unittest.) Some more actual code (which tests all items in a list, and defines a meaningful name for each test):

(test-group "*opcodes*"
  (define (test-opcode opcode)
    (define test-name (sprintf "test opcode: ~s" opcode))
    (test-assert test-name (member (opcode-type opcode) *opcode-types*)))
  (for-each test-opcode *opcodes*))

When running these scripts, test reports are produced that look like this:

-- testing make-address ------------------------------------------------------
(make-address 192 0) ................................................. [ PASS]
(make-address 18 52) ................................................. [ PASS]
2 tests completed in 0.002 seconds.
2 out of 2 (100%) tests passed.
-- done testing make-address -------------------------------------------------

-- testing high --------------------------------------------------------------
(high 2049) .......................................................... [ PASS]
(high 49152) ......................................................... [ PASS]
(high 4660) .......................................................... [ PASS]
3 tests completed in 0 seconds.
3 out of 3 (100%) tests passed.
-- done testing high ---------------------------------------------------------

...etc.

The test egg works for my current purposes; writing tests is easy, and so is grouping them, or running several files of tests. (Just create a file test-all.scm that loads all the other test files.) However, in the future I might want to look for a testing framework that is a little more configurable. For example, as far as I can tell, it's impossible to make small changes to the test report output, without rewriting the whole function that handles it (and registering it as the current-test-group-reporter). But I'll stick with it for now -- it's a great way to do real testing (with surprising flexibility) with little work.

Update (2008-01-24): I found it somewhat annoying that, if you have a lot of tests in separate groups, you have to scroll up in the terminal window to see if any failed, because totals are only displayed for each group, but not for all groups as a whole. However, this is easily fixed with a small shell script:

csi -s test-all.scm | grep FAIL

shows quickly if there were any failures. It's not perfect (e.g. it doesn't show which groups the failed tests were in), but it helps.

:: Comments (3)

Python vs Scheme: dictionaries

Python has a separate dictionary type. I'm assuming most of my readers are familiar with it, as it's very common; if not, please read the fine tutorial first, followed by the library reference.

In short, Python's dictionaries are mutable objects that associate unique keys with values. There is special syntax to create them.

Now, going by R5RS, Scheme doesn't even have anything similar. All it has is three closely related functions, that look up pairs in a list, based on a "key" which is matched to the first element of each pair. These functions are assq, assv and assoc. (See here in R5RS.)

Naturally, it's possible to write extensions in Scheme that look more like Python's dict (or Ruby's Hash, etc), and I'm sure people have done so; for example, SRFI-69 defines hash tables. But for now, let's see what we can do with the bare-bones approach.

Its usage is simple: you define a list of pairs, possibly augmenting them by consing new pairs onto it, or deleting elements from it. (The order doesn't really matter.) Then you use the aforementioned functions to look up "keys" (matched to the first element in each pair). If not found, #f is returned, otherwise the matching pair.

That's right; the whole pair is returned, not just the second element of the pair. By doing so, Scheme sidesteps the problem that some languages have (e.g. Ruby); it either returns a pair (found) or #f (not found), so there can never be any confusion whether the key was found or not. By contrast, in Ruby, if myhash[value] returns nil, that could mean that the value was found and that its associated value was nil, *or* that it was not found at all. (Python doesn't have this problem either; it raises a KeyError exception if the key is not found; in addition, the Ruby behavior can be emulated with the dict.get() method.)

Anyway, here's an example (R5RS only but we're secretly assuming that filter exists):

(define language-designers
  '((guido python)
    (matz ruby)
    (rasmus php)
    (larry perl)))

; add some...
(define language-designers
  (cons '(felix chicken) language-designers))

; I don't like PHP; remove it :-)
(define language-designers
  (filter (lambda (pair) (not (equal? (cadr pair) 'php)))
          language-designers))

(print (assoc 'guido language-designers))  ; => (guido python)
(print (assoc 'hans language-designers))   ; => #f

Note how the usage is completely different from Python's dicts. This can be written differently -- hell, OF COURSE it can be written differently, it's Scheme! :-) But let's stick with this approach for a minute.

(I'm using assoc here, which compares key and first element using the equal? predicate. assq and assv basically do the same thing, using the eq? and eqv? predicates, respectively. Equality testing in Scheme will be dealt with in yet another forthcoming post...)

Basically, this is all you need. Since a "dictionary" is just a list of pairs, all the usual list operators apply, and can be used to write any Python-esque dict operations fairly easily: keys, has_key, adding and removing using [], etc. (Doing so is left as an exercise for the reader. ;-)

Fortunately, the author of SRFI-1 (a list library) recognized the need for such functions, and supplied a few of them. Using the SRFI, we could write:

(define language-designers
  (alist-cons 'felix chicken language-designers))

(define language-designers
  (alist-delete 'rasmus language-designers))

... which is at least a bit clearer.

(More about Scheme lists, and SRFI-1 which is *very* necessary, in a separate post.)

:: Comments (4)

Scripting with Chicken

Today a cool script was posted to the chicken-users mailing list. Not only does it illustrate how to make an egg, it also demonstrates how to use Chicken Scheme as a scripting language.

The eggify function is somewhat long, but still relatively easy to read; the second let* part is long, but not complicated, as it can be read line by line. It almost reads like line-for-line statements, e.g.

# pseudo-translation to Python
metafile = metafiles[0]
metadata = handle_exceptions(...)
egg(metadata['egg'][0])
...etc...

And the actual body of it is just three "statements" (yeah, yeah, I know Scheme doesn't really have statements), dealing with the file system and issuing commands (like tar).

This is of course nothing special for languages that are often regarded as "scripting languages" -- like Perl, Python and Ruby -- but it's unusual to see it in Scheme. I'm delighted that it's possible. This is one of the reasons I like Chicken -- it tends to be more "real world" and "pragmatic" than some other implementations.

:: Comments

Cool listcomp, revisited

About two weeks ago I looked at a list comprehension in Haskell and Python. Since then I have also looked at eager comprehensions in Scheme (using SRFI-42). So, I just thought I'd be interesting to write that same listcomp in Scheme. Here it is...

(use syntax-case)
(use srfi-42)

(define (f n)
  (list-ec (: x 1 (+ n 1))
           (: y (+ x 1) (+ n 1))
           (: z (+ y 1) (+ n 1))
           (if (= (+ (* x x) (* y y))
                  (* z z)))
           (list x y z)))

(print (f 20))

From a readability point of view, this code might benefit from something like

  (let ((m (+ n 1)))
      (list-ec (: x 1 m)
               (: y (+ x 1) m)
               (: z (+ y 1) m)
               ...

as the ranges used by eager comprehensions don't include the upper bound, much like Python's range function. (I.e. (: x 10) includes the numbers 0 through 9, but not 10.)

:: Comments

SRFIs

In previous posts, I've mentioned SRFIs several times, without bothering to explain to non-Schemers what those are.SRFI stands for "Scheme Request For Implementation". They are a bit like Python PEPs, but there are a few important differences. Like PEPs, anybody can submit a SRFI. However, where approved PEPs become part of the Python core language or its standard library, there is no such mechanism for SRFIs. When declared "final", implementations may choose to include or ignore them. They do not become part of the Scheme standard, and there is no BDFL to approve or reject them.

The Scheme standard is intentionally "bare". This is great from a minimalistic perspective, but it's not very practical if you have to write lots of library functions yourself. SRFIs fix this problem by complementing the standard with libraries. For example, there aren't many list manipulating functions defined in R5RS. (Like, e.g., filter, or the range function I mentioned in my post about eager comprehensions.) Fortunately, SRFI-1, the list library, defines all the list functions you can think of, and then some. (More about this in a separate post.)

So, yeah, if you want to do any Scheme programming that goes beyond academic exercises, you'll definitely want to look at the SRFIs. srfi.schemers.org is the place to be, containing not only all the final requests, but also archives of the discussion lists (which are useful when you want to know why a certain SRFI has feature X but lacks Y, etc).

:: Comments

« Previous entries · Next entries »