Seven Languages in Seven Weeks – Io – Day 3

Sunday, March 6, 2011

It is day 3 with Io and the subjects that we are tackling are DSLs (domain specific languages) with a help from metaprogramming and concurrency in Io.

As you can expect Io lends itself very naturally to defining DSLs due to its proverbial flexibility – after all the ability to change practically everything what it does, allows you to change the nature of Io to your specific problem domain.

In book this is explained through a simple example of defining a DSL in 13 lines of code that allows map definition in JSON like syntax. Homework 2 sets a similar but simpler goal of defining a syntax based on brackets that allows definition of lists. So here it is:

HW2: Create a list syntax that uses brackets

squareBrackets := method(
    call message arguments map(value,
        self doMessage(value)
    )
)

aList := [1, 2, 30, "forty"]
aList println
aList at(3) println

I have added squareBrackets slot that gets invoked when square brackets are encountered in the environment. This means that my method will be invoked when parser comes across [] pair. Once invoked the method inspects the activation to fetch arguments passed, arguments are then simply mapped into a list of their evaluated values.

After a quick scan of the reference documentation I noticed that Call prototype has an evalArgs method that will do it all for me. By using it solution becomes almost embarrassingly simple, so I have also added support for retrieving list elements by using square bracket notation by defining squareBracket slot on List prototype. This method gets invoked when square brackets are encountered in scope of an object cloned from List.

squareBrackets := method(call evalArgs)

List squareBrackets := method(index, self at(index))

aList := [1, 2, 30, "forty"]
aList println
aList[3] println

Another neat trick Bruce shows for tackling DSLs is redefining the “forward” method. This is the method that gets invoked when an object receives a message and doesn’t have a corresponding slot. As mentioned in day1 article the usual implementation of forward searches object’s prototypes trying to find a matching slot, but as it is just a method it can be replaced with a different functionality all together. This mechanism is then exploited to redefine a forward method on a Builder object in order to allow us to expres XML in Lisp-ish syntax. The example provided is then used as a base for two homework assignments.

HW1: Enhance the XML program to add spaces to show the indentation structure.

#!/usr/bin/env io

Builder := Object clone do(
    indent := 0

    forward := method(
        indentedText := method(text,
            "  " repeated(indent) .. text
        )

        indentedTag := method(tag, open,
            indentedText(if(open, "<", "</") .. tag .. ">")
        )

        indentedOpenTag := method(tag,
            indentedTag(tag, true)
        )

        indentedCloseTag := method(tag,
            indentedTag(tag, false)
        )

        writeln(indentedOpenTag(call message name))
        indent = indent + 1
        call message arguments foreach(arg,
            content := doMessage(arg)
            if(content type == "Sequence",
                writeln(indentedText(content))
            )
        )
        indent = indent - 1
        writeln(indentedCloseTag(call message name))
    )
)

Builder ul(
    li("Io"),
    li("Lua", ul(li("Ioke"), li("Self"))),
    li("JavaScript")
)

The first part of the forward method defines some internal helper methods to deal with indentation (I like how you can limit a scope like that), the second (business) part of method the inspects call object introduced in day 2 and renders the XML.

HW3: Enhance the XML program to handle attributes: if the first argument is a map (use the curly brackets syntax), add attributes to the XML program. For example: book({“author”: “Tate”}…) would print <book author=”Tate”>

#!/usr/bin/env io

OperatorTable addAssignOperator(":" , "atPutProperty" )

curlyBrackets := method(
    aMap := Map clone
    call message arguments foreach(arg,
        aMap doMessage(arg)
    )
    return aMap
)

Map atPutProperty := method(
    self atPut(
        call evalArgAt(0) asMutable removePrefix("\"") removeSuffix("\"" ),
        call evalArgAt(1)
    )
)

Map asAttributes := method(
    self map(k, v, " #{k}=\"#{v}\"" interpolate) join
)

Builder := Object clone do(

    indent := 0

    forward := method(

        indentedText := method(text,
            "  " repeated(indent) .. text
        )

        args := call message arguments
        tag := call message name

        write(indentedText("<" .. tag))
        if(args first name == "curlyBrackets",
            write(doMessage(args first) asAttributes)
            args = args rest
        )
        writeln(">")
        indent = indent + 1

        args foreach(arg,
            content := doMessage(arg)
            if(content type == "Sequence",
                writeln(indentedText(content))
            )
        )

        indent = indent - 1
        writeln(indentedText("</" .. tag .. ">"))
    )
)

doRelativeFile("lispML.txt")

lispML.txt:

Builder ul(
    li(
        ul({"paradigm":"Object Oriented", "typing":"Dynamic"},
                li("Io"),
                li("Ruby"))),
    li (
        ul({"paradigm":"Object Oriented", "typing":"Static"},
                li("Java"),
                li("C#"))))

So let me try to give brief explanation of what is happening here. The bit of code on the top relating to operators, Map and curly brackets will parse JSON like Map definition and expose it as a string representing the XML element attributes. By now it should be fairly straightforward to understand. The Builder’s forward method was changed to check if the first argument is a “curlyBrackets” message. If it is it receives special treatment by being converted to string representing element attributes and written to opening tag. The rest of the arguments get treated in the usual manner as in HW1.

There is a caveat which wasted a fair bit of my time (and other peoples too). When you update OperatorTable within the file, you are not changing semantics of the current file as the table was already read. So the changed behaviour only applies to any subsequently parsed files (or strings). This is why there is a separate lispML.txt file.

Next subject that book tackles is concurrency. As I expected by now Io has a very simple yet elegant approach to concurrency. From concurrency point of view every object can be seen as an actor. Messages can be passed to actors (objects) by prepending them with @ or @@. If a message is prefixed with @ it will be sent to actor’s queue and a future will be returned. Future object will become a result once the result is available. If it’s value is requested and result is not yet available it will block until it is. If a message is prefixed with @@ it will be put on actor’s queue and nil will be returned immediately.  When processing the message actor will start a coroutine which will process it asynchronously. That is pretty much all there is to it – simples. There were no homework assignments regarding concurrency but I might revisit this area to provide an example.

Let me try to sum up my impression of the Io after this brief encounter. The language is very simple and flexible (you could almost say it’s liquid like). Which I found to be a very good trait as it tries to get out of your way as much as possible and lets you do with practically  anything you can possibly come up with. This makes it exciting and fun to work with. On the flip side there is not that much of resources available for Io. And sometimes you are left wondering and trawling through mailing lists and lurking on IRC channels. Thankfully this doesn’t happen that often due to intrinsic simplicity and decent documentation on iolanguage.com which is very simple and minimal in true fashion of the language. As expected of such a dynamic language the single thread performance leaves much to be desired.

I am sure I will be returning to Io, if nothing else when working on some hobby code. Working with Io is just too much fun to leave it aside. Long live the clones!

Leave a Reply