Wakatta!

Like Eureka!, only cooler

Seven Languages in Seven Weeks Io Day 3

Third and final day with Io (as far as the book is concerned). This time Io metaprogramming abilities are used to bend the language into strange shapes.

Of course, metaprogramming bends the mind just as much at the language, and this last day is quite a ride.

Everything is up for redefinition, and new syntactic structure can be added as well. Operators were covered in Day 2. Today curly braces and square brackets are covered as well. The neat thing is that the method used for the iterpretation is looked up using the same logic as other methods. So it is possible to define a square bracket syntax to access list items simply with:

1
List squareBrackets := method(i, at(i))

With the definition above, it becomes possible to use the familiar bracket syntax:

1
2
Io> list(1,2,3)[1]
==> 2

Updates are not possible (as far as I can tell), however, so for that usage it is less expressive than the [] and []= methods in Ruby.

The content inside the braces or brackets must be a comma separated list, each element acting as an argument for the curlyBrackets or squareBrackets methods (no link as I could not find any documentation for either method).

A moderately annoying problem with operator extensions is that they are not available in the file in which they are defined.

The last topic covered was concurrency. Io implements an actor model, like all the cool kids (Erlang, Scala, …). The book doesn’t go into details. And the available documentation is sparse as well. But from what I could gather, the model is cooperative concurrency, and an asynchronous message is a simple extension of the standard one, with one caveat: the call sender information is lost when asynchronous messages are used. Or more precisely, call sender does not return the original initiator of the call, but the piece of logic in the target coroutine that handles the dispatching of messages. So to return a answer, the sender must be passed as argument.

For instance:

Actors
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Agent := Object clone
Agent msg := "message"
agent1 := Agent clone
agent1 msg = "agent1 pinged"
agent2 := Agent clone
agent2 msg = "agent2 ponged"
agent1 ping := method(sender, message, cutoff,
  if(cutoff > 0,
      wait(Random value(1, 5) floor)
      "Received message: " print
      message println
      sender @@pong(self, msg, cutoff - 1)
      yield,
      yield))
agent2 pong := method(sender, message, cutoff,
  if(cutoff > 0,
      wait(Random value(1, 5) floor)
      "Received message: " print
      message println
      sender @@ping(self, msg, cutoff - 1)
      yield,
      yield))
agent1 @@ping(agent2, agent2 msg, 5)
Coroutine currentCoroutine pause

The code above will spawn two agents, and they will exchange messages (5 times here).

Exercises

Today’s exercises are only on metaprogramming, essentially syntax extension.

Indenting XML output

To indent properly, the Builder must keep track of the nesting depth. This is done with a slot, and a few utility methods. The depth slot is the nesting depth, it is changed with nest (increase) and unnest (decrease), which should bracket the code that processes children. Finally, indent emits the required amount of blank space.

(builder_indent.io) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Builder := Object clone
Builder depth := 0
Builder indent := method(depth repeat("  " print))
Builder nest := method(depth = depth + 1)
Builder unnest := method(depth = depth - 1)
Builder forward := method(
  indent
  writeln("<", call message name, ">")
  nest
  call message arguments foreach(
      arg,
      content := self doMessage(arg);
      if(content type == "Sequence", indent; writeln(content)))
  unnest
  indent
  writeln("</", call message name, ">"))
Builder ul(
  li("Io"),
  li("Lua"),
  li("JavaScript"))

produces the following

1
2
3
4
5
6
7
8
9
10
11
<ul>
  <li>
    Io
  </li>
  <li>
    Lua
  </li>
  <li>
    JavaScript
  </li>
</ul>

Bracket syntax for list

This is not difficult: the implementation just creates an empty list, then append each arguments to the list, before returning it:

Bracket syntax for list
1
2
3
4
5
6
7
squareBrackets := method(
     l := List clone
     call message arguments foreach(arg,
          l append(arg)
     )
     l
)

The result is:

Building a list
1
2
Io> [1,2,3]
==> list(1, 2, 3)

Attribute syntax for XML Builder

The last exercise is a bit tricky. The code as presented in the code mixes parsing and output. The problem now is that the first argument could be the attribute list, rather than a child element. The solution is to stop printing the result as we parse it, and instead to build a string representation of the XML.

(builder_attrib.io) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
Builder := Object clone
Builder depth := 0
Builder indent := method(buf := "" asMutable; depth repeat(buf appendSeq("  ")); buf)
Builder nest := method(depth = depth + 1)
Builder unnest := method(depth = depth - 1)

Builder forward := method(
  buf := "" asMutable
  args := call message arguments
  buf appendSeq(indent, "<", call message name)
  if(args size > 0 and doMessage(args at(0)) type == "Map",
      h := doMessage(args removeFirst)
      h keys foreach(k,
      buf appendSeq(" ", k, "=\"", h at(k), "\"")))
  buf appendSeq(">\n")
  nest
  args foreach(
      arg,
      content := self doMessage(arg);
      if(content isMutable, buf appendSeq(content), buf appendSeq(indent, content, "\n")))
  unnest
  buf appendSeq(indent, "</", call message name, ">\n")
  buf)


OperatorTable addAssignOperator(":", "atPutColon")

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

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

Once loaded, it can interpret the following (using print to display the generated text):

1
Builder ul({"author": "Tate"}, li("Io"), li("Lua"), li("JavaScript")) print

as

1
2
3
4
5
6
7
8
9
10
11
<ul author="Tate">
  <li>
    Io
  </li>
  <li>
    Lua
  </li>
  <li>
    JavaScript
  </li>
</ul>

Wrapping day 3 and Io

that Io is interesting, but in the sense and to the extent that Brainfuck is interesting. And I don’t necessarily mean that in a bad way.

The terseness and uniformity of syntax achieves quite a great deal; the actor model is modern and hip, although the cooperative concurrency isn’t.

This is a language that requires commitment; it is less clear it deserves so much.

Comments