RUBOL: A Ruby-like "Language" with Reflection

Date: 13-Nov-2009/18:52

Tags: ,

Characters: (none)

The Rubol logo
Note This is a vision statement for a pre-pre-pre-alpha project. I'm trying to illustrate what I'm doing, but please do not circulate widely yet, the code samples are currently in flux. That said... anyone who is interested in the current state of the project is welcome to contribute and help make it better.
Ruby is an interesting language with some handy features. But it's missing something... the ability to operate on its own source. Rubol has adapted many of Ruby's constructs so that they fit into a uniform JSON-like format which can be dynamically inspected, loaded, saved, and executed. It has source code as a first-class object in the runtime!
Rubol is not Ruby, nor is it an attempt to build a Ruby implementation. It merely draws strong inspiration from the convenient programming patterns of collections and yield combined with code blocks.
As in Ruby, a single line of Rubol can pack a powerful punch!!!

First, a source code comparison...

Let's start by looking at this sample Ruby code from fincher.org:
primes = [1,3,5,7,11,13];
 
# using "inject" to sum.  We pass in "0" as the initial value
sum = primes.inject(0){|cumulative,prime| cumulative+prime}
puts sum
# ^^-- this outputs "40"
 
# we pass in no initial value, so inject uses the first element
product = primes.inject{|cumulative,prime| cumulative*prime}
puts product 
# ^^-- this outputs "15015"
 
# just for fun let's sum all the numbers from 1 to, oh, say a million
sum = (1..1000000).inject(0){|cumulative,n| cumulative+n}
puts sum   
# ^^-- this outputs "500000500000"
 
#you can do interesting things like build hashes
 
hash = primes.inject({}) { |s,e| s.merge( { e.to_s => e } ) }
p hash  
# ^^-- this outputs {"11"=>11, "7"=>7, "13"=>13, "1"=>1, "3"=>3, "5"=>5}
If you're not familiar with Ruby, you might be impressed at how readable it is to declare a function that's passed as a parameter to a routine on the same line as the call!! The language can also handle collections as seamlessly as it can single values, with built-in iteration primitives that are rather remarkable.
Now let's see this converted by rote into Rubol:
primes: [1 3 5 7 11 13]

; using "inject" to sum.  We pass in "0" as the initial value 
sum: inject/initial primes [ [ cumulative prime ] cumulative + prime ] (0)
puts sum
^^-- this outputs "40"
 
; we pass in no initial value, so inject uses the first element
product: inject primes [ [ cumulative prime ] cumulative * prime ]

puts product
; ^^-- this outputs "15015"
 
; just for fun let's sum all the numbers from 1 to, oh, say a million
sum: inject/initial ruby-in [1 .. 1000000] [ [cumulative n] cumulative + n ] (0)
puts sum
; ^^-- this outputs "500000500000"

; you can do interesting things like build hashes
hash: inject/initial primes [ [s e] append s reduce [(to-string e) e] ] (make map! [])
p hash
; ^^-- this outputs {"1"=>1, "3"=>3, "5"=>5, "7"=>7, "11"=>11, "13"=>13}
It's a little different, but parallels the original pretty well. Spaces separate items without need for commas (which, we, know, from, English, is, good, for, readability). All of the lists for things like parameters and code blocks have been unified to use brackets. When notions of assignment are being used, there's a colon instead of an equals sign.
Explaining why these decisions are important is kind of a long story. So let's skip that and instead see it do something cool. After that, you might be more likely to care about those details (or at least believe they might be something other than arbitrary) :P

Reflecting the Source

You've seen a short example of Rubol doing a little of what people love about Ruby. But now let's look at one of the many things it can do which Ruby cannot:
; Store the first summation part of the previous program into a variable

sumProgram: [
    primes: [1 3 5 7 11 13]

    sum: inject/initial primes [ [ cumulative prime ] cumulative + prime ] (0)
    puts sum
]

; The program is in... a variable!  That's right!
; Now let's run it.  We use the Rubol keyword "do" which is
; different from Ruby's notion... that's available also as
; "ruby-do" if you need it!

do sumProgram
; ^^-- this prints 40!

; Now for the reflection...
each sumProgram [[codeBit] puts (mold (codeBit))]
What's the output of running that last part, you ask? It's this!!!
primes:
[1 3 5 7 11 13]

sum:
inject/initial
primes
[[cumulative prime] cumulative + prime]

(0)
puts
sum
The codeBit is not a string, but the magical function mold can convert it into one. What's so magical about it is that there is another function called load which is able to turn those strings back into executable code at runtime. It's fully dynamic!
Rubol's rearrangement from Ruby isn't just a whim. It's to bring a JSON-like uniformity to the code, precisely to support features like mold and load. While it can be confusing initially, the simplicity isn't just good for the interpreter. It helps the human reader once they've "been converted".
Let's see how you can use the Ruby detect pattern to operate on this reflected code.
; find the first Rubol block in this code, e.g. a section in brackets;
; (this may or may not be a Ruby-style "code block")

firstBlock: detect sumProgram [[codeBit] block? codeBit]

puts (mold (firstBlock))
; ^^-- this outputs [1 3 5 7 11 13]

; Now let's show it's "live"
puts collect firstBlock [[value] value * 2]

; ^^-- this outputs [2 6 10 14 22 26]
Pretty nifty, no?

Strings Attached

You're probably thinking this is too good to be true, and there has to be a catch.
There isn't and there is. I said that Rubol wasn't a language because it is merely a small set of extensions to a language called Rebol.
I didn't make Rubol to try and clone Ruby. What I wanted to do was to show that Rebol programmers are already enjoying a lot of what Ruby developers really love about the language... and a whole lot more. The native syntax and operators is different, and consists of some puzzling words (like mold and rejoin). But the Rubol abstractions push those issues a little further out.
Due to some fundamental differences, Rebol's runtime is going to sometimes be at odds with Ruby and other more conventional interpreters. It's different, and quite often those differences have interesting nuances. But be forewarned—it's one of those "a minute to learn, a lifetime to master" type of situations. :)

License

The Rubol extensions are released under a permissive MIT license.
Right now, the Rebol interpreter kernel is "free as in beer" but not "free as in freedom". No complete open source implementation of exists at this point in time. But the company behind Rebol has released a large portion of the common library under the MIT license.
There are possibilities forward for an open Rebol initiative, and I am trying to rally both Rebol Technologies Inc. and the open source community to solve the issue in a way that brings productive results and not flamewars. So visit Freebol.org for that manifesto.

More Examples

Coming soon

Getting the Source

Download it or browse the project at GitHub:

Please subscribe to the Feed Icon Atom 1.0 Feed or use Feedburner Icon Feedburner to receive updates as they are posted!!

comments powered by Disqus