The Flexible Series as a Core Concept of REBOL

This is the first of several articles I’m going to write about the REBOL programming language. To learn more about it, you can visit rebol.com. But my hope is to demystify some of its strengths and weaknesses in a way that their website currently does not, so if you read what I write first then it might help. :)

(Clear Warning: REBOL is not “free as in freedom” software, and no commitment has been laid out for how the commercial scaffolding which supports its development would be phased out. I know of no published statement that RT would not sue ORCA or other open-source efforts to implement the language. Until these issues are resolved, I consider it only an interesting thing to study and do *not* suggest its use in important projects. While REBOL may rebel against complexity, I think the rebellion for freedom is more fundamental—and the infrastructure we build on is too crucial to be left in the hands of one company that decides who may use a tool and how.)


REBOL is built on the foundational concept of a flexible series, and that’s what I’m going to focus on here. It’s one of the many things that give the language a kind of elegant uniformity. In my article about Computer Languages as Artistic Medium, someone likened it to modeling clay (mold is even a keyword in the language). I thought that might even make a good marketing visual and made a prototype, so keep the words “flexible”, “artistic”, “uniform”, “workable” and “fun” in mind.

My rendition of the REBOL logo in clay

How REBOL is like other Interpreted Languages

At first blush, REBOL’s series might remind you of other languages. For instance, LISP has Sequences that are used to represent not only data, but also the program code itself. A simple LISP program to print “Hello World” could look like this:

(print "Hello world")

LISP programmers understand that although that looks like source code, the interpreter also thinks of it as a “list” with two items. The first item is a token for the print command, and the second item is a string.

Depending on what suits our purposes, we can treat this information as data or code. If we use a certain evaluator, we will get the desired effect of printing out the string. Or we could write some other code that would process the list in some way. Let’s look at this principle in action with REBOL.

(Note: In the samples below, I’m showing code being evaluated in the REBOL interpreter. When you see two greater than signs, that’s just the command line prompt for the interpreter >> When you run something, it will print any output… and after that, if the expression evaluated to a value, it will show the value next to two equals signs ==.)

>> first [ print "Hello world" ]
== print

The expression evaluated to print because the print command is the first element in the list. Notice that REBOL uses brackets the way LISP uses parentheses. If you use the more generalized method of picking an element by its index position, you can get the second item in the series, which is a string in this case:

>> pick [ print "Hello world" ] 2
== "Hello world"

Now let’s do some “meta-programming”… a bit of code which manipulates our HelloWorld program a little bit before running it. You could use the replace command, and get a representation of a program that would print a different message:

>> replace [print "Hello world"] "Hello world" "Goodbye world"
== [ print "Goodbye world" ]

To take that one step further and actually run this new program, you’d evaluate the expression using do:

>> do replace [print "Hello world"] "Hello world" "Goodbye world"
Goodbye world

This style will come as no shock to anyone who’s worked with interpreted languages with heritage from LISP. My own first exposure to such thinking was actually John Ousterhout’s wildly popular Tool Command Language (Tcl).

The Series! Interface: Turtles All the Way Down

There’s a funny part in Stephen Hawking’s Brief History of Time, where he gives a retelling of a story known in science circles:

A well-known scientist (some say it was Bertrand Russell) once gave a public lecture on astronomy. He described how the earth orbits around the sun and how the sun, in turn, orbits around the center of a vast collection of stars called our galaxy. At the end of the lecture, a little old lady at the back of the room got up and said: “What you have told us is rubbish. The world is really a flat plate supported on the back of a giant tortoise.” The scientist gave a superior smile before replying, “What is the tortoise standing on?” “You’re very clever, young man, very clever,” said the old lady. “But it’s turtles all the way down!”

Once you get past the oddity of what’s on REBOL’s full plate, it can start to look surprisingly like turtles all the way down. For instance, when you write:

[ print "Hello world" ]

What you’re getting under the hood of the interpreter is a lot more like if you’d written it as:

[ print [ #"H" #"e" #"l" #"l" #"o" #" " #"w" "o" #"r" #"l" #"d" ] ]

This is because REBOL makes many data types expose the Series! interface. So strings and other objects can be treated like lists even if they are not “lists”. So let’s try using some of the commands we saw working on blocks operate on strings instead:

>> first "Hello world"
== #"H"
 
pick "Hello world" 2
== #"e"
 
>> replace "Hello world" #"o" #"a"
== "Hella world"

If you’re only vaguely familiar with LISP you might want to say that it does this too, since Strings are actually Sequences. But LISP’s Strings and Vectors are in truth a subclass of Sequence known as “Array”, which have the nasty property that you can’t change their size once they’ve been created! The results aren’t pretty:

* (defparameter *myString* (string "Karl Marx"))
*MYSTRING*
 
* (subseq *myString* 0 4)
"Karl"
 
* (setf (subseq *myString* 0 4) "Harpo")
"Harpo"
 
* *myString*
"Harp Marx"
 
* (subseq *myString* 4)
" Marx"
 
* (setf (subseq *myString* 4) "o Marx")
"o Marx"
 
* *myString*
"Harpo Mar"

Whether you know LISP or not, I’ll just say that the credibility toward elegance sort of falls apart there. Here’s the equivalent code in REBOL, showing how it should have been done:

(Note: If you’re new to REBOL, you’ll notice that it doesn’t use equals signs for assignments. It uses a name followed by a colon and then a value, such as numberOfPizzas: 10. Another thing is that functions support optional parameters known as “refinements” via slashes, so really just model copy and copy/part as two completely different functions for now.)

>> myString: "Karl Marx"
== "Karl Marx"
 
>> copy/part myString 0 4
== "Karl"
 
>> head change/part myString "Harpo" 4
== "Harpo"
 
>> myString
== "Harpo Marx"
 
>> skip myString 5
== " Marx"
 
>> insert skip myString 5 " of the Brothers"
== " Marx"
 
>> print myString
"Harpo of the Brothers Marx"

This is a powerful concept, especially because there are 13 fundamental types in REBOL that have this behavior. Here they are:

block! Blocks of values
paren! Blocks of values enclosed in parentheses
path! Paths of values
list! Linked lists
hash! Associative arrays
string! Character strings
binary! Byte strings
tag! HTML and XML tags
file! File names
url! Internet uniform resource locators
email! Email names
image! Image data
issue! Sequence codes

REBOL has Safe Iterators Built In

Iterators aren’t a new thing. In fact, the C++ standard template library was built on the notion of generic algorithms that work with any object that elects to expose the iteration interface. You can binary search characters in a string using the same code that would search an array, or a linked list, or a hash table. Sorting and many other operations are defined abstractly in a powerful fashion.

Yet doing things “the right way” in C++ took a long time to standardize. These features were an afterthought… and many people who program in C++ are either oblivious to them or avoid learning how to use them effectively. In part, that’s because it looks like some crazy moon language:

std::string myString = "Hello world";
std::string::iterator myIterator = myString.begin();
while(myIterator != myString.end()) {
    cout << *myIterator << std::endl; 
    myIterator++;
}

But REBOL took generalized iteration to heart and implemented this very important interface within the core interpreter. The result is efficient, terse, and every program takes it for granted. Here’s a line-by-line parallel of the above C++ code in REBOL:

myString: "Hello world"
myIterator: head myString
while myIterator <> tail myString [
    print (first myIterator)
    myIterator: next myIterator
]

Just as the C++ program could work with a linked list as input instead of a string, the REBOL program could work with any data type that exposes the Series! interface. But Series! in REBOL have some other interesting properties you don’t have in C++.

For instance, there is no separate type for iterators. They are merely themselves references to Series!. When you use operations like next, what you get back is a subsequence of the original series which starts at the element you are currently referencing and goes to the end:

>> myString: "Hello world"
== "Hello world"
 
>> myIterator: head myString
== "Hello world"
 
>> myIterator: next myIterator
== "ello world"
 
>> myIterator: next myIterator
== "llo world"

The entire series is reference counted, and any sub-series you have indexing into it keeps the data alive…even if you destroy the original series reference! You can still find the head of the series again when this happens.

>> unset 'myString
 
>> print myString ; ** Just to prove it's gone **
** Script Error: myString has no value
** Near: print myString
 
>> print myIterator
== "llo world"
 
>> myIterator: head myIterator
== "Hello world"

There are some unusual idioms in REBOL programming that may confuse and frighten one’s sensibilities. Plus, the behavior is a moving target—early versions of REBOL had the forall construct actually modify the series reference you are looking through, leaving it at the end of the series when it is done. You had to manually reset it to the head if you wanted to iterate it again, like this:

>> colors: [red green blue yellow orange]
== [red green blue yellow orange]
 
>> print colors
== [red green blue yellow orange]
 
>> forall colors [print first colors]
red
green
blue
yellow
orange
 
>> print colors
== []
 
>> colors: head colors
== [red green blue yellow orange]
 
>> print colors
[red green blue yellow orange]

This is no longer the case in REBOL 2.7.6, the series is reset. Tweaks and changes like this abound, showing that the language is still evolving and being refined—with even more drastic changes coming in 3.0. Yet the idea of encouraging clean-up of ideas that didn’t work stands in contrast to the bend-over-backwards compatibility that makes other systems so hard to work with as time goes on.

One bit of bad news when compared with C++ iterators is that you can’t make new object types in REBOL with the interface. This reveals you can’t loop through the integer 304 as if it were decimal digits (e.g. [3 0 4]) or its binary representation (e.g. [1 0 0 1 1 0 0 0 0]). You also can’t loop through a date as if it were a three-element list containing the month, year, and day (e.g. [20 Aug 2010]). If you want something other than these 13 types to be iterated like a series, you must physically build a series out of your object and iterate that.

In Conclusion

REBOL gives you a kind of universal iteration and homogeneous structure that LISP seemed to promise but failed to follow all the way through on. Though the designers of the C++ Standard Template Library made a better shot and designed a system that is arguably more general than REBOL’s, the awkward syntax and steep learning curve has kept it out of reach of some of the programmers who need it most. Also, REBOL is a fun and quick interpreted language—keeping the substrate that made Tcl and LISP so popular.

So indeed, there is something special about the specific trade-offs in REBOL series!. I hope I’ve shed a little bit of light on that specialness. But you can find out more about it by reading the Series Documentation… as well as some specific documentation for block! series and string! series.

If you’re somewhat religious about programming, and you’re sick of dealing with scripting languages that aren’t interesting, give REBOL a look. It’s unusual, but working with it can boost both your productivity and your morale. Feel free to comment below if you want to share your thoughts on this or correct any errors I’ve made.

Tags:

6 Responses to “The Flexible Series as a Core Concept of REBOL”

  1. Speaker of the moon language Says:
    #include <iostream>
    #include <string>
    int main(int argc, char *argv[]) {
        std::string s = "This is a string.";
        for ( std::string::iterator it = s.begin(); it != s.end(); ++it)
            std::cout << *it << std::endl;
        return 0;
    }

    Both iostream and string are part of the C++ Standard Library (the “default” installation, if you will). C++ can be quite concise and elegant.

  2. Hostile Fork Says:

    Hi there! Always glad to find someone reading this blog! :)

    I updated the article a little since last night and added some more information….and a picture. I’ve made a few better arguments, and did a little digging on LISP and contrasting it more accurately (see the section “Turtles All The Way Down”). While I was at it, I tried to say what I meant about the C++ iterators a little more clearly. So if you have time to re-skim you might find it got better!

    Truthfully, I speak Bjarne’s moon language pretty well too. But I still remember before I used STL and how alien it seemed. Everyone I knew avoided it, so I figured I would too. (Though today when I meet people who don’t accept modern C++ I beg them to look at Guide To Learning C++ as a New Language.)

    Bjarne has said “As I have often said, I consider not shipping a larger standard library my biggest mistake.” And the STL really did come onto the scene until late in the game. C++ started being known to the public by its current name in 1993, and had made it out of academia and labs to reach the mass market as Turbo C++ in 1991. The earliest version of the STL that was public that people could start trying for themselves was in 1994, but you had to download it off the Internet.

    1997 saw the first version of Microsoft Visual C++ shipping the STL in the box, even while most of its documentation was still pushing their own MFC Collection Classes on developers. Today the same factionalization continues even in more open projects. Just look at the Qt Collection Classes, or the wxWidgets string type…Mozilla doesn’t use any of these in its internal code, and it’s one of the most prominent C++ programs!!! Everyone can’t be an idiot, so there must be something fundamental here.

    On its native classes, REBOL does just about all the C++ iterators do and more, without needing an iterator class that’s distinct from the object being iterated. It’s interesting to see the LISP ideas meshed so artfully with the best aspects of C++ iteration. Not that REBOL doesn’t have crazy maddening things in it, but I’ll write about those in the next article. :)

    Thanks for commenting!

  3. Gregg Irwin Says:

    Someone noticed that you’re using an older version of REBOL. In newer versions, FORALL remembers the series position, and returns the result of the last body evaluation. The behavior when break/return was used in the body was also considered during its rewrite.

    Because many REBOL functions are written in REBOL themselves (they’re called mezzanines, while funcs written in C are called natives), it’s easy to change them to work how you want. That may make them incompatible with the standard versions, which is a problem, and getting a new version accepted as standard isn’t easy. An enormous amount of effort goes into the design and evaluation of even simple functions. On the plus side, the community loves to see new ideas and talk about the best way to do things.

  4. Hostile Fork Says:

    Hi Gregg, thanks again for another follow up. I updated the article. I’m using REBOL 2.7.6 actually, but I may have copy-pasted that example from somewhere instead of actually running in the interpreter. So much for journalistic integrity, eh?

    Someone pointed me toward ORCA:

    http://freshmeat.net/projects/rebol-orca/

    Since you seem close to the REBOL community, I am wondering if there is an official stance on disposition toward a competing GPL interpreter kernel. Could it use the same mezzanine functions that the canonical version of REBOL does? Can an official statement be made on what is and isn’t allowed in terms of creating a clone, so people working on such an effort do not waste their time, only to face lawsuits when the project becomes big enough to be a “threat”?

  5. sikanrong Says:

    Couldn’t hate it more. Syntax is confusing and generally annoying. Don’t know if I could ever familiarize myself with it, or why I would want to. Doesnt seem to offer any serious strengths. Also probably worthless until it’s “Free as in free” and “Open as in source”.

  6. Hostile Fork Says:

    Hi SiKaNrOnG,

    Well, certainly your feelings are not uncommon (and I share the opinion on the open source bit). Beyond that, it’s just generally hard to convince someone to give almost anything “new” a chance. People are busy.

    For instance I haven’t learned to type on a Dvorak keyboard. Even though I type all day, I refuse to try it without serious incentive. Every time I go to another developer’s terminal to look at something and they have to go switch the keyboard settings back to standard I roll my eyes. It’s definitely points off my evaluation of a developer if they do that sort of thing (due to the fact that it’s all pseudoscience).

    I’m willing to believe REBOL would fall in the category of things that will be perpetually off your radar. But this set of articles here are an attempt to explain why I find the novelty of REBOL interesting. Then again, I don’t demand something be completely practical to think they’re worth looking at. Turing Machines sure aren’t very “useful”, but everyone who programs should become familiar with them at some point.

    If you don’t like weird language & platform projects (like Erlang and Squeak and such) then you’re almost certainly not going to like REBOL either. People who feel that languages which are “Java, more or less” stay out of their way and let them achieve what they want in software will not be motivated to learn REBOL…

Leave a Reply


Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported
Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported