in which you may get started with bus scheme

So a number of folks have asked me how they should get started with Bus Scheme.[1]. I've mostly just said silly things like, "Umm... good question. Maybe read/watch SICP?", which is silly because it doesn't have much to do with the Bus part of Bus Scheme, not because The Structure and Interpretation of Computer Programs is silly.

There's a poster in my favourite bookstore that has Dante's Comedy, the Iliad, and a few other classics captioned with something like "Might as well start them now; you're going to have to read them eventually anyway." I hold the same notion regarding SICP and perhaps The Little Schemer, but I could see how it'd be helpful to have an introduction to Scheme from a Rubyist's perspective since reading a book like that can be large-ish mental investment.

Scheme is a programming language directly descended from Lisp. It's most often compared to Common Lisp, which is in some senses its big brother. Scheme is usually considered less "kitchen-sink"-ish than Common Lisp in that it only defines an extremely clean small core language and allows developers to extend it seamlessly to do what they need. In the words of the creators of Scheme:

Programming languages should be designed not by piling feature on top of feature, but by removing the weaknesses and restrictions that make additional features appear necessary.

(As a potential student of Scheme, you should be encouraged by this notion as it directly translates into fewer concepts to learn.)

Ruby draws a lot of its heritage from Scheme, though Matz does not share the idea that a language should be limited to a very small number of core axioms from which everything else can be defined. [2] Destructive method names ending in "!" and predicates ending in "?" were inspired by Scheme. Matz himself has even lightheartedly referred to Ruby as "MatzLisp". So this is a language that at the core should not feel too foreign to a Rubyist, even if the syntax looks quite different.

Let's dive in. sudo gem install bus-scheme if you haven't got it installed. Go ahead and launch Bus Scheme with the bus executable. Like irb, it drops you into a REPL, or Read-Eval-Print Loop. Scheme programs are made up of expressions. When you enter expressions into the REPL, they get evaluated and their value is shown. There are only a few simple rules for how expressions get evaluated that we'll address below. Feel free to experiment with entering expressions and seeing what gets returned.

The simplest expressions are just atoms, which are simple "indivisible" values, like symbols, numeric values, and strings. Some atoms evaluate to themselves just like in Ruby, so entering 12 into the REPL returns (and echoes) 12. "foo" works the same way. Symbols are a little different. Ruby uses a colon before the symbol's name, but in Scheme you refer to a symbol just using its name. So baz refers to the symbol with the name "baz". But if you enter baz into the REPL, Bus Scheme complains:

> baz
Error: Undefined symbol: baz

This is because symbols aren't considered literals; that is, they don't evaluate to themselves like they do in Ruby. When Bus Scheme encounters a symbol in this context, it treats it as a variable and tries to return the value that's bound to it, which doesn't work when it's not bound. So let's see what happens with a symbol that already has a value bound to it:

> +

This is the way Bus Scheme represents a built-in (primitive) function. In Scheme, functions are first-class values, so you can bind them to variables, like you can with the lambda keyword in Ruby. But in Scheme this the primary way you refer to functions when you want to call them or pass them to other functions.

Speaking of calling functions, it works something like this:

> (+ 3 4)

This is a list, which is Scheme's compound expression. This list is made up of three elements, in this case all atoms: the symbol +, the number 3, and the number 4. In normal contexts, when Scheme sees a list it treats it as a function call. First the first item in the list is evaluated, which evaluates to a Ruby Proc object. Then each of the remaining list elements are evaluated. Since they're all literals here, they evaluate to themselves. Then the arguments get passed to the function. Behind the scenes, this translates rougly into{|*args| args.sum}.call(3, 4). Let's see something a bit more complicated:

> (+ (+ 1 2) (+ 3 4))

In this case, the first + gets evaluated, and Bus Scheme sees that it's a function. So it looks at its arguments: (+ 1 2) gets evaluated to 3, and (+ 3 4) gets evaluated to 7. Then those two arguments get passed to + and the result becomes the value of the whole expression.

That's the basics of how program execution happens, but you won't get far without having a few more functions under your belt. Here are a some to get you rolling:

+, -, *, and /
You've been introduced to + above, but I'm sure you recognize your other old friends from grade-school days. + and * support any number of arguments, but - and / take two. In regular Scheme these all only work for numerical types, but Bus Scheme borrows Ruby's methods and lets you pass strings and other objects to + and *.
<, >, and =
These are comparison functions. They work like they do in any language, but in Scheme you invoke them as (> 3 7) etc. Again, Bus Scheme uses Ruby's underlying methods, so you can pass strings and other objects in, unlike in regular Scheme.
If you want a list of numbers, you may think you get this by entering (1 2 3). The problem with this is that in normal contexts it gets treated like a function call, and it will complain that 1 is not a function. What you can do instead is (list 1 2 3), which evaluates to (1 2 3).
This works like Ruby's map, but it's a free-standing function instead of a method. So instead of [1, 2, 3].map {|x| x + 3} you would do (map (lambda (x) (+ x 3)) (list 1 2 3), which would return (4 5 6).
Bus Scheme's (substring "foobar" 3 5) translates into "foobar"[3 .. 5] in Ruby.
The most basic conditional is if. Use it like this: (if x "x is true" "x is false"). if evaluates its first argument, which in this case is x. If it evaluates to a true value [3] then its second argument gets evaluated and returned. If it's false then the remaining arguments (if any) are evaluated and the last one is returned.[4]

Well, that's enough for now. You may not know enough to be dangerous, but I hope you know enough to explore. Tune in next time when I uncover the true Secrets of Lisp™ by explaining cons, lambda, and special forms.

1 - I didn't say it was a large number.

2 - I imagine this causes Evan and Charles some varying amounts of distress.

3 - In Scheme every value is true except #f, which is equivalent to Ruby's false.

4 - Observant readers will note that this does not follow the evaluation rule for functions given above which states that every argument is evaluated before the function is called. This is because if is not technically a function, but rather a special form, and different rules apply for the evaluation of a special form's arguments. There's more to this than I can cover in this article, but these rules allow for great syntactic flexibility.

« older | 2008-02-18T10:36:59Z | newer »