Tuesday, July 7, 2009

Prototype based programming in Ioke

Lets look a bit closer on programming in a prototype based language. In class based object oriented programming we have the concept of classes and objects of these classes. The objects behavior is defined in the class, and new objects are created from the class. If we want to reuse behavior in the original class but alter it in some way we can either subclass the original class or delegate calls from a new class to the original one. In contrast, in a prototype based language we create new objects from already existing objects in the system. In the new object we can redefine variables and methods or choose to leave them unaltered. If the new object does not provide a definition for a certain variable the prototype object will be searched, continuing further up the chain until the topmost object in the system is reached, in Ioke called Origin. So if we want to model an elephant in Ioke we can do so by creating a mimic of Origin, and defining the behavior and state we want to use to represent an elephant on that object.

iik> joe = Origin mimic
iik> joe color = "grey"
iik> joe legs = 4

If we then want to create another elephant we can mimic Joe and redefine the cells that differs from Joe's.

iik> charlie = joe mimic
iik> charlie legs = 3
iik> charlie color
+> "grey"

Ioke also has the concept of mixins, similar to traits in Self and Scala, which allows you to share behavior between different classes that don't share a common base object. You create new mixins by mimicing the Mixins kind (strictly speaking you can use any object as a mixin but the convention is to use a mimic of the Mixins kind). The Mixins kind does not have Base in its mimic chain, so you should not create objects (other than new mixins) from the Mixins kind.

iik> runBehavior = Mixins mimic
iik> runBehavior run = method("Run for your life" println)

If we want to use this mixin in our charlie object we add it to charlie's mixin chain with the mixin! method.

iik> charlie mixin!(runBehavior)
iik> charlie run
Run for your life
+> nil

Sokoban example
If you are used to class based object oriented programming it might feel strange to not have any objects to put your behavior in. But if you use it for a while you will notice that it is quite natural. If you want to know more about modeling in a prototype based language I can recommend Organizing Programs Without Classes, a paper about programming in the self language, and Steve Yegge's The Universal Design Pattern.

To get a fell for what a program in a prototype based language can look like I decided to try to translate a Ruby program to Ioke. I chose one of the solutions to the Sokoban Ruby Quiz. You can see the solution by Dave Burt here. The problem in the quiz is to create an implementation of the Sokoban game. It's a good exercise to try to translate the Ruby program yourself. The translation was pretty straightforward. The only problem I encountered was how to read files and user input on the console. But after I found the Ioke System class and its 'in' attribute it was pretty easy. I also decided to alter Dave's solution a bit to make it even more object oriented. I introduced the Direction and Position objects and added some convenience methods.

Considerations
When using the do method after creating a new object the name of objects created inside the do will be somewhat unintuitive (at leas to me). If we create an object A with a cell B inside it these two don't give the same kind for B:

A = Origin mimic
A B = Origin mimic
A B kind
+> "Origin A B"

A = Origin mimic do( B = Origin mimic )
A B kind
+> "Origin B"

So to be able to check if the resident on a certain tile is a crate or a person I reassigned the kind cell on these objects. After that we can refer to them with the name that we want, in this case Sokoban crate and Sokoban person.

After some thought I chose to make wall, crate and person objects in the Sokoban kind and not kinds in them self. Because these objects don't hold any state we do not need separate instances of them. So we can for example reuse the same crate object for all the crates on the level. But since the Floor tiles need to remember if there is anything on them we need separate objects for each of the floor tiles. So floor is a kind that we call mimic on but wall, crate and person are plain objects that we use directly.

Here is the complete Ioke code.

Sokoban = Origin mimic do (

Tile = Origin mimic do (
asText = method("~")
storage? = method(self kind == Sokoban Storage kind)
hasPerson? = method(false)
hasCrate? = method(false)
free? = method(false)
walkable? = method(false)
solved? = method(true) ; treat all tiles as solved unless they overried this method
kind = "Sokoban Tile"
)

CharTile = Tile mimic do (
initialize = method(chr,
@ chr = chr
)
asText = method(@ chr)
kind = "Sokoban CharTile"
)

wall = Tile mimic do (
asText = method("#")
kind = "Sokoban wall"
)

Floor = Tile mimic do (
initialize = method(resident nil,
@ resident = resident
)
asText = method(
if(@ resident,
@ resident asText,
" ")
)

clear = method(
r = @ resident
@ resident = nil
r
)

hasCrate? = method(!free? && @ resident kind?("Sokoban crate"))
hasPerson? = method(!free? && @ resident kind?("Sokoban person"))
free? = method(!(@ resident))
walkable? = method(true)

cell("<<") = method(resident,
if(@ resident, error!("Can't go there - this tile is full"))
@ resident = resident
)

kind = "Sokoban Floor"
)

Storage = Floor mimic do (
initialize = method(resident nil,
super(resident)
)

asText = method(
case(@ resident kind,
Sokoban crate kind, "*",
Sokoban person kind, "+",
"."
)
)
solved? = method(hasCrate?)
kind = "Sokoban Storage"
)

crate = Origin mimic do (
asText = method("o")
kind = "Sokoban crate"
)

person = Origin mimic do (
asText = method("@")
kind = "Sokoban person"
)

Tile createTile = method(chr nil,
case(chr,
"#", Sokoban wall,
" ", Sokoban Floor mimic,
"@", Sokoban Floor mimic(Sokoban person),
"o", Sokoban Floor mimic(Sokoban crate),
".", Sokoban Storage mimic,
"+", Sokoban Storage mimic(Sokoban person),
"*", Sokoban Storage mimic(Sokoban crate),
Sokoban CharTile mimic(chr)
)
)

Point = Origin mimic do(
initialize = method(x, y,
@ x = x
@ y = y
)
)

Direction = Point mimic(0,0) do(
kind = "Sokoban Direction"

cell("*") = method(mult,
Sokoban Position mimic(@ x * 2, @ y * 2)
)
)

Position = Point mimic(0,0) do(
cell("+") = method(dir,
Sokoban Position mimic(@ x + dir x, @ y + dir y)
)

kind = "Sokoban Position"
)

NORTH = Direction mimic(-1,0)
SOUTH = Direction mimic(1,0)
EAST = Direction mimic(0,1)
WEST = Direction mimic(0,-1)

Level = Origin mimic do(
initialize = method(levelsString,
@ grid = createLevels(levelsString)
if(!playerIndex, error!("No player found on level"))
if(solved?, error!("No challenge!"))
@ moves = 0
)

kind = "Sokoban Level"

createLevels = method(levelsString,
levelsString split("\n") map(ln,
ln split("") map(c, Sokoban Tile createTile(c) )
)
)

cell("[]") = method(position,
@ grid[ position x ][ position y ]
)

asText = method(
@ grid map(row, row join) join("\n")
)

playerIndex = method(
"Returns the position of the player"
@ grid each(rowIndex, row,
row each(colIndex, tile,
if(tile hasPerson?,
return Sokoban Position mimic(rowIndex, colIndex)
)
)
)
nil
)

solved? = method(
; a level is solved when every tile is solved
@ grid flatten all?(solved?)
)

moveCrate = method(
"Move the crate in front of the player in the given direction",
cratePos, newCratePos,
self[newCratePos] << self[cratePos] clear
)

movePlayer = method(
"Move the player to the given position",
newPlayerPos,
self[newPlayerPos] << self[playerIndex] clear
)

move = method(
"Move the player in the given direction and update the level to reflect it",
dir,
pos = playerIndex
playerTarget = pos + dir
if(self[playerTarget] walkable?,
if(self[playerTarget] hasCrate?,
crateTarget = pos + dir * 2
if(self[crateTarget] free?,
moveCrate(playerTarget, crateTarget)
movePlayer(playerTarget)
),
movePlayer(playerTarget)
)
return @ moves += 1
)
nil
)
)

; command-line interface
cli = method(levelsFile "sokoban_levels.txt",
cliHelp = "

Dave's Cheap Ruby Sokoban
Ported to Ioke by Mikael Amborn
© Dave Burt 2004

@ is you
+ is you standing on storage
# is a wall
. is empty storage
o is a crate
* is a crate on storage

Move all the crates onto storage.

to move: k
|
h -+- l
|
j
to restart the level: r
to quit: q
to show this message: ?

You can queue commands like this: hhjjlll...

"
cliHelp replaceAll(#/\t+/, " : ")
cliHelp println
"Press [enter] to begin." println
System in read

FileSystem readFully(levelsFile) split("\n\n") each(levelIndex, levelString,
level = Sokoban Level mimic(levelString)
while(!level solved?,
level println
"L:#{levelIndex+1} M:#{level moves} > " print
System in read asText split("") each(c,
case(c,
"h", level move(Sokoban WEST),
"j", level move(Sokoban SOUTH),
"k", level move(Sokoban NORTH),
"l", level move(Sokoban EAST),
"r", level = Sokoban Level mimic(levelString),
"q", "Bye!" println . System exit,
"d",
"ioke> " print
bind(
handle(fn(c, c println)),
Message doText(System in read code) println
),
"?", cliHelp println,
or("\n", "\r", "\t", " ", "."),
; System in read gives ".\n" if you just press enter so ignore . as well
nil,
"Invalid command: #{c}" println
)
)
)
"\nCongratulations - you beat level #{levelIndex + 1}!\n\n" println
)
)
)

;Sokoban cli



To be able to do the refactorings I did without breaking the implementation I also added some ISpec tests.

use("ispec")
use("sokoban_refactor")

SIMPLE_LEVEL = "#####\n#@o.#\n#####"
SOLVED_LEVEL = "#####\n# @*#\n#####"

position = method(x, y,
Sokoban Position mimic(x,y)
)
direction = method(x, y,
Sokoban Direction mimic(x,y)
)

describe(Sokoban,

describe(Sokoban wall,
it("should have the correct kind",
Sokoban wall should have kind("Sokoban wall")
)

it("should not be possible to walk on",
Sokoban wall should not be walkable
)
)

describe(Sokoban Crate,
it("should have the correct kind",
Sokoban Crate should have kind("Sokoban Crate")
)
)

describe(Sokoban Floor,
it("should have the correct kind",
Sokoban Floor should have kind("Sokoban Floor")
)
)

describe(Sokoban Floor,

it("should have the correct kind",
Sokoban Floor should have kind("Sokoban Floor")
)

describe("walkable?",
it("should be possible to walk on",
Sokoban Floor mimic should be walkable
)
)

describe("<<",
before(floor = Sokoban Floor mimic)
it("should be able to add a person to the floor tile",
floor clear
floor << Sokoban person
floor hasPerson? should == true
)

it("should be able to add a crate to the floor tile",
floor clear
floor << Sokoban crate mimic
floor hasCrate? should == true
)
)

describe("clear",
before(floor = Sokoban Floor mimic)
it("should return and remove resident",
floor clear
floor << Sokoban Crate mimic
floor clear should have kind("Sokoban Crate")
)
)

describe("free?",
before(floor = Sokoban Floor mimic)
it("should return true when there is no one on the tile",
floor clear
floor should be free
)
)

)

describe(Sokoban Storage,

it("should have the correct kind",
Sokoban Storage should have kind("Sokoban Storage")
)

describe("walkable?",
it("should be possible to walk on",
Sokoban Storage mimic should be walkable
)
)

describe("solved?",
before(storage = Sokoban Storage mimic)

it("should return false when it has no resident",
storage should not be solved
)

it("should return false when it has a person on it",
storage << Sokoban person
storage should not be solved
)

it("should return true when it has a crate on it",
storage clear
storage << Sokoban crate
storage should be solved
)
)
)

describe(Sokoban Tile,
describe("createTile",
it("should be able to create Floor tiles",
Sokoban Tile createTile(" ") should have kind("Sokoban Floor")
)
it("should be able to create Wall tiles",
Sokoban Tile createTile("#") should have kind("Sokoban wall")
)
it("should be able to create Storage tiles",
Sokoban Tile createTile(".") should have kind("Sokoban Storage")
)
it("should be able to create Floor tiles with a Person on them",
floorWithPerson = Sokoban Tile createTile("@")
floorWithPerson should have kind("Sokoban Floor")
floorWithPerson hasPerson? should be true
)
it("should be able to create Floor tiles with a Crate on them",
floorWithCrate = Sokoban Tile createTile("o")
floorWithCrate should have kind("Sokoban Floor")
floorWithCrate hasCrate? should be true
)
it("should be able to create Storage tiles with a Person on them",
floorWithPerson = Sokoban Tile createTile("+")
floorWithPerson should have kind("Sokoban Storage")
floorWithPerson hasPerson? should be true
)
it("should be able to create Storage tiles with a Crate on them",
floorWithCrate = Sokoban Tile createTile("*")
floorWithCrate should have kind("Sokoban Storage")
floorWithCrate hasCrate? should be true
)

)
)

describe(Sokoban Direction,
it("should have the correct kind",
Sokoban Direction should have kind("Sokoban Direction")
)

describe("*",
it("should multiply x and y with the given multiplyer",
dir = direction(1,2) * 2
dir x should == 2
dir y should == 4
)
)
)

describe(Sokoban Position,
it("should have the correct kind",
Sokoban Position should have kind("Sokoban Position")
)

it("should have an x and a y coordinate",
pos = position(1,2)
pos x should == 1
pos y should == 2
)

describe("+",
it("should add a direction to a position",
pos = position(1,2) + direction(1,2)
pos x should == 2
pos y should == 4
)
)
)

describe(Sokoban Level,

it("should have the correct kind",
Sokoban Level should have kind("Sokoban Level")
)

it("should be able to construct a level from a string",
Sokoban Level mimic(SIMPLE_LEVEL)
; just check that we do not get any errors
)

describe("asText",
before(level = Sokoban Level mimic(SIMPLE_LEVEL))
it("should return the level as a string",
level asText should == SIMPLE_LEVEL
)
)

describe("[]",
before(level = Sokoban Level mimic(SIMPLE_LEVEL))
it("should return the tile at the given index",
level[position(1,3)] should have kind("Sokoban Storage")
)
)

describe("playerIndex",
before(level = Sokoban Level mimic(SIMPLE_LEVEL))
it("should return the index of the player",
level playerIndex x should == 1
level playerIndex y should == 1
)
)

describe("movePlayer",
before(level = Sokoban Level mimic(SIMPLE_LEVEL))
it("should update the players position",
level movePlayer(position(1,3))
level playerIndex x should == 1
level playerIndex y should == 3
)
)

describe("moveCrate",
before(level = Sokoban Level mimic(SIMPLE_LEVEL))
it("should update the crates position",
level moveCrate(position(1,2), position(1,3))
level[position(1,3)] hasCrate? should be true
level[position(1,2)] hasCrate? should be false
)
)

describe("move",
before(level = Sokoban Level mimic(SIMPLE_LEVEL))
it("should update both the players and the crates position",
level move(Sokoban EAST)
level asText should == SOLVED_LEVEL
)
)

describe("solved?",
before(level = Sokoban Level mimic(SIMPLE_LEVEL))
it("should return false when all crates not on storage tiles",
level should not be solved
)

it("should return true when all crates are on storage tiles",
level moveCrate(position(1,2), Sokoban position(1,3))
level should be solved
)
)
)

)

Friday, June 5, 2009

Ioke macros

This is my third post about Ioke, you can also read the first and second posts.

You define macros (a la Lisp, not C) in Ioke with the macro method on DefaultBehaviour. To quote the Ioke guide "The main difference between a macro and a method in Ioke is that the arguments to a macro are not evaluated before they are sent to the macro." The arguments to the macro are accessible via the 'arguments' method on the 'call' cell. The following macro first prints the passed in message chain and then evaluates it.
iik> l = macro(code = call arguments first . code formattedCode println  . code evaluateOn(call ground))

'.' works as a terminator, kind of like newline, which can be useful when playing with iik.
'first' is a method on List that gets the first element in the list.
'formattedCode' is a method on Message that gives a representation suitable for output.
'evaluateOn' is a method on Message that evaluates the message with a given object as ground.
'ground' is a method on Message that gives the execution ground of the macro.
There is also a dmacro, for destructing macro, that lets us match the input arguments and execute different blocks depending on input. So a dmacro that can take 2 or 3 arguments can be written like this.
myUnless = dmacro(
[>condition, falseCode]
if(condition, condition, falseCode evaluateOn(call ground)),

[>condition, falseCode, trueCode]
if(condition, trueCode evaluateOn(call ground), falseCode evaluateOn(call ground)))

The '>' before the condition parameter tells Ioke to evaluate this argument.
iik> myUnless(false, 2 + 3)
+> 5

iik> myDmacro(2>1, 1/0)
+> true

Let's write an Ioke version of the Ruby 1.9 each_with_object method with an Ioke dmacro.
"Each_with_object is the same thing as inject, except you don’t have to return the object at the end of the block you pass. Since most of the time you are using inject, you have to return the hash or array as the last line in the block, using each with object will let you do the same thing, but with less code."
If we want to create a hash from an array with inject we can write something like.
iik> ["foo", "bar", "baz"] inject({}, res, elm, res[elm] = elm upper . res)
+> {"foo" => "FOO", "baz" => "BAZ", "bar" => "BAR"}

We have to return res from the code block, but with each_with_object this is done automatically for us.
[Update]
The same thing can be done with the ioke collect method for List, like this.
iik> ["foo", "bar", "baz"] collect(elm, elm => elm upper)
+> {"foo" => "FOO", "baz" => "BAZ", "bar" => "BAR"}

Let's try to implement it with an Ioke dmacro. We will add it as a method on Mixins Enumerable so that it will be available on all classes that mixins this class, such as arrays and dicts.
Mixins Enumerable each_with_object = dmacro(
[>sum, res, arg, code]
;; save the name of the arg parameter in elementName
elementName = arg name
;; do the same with the res parameter
resName = res name
;; define a new cell on call ground
call ground cell(resName) = sum
;; add resName as the return from the passed in code block
code last -> Message fromText(".") -> message(resName)

;; loop through the array we are evaluated on, accessible through self
self each(n,
;; save the current elements value in the elementName cell so that it can be used in the passed in code
call ground cell(elementName) = cell(:n)
;; run the code on this element and save the result in the resName cell
call ground cell(resName) = code evaluateOn(call ground))
;; return the value in the resName cell from the macro
call ground cell(resName))

-> is a way in Ioke to add messages to a messages chain. Let's try our new macro with the previous example. When we enter the macro the res 'parameter' will be of type Message, to be able to use the variable name contained in res we use the 'name' function on Message. This gives us the variable name in 'resName' of type Symbol. But to be able to assign a cell with the Symbol name in 'resName' we have to use the cell() function. You can try it in iik.
iik> symbolVar = :symbolName
+> :symbolName

iik> cell(symbolVar) = 42
+> 42

iik> symbolName
+> 42

Let's try our new macro with the array to hash example we saw earlier.
iik> ["foo", "bar", "baz"] each_with_object({}, res, elm, res[elm] = elm upper)
+> {"foo" => "FOO", "baz" => "BAZ", "bar" => "BAR"}

Our macro can be simplified with the use of a lexical block in which we define the res and arg parameters. Lexical blocks can be created with fn, fnx in DefaultBehavior Definitions and the createFrom method in LexicalBlock. The lexical block can then be invoked with the call method in LexicalBlock or by using regular method invocation syntax with (). From the Ioke guide:
iik> x = fn(z, z println)
iik> x call(42)
42

iik> y = fnx(z, z println)
iik> y(42)
42

The method createFrom in LexicalBlock takes two arguments, the first is a list with arguments and code and the second is the context that the lexical scope should be created in. This lets us redefine our macro like this.
Mixins Enumerable each_with_object = dmacro(
[>sum, resName, argName, code]
code last -> Message fromText(".") -> message(resName)
lexicalCode = LexicalBlock createFrom(list(resName, argName, code), call ground)
self each(n,
sum = lexicalCode call(cell(:sum), cell(:n)))
return(sum))

In this version we reuse the sum parameter as storage of the intermediate result. This also happens to be equivalent to the way inject is defined in Ioke, F30_enumerable.ik search for inject.

Friday, May 22, 2009

Ioke and ISpec

This is the second post about the Ioke programming language, you can read the first part hear A Look at Ioke.

Ioke comes with the ISpec framework that lets you do bdd in Ioke, the syntax is very similar to that of Ruby's RSpec. There is a short introduction to ISpec on the Ioke wiki. To try it out I will use a problem from the TDD problems site. The mission is to convert a dollar amount to text. Let's start with a simple spec that says that 0 should be spelled out as 'zero'.
use("ispec")

describe(DollarToTextConverter,
it("should convert 0 to zero",
dttc = DollarToTextConverter mimic
dttc convert(0) should == "zero"
)
)

If we run this specification through Ioke we get the following output.
*** - couldn't find cell 'DollarToTextConverter' on 'Ground' (Condition Error No
SuchCell)

DollarToTextConverter [bddTest.ik:3:9]

So let's create the DollarToTextConverter kind. Because Ioke is a prototype based language there is no concept of a class, instead all objects are created as prototypes of already existing objects. New objects are created with the keyword mimic. Base is the top most object in Ioke but is not meant to be used to create new objects from. Instead we will use the Origin kind to create our object from.
DollarToTextConverter = Origin mimic

We save this to a dollarToTextConverter.ik file and add a use("dollarToTextConverter") statment to our ISpec test file. Running the specification again we get.
F

1)
Condition Error NoSuchCell in 'DollarToTextConverter should convert 0 to zero'
couldn't find cell 'convert' on 'DollarToTextConverter_0x1123BE5' (Condition Err
or NoSuchCell)

dttc convert(0) should ==("zero") [bddTest.ik:8:7]
bddTest.ik:7:2
bddTest.ik:7:2

Finished in 0.16 seconds

1 example, 1 failure

This tells us that there is no convert method on DollarToTextConverter, let's create it.
DollarToTextConverter = Origin mimic
DollarToTextConverter convert = method(amount, "zero")

Running our specification again we get.
.

Finished in 0.0 seconds

1 example, 0 failures

Our first passing specification in Ioke. The next test will be to make sure that 1 returns 'one'.
use("ispec")
describe(DollarToTextConverter,
it("should convert 0 to zero",
dttc = DollarToTextConverter mimic
dttc convert(0) should == "zero"
)

it("should convert 1 to one",
dttc = DollarToTextConverter mimic
dttc convert(1) should == "one"
)

Of course it fails when we run it as our convert functions always return 'zero'.
'DollarToTextConverter should convert 1 to one' FAILED
expected "zero" to == "one" (ISpec ExpectationNotMet)


bddTest.ik:16:18

Finished in 0.16 seconds

2 examples, 1 failure

Let's change our convert method to look at the incoming amount parameter and return an appropriate text representation.
DollarToTextConverter = Origin mimic
DollarToTextConverter convert = method(amount,
if (amount == 0,
"zero",
"one"
)
)

This will make our specification pass.
..

Finished in 0.15 seconds

2 examples, 0 failures

Time for some refactoring. Both test cases creates a new mimic of DollarToTextConverter so lets move this out of the it method calls.
describe(DollarToTextConverter,
dtcc = DollarToTextconverter mimic

it("should convert 0 to zero",
dttc convert(0) should == "zero"
)

it("should convert 1 to one",
dttc convert(1) should == "one"
)

The tests still passes. Let's consider what to test next. We want all one digit numbers to convert to the corresponding english text representation. So let's write a new test that tests all one digit numbers are converted to the correct text.
describe(DollarToTextConverter,
dtcc = DollarToTextconverter mimic

it("should convert to the correct text for a single digit amount",
(0..9) map(num, dttc convert(num)) should == ["zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine"]
)

it("should convert 0 to zero",
dttc convert(0) should == "zero"
)

it("should convert 1 to one",
dttc convert(1) should == "one"
)

The specification fails so let's write some code to try to make it pass. A dict with numbers as keys and texts as values seems like a natural fit at this point. The literal syntax to create a dict in Ioke is {}(). In order to only refactor our code when the test cases are green we begin without the new specification and only refactor our code while keeping the orignal tests passing. The do method is a way to add cells to an object.

DollarToTextConverter = Origin mimic
DollarToTextConverter do (
numberDict = {}(0 => "zero", 1 => "one")
convert = method(amount,
numberDict[amount]
)
)

The tests passes so we can safely introduce our new test. We leave the previous tests until we are sure this new one works and covers the same cases. The new specification fails. But it is easy to fix by adding the numbers from 2 to 9 in our numberDict.
DollarToTextConverter do (
numberDict = {}(0 => "zero", 1 => "one", 2 => "two", 3 => "three", 4 => "four",
5 => "five", 6 => "six", 7 => "seven", 8 => "eight", 9 => "nine")
convert = method(amount,
numberDict[amount]
)
)

This makes all the tests pass, so we can safely remove the original two test cases.
Lets move on to two digit amounts. The numbers from 10 to 19 are similar to the single digit amounts and are easily converted by adding them to the numberDict. What about the numbers between 20 and 99? The special cases are the numbers dividable by 10, i.e 20 30 40 etc. We can just add them to our numberDict. The numbers between the 10-multiples are combinations of the corresponding 10-multiple and singel digit number, i.e. 21 => twenty-one. So let's write a new specification for the numbers up to 99.
describe(DollarToTextConverter,
dtcc = DollarToTextconverter mimic

it("should convert numbers between 0 and 19 to the correct text",
(0..19) map(num, dttc convert(num)) should == ["zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixtee", "seventeen", "eighteen", "nineteen"]
)

it("should convert multiples of 10 below 100 to the correct text",
[20, 30, 40, 50, 60, 70, 80, 90] map(num, dttc convert(num)) should == ["twenty", "thirty", "fourty", "fifty", "sixty", "seventy", "eighty", "ninty"]
)

it ("should convert two digit numbers to the correct text",
[21, 32, 43, 44, 55, 66, 67, 78, 88, 99] map(num, dttc convert(num)) should == ["twenty-one", "thirty-two", "fourty-three", "fourty-four", "fifty-five", "sixty-six", "seventy-eight", "eighty-eight", "ninty-nine"]
)

)

We just need a simple adjustment to our convert method to handle the new possible amount values.
DollarToTextConverter do (
numberDict = {}(0 => "zero", ... ) ;; lots of numbers left out
convert = method(amount,
if(numberDict[amount],
numberDict[amount], ;; if there is a direct mapping for our number return it
;; otherwise we construct a compound text
"#{numberDict[amount - amount % 10]}-#{numberDict[amount % 10]}"
)
)
)

The rules for multiples of 100 are combinations of the single digit and the 'hundred' suffix. The pattern for numbers below a thousand is then repeated for the 1000 part up to a million, but with a thousand suffix. Let's start with numbers between 100 and 999.
describe(DollarToTextConverter,
dtcc = DollarToTextconverter mimic

it("should convert numbers between 100 and 999 to the correct text",
dttc = DollarToTextConverter mimic
[100, 298, 321, 400, 555, 654, 777, 890, 901] map(num, dttc convert(num)) should == ["one hundred", "two hundred ninety-eight", "three hundred twenty-one", "four hundred", "five hundred fifty-five", "six hundred fifty-four", "seven hundred seventy-seven", "eight hundred ninety", "nine hundred one"]
)
)

Before inserting this test we need to refactor our code. We start by creating a new method convertTens.
convert = method(amount,
textArray = convertTens(amount)
)

convertTens = method(amount,
if(numberDict[amount],
numberDict[amount],
"#{numberDict[amount - amount % 10]}-#{numberDict[amount % 10]}"
)
)

The tests still passes so let's move on, we add our new test case for numbers between 100 and 999. To convert numbers between 100 and 999, we need to first convert the leftmost digit and add a "hundred", then convert the two rightmost digits if they are > 0. To do this we create a method convertHundreds, and a helper method to be able to get at the leftmost digit.
convertHundreds = method(amount,
result = []
hundreds = decimalShiftRight(amount, 2)
if(hundreds > 0,
result << convertTens(hundreds) << "hundred")
result << convertTens(amount%100)
)

decimalShiftRight = method(amount, places,
(amount - amount%(10**places)) / (10**places)
)


As you can see we have also chosen to start to store the intermediate result in an array. We change convertTens so that it also returns an array. And join the array with " " in the convert method.
convert = method(amount,
textArray = convertHundreds(amount) flatten
textArray join(" ") trim
)

convertTens = method(amount,
if(amount == 0,
[],
if(numberDict[amount],
numberDict[amount],
"#{numberDict[amount - amount % 10]}-#{numberDict[amount % 10]}"
)
)
)

This makes the new test pass. So let's add a test case for numbers above 1000. The conversion of numbers above 999 will be uniform and can be handled by a new method convertWithModifier.
modifier = ["", "thousand", "million"]
convertWithModifier = method(amount, modifierIndex,
if (amount == 0,
[],
convertWithModifier(decimalShiftRight(amount,3), modifierIndex + 1) <<
convertHundreds(amount%1000) << modifier[modifierIndex]
)
)

Here we convert the total amount in groups of three digits at a time. We call convertHundreds on the rightmost three digits we currently have, add an appropriate modifier, e.g. "thousand", and recursively call ourself with the same number decimal-shifted to the right 3 places. Our final code looks like this.

DollarToTextConverter = Origin mimic
DollarToTextConverter do (
numberDict = {}(1 => "one", 2 => "two", 3 => "three", 4 => "four",
5 => "five", 6 => "six", 7 => "seven", 8 => "eight", 9 => "nine", 10 => "ten",
11 => "eleven", 12 => "twelve", 13 => "thirteen", 14 => "fourteen",
15 => "fifteen", 16 => "sixtee", 17 => "seventeen", 18 => "eighteen", 19 => "nineteen",
20 => "twenty", 30 => "thirty", 40 => "fourty", 50 => "fifty", 60 => "sixty",
70 => "seventy", 80 => "eighty", 90 => "ninety")
modifier = ["", "thousand", "million"]

convert = method(amount,
textArray = convertWithModifier(amount, 0) flatten
textArray join(" ") trim
)

convertWithModifier = method(amount, modifierIndex,
if (amount == 0,
[],
convertWithModifier(decimalShiftRight(amount,3), modifierIndex + 1) <<
convertHundreds(amount%1000) << modifier[modifierIndex]
)
)

convertHundreds = method(amount,
result = []
hundreds = decimalShiftRight(amount, 2)
if(hundreds > 0,
result << convertTens(hundreds) << "hundred")
result << convertTens(amount%100)
)

decimalShiftRight = method(amount, places,
(amount - amount%(10**places)) / (10**places)
)

convertTens = method(amount,
if(amount == 0,
[],
if(numberDict[amount],
numberDict[amount],
"#{numberDict[amount - amount % 10]}-#{numberDict[amount % 10]}"
)
)
)
)


And the ISpec tests.
use("ispec")
use("dollarToTextConverter")

describe(DollarToTextConverter,
dttc = DollarToTextConverter mimic
it("should convert numbers between 0 and 19 to the correct text",
(1..19) map(num, dttc convert(num)) should == ["one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixtee", "seventeen", "eighteen", "nineteen"]
)

it("should convert multiples of 10 below 100 to the correct text",
[20, 30, 40, 50, 60, 70, 80, 90] map(num, dttc convert(num)) should == ["twenty", "thirty", "fourty", "fifty", "sixty", "seventy", "eighty", "ninety"]
)

it ("should convert two digit numbers to the correct text",
[21, 32, 43, 44, 55, 66, 67, 78, 88, 99] map(num, dttc convert(num)) should == ["twenty-one", "thirty-two", "fourty-three", "fourty-four", "fifty-five", "sixty-six", "sixty-seven", "seventy-eight", "eighty-eight", "ninety-nine"]
)

it("should convert multiples of 100 to the correct text",
[100, 298, 321, 400, 555, 654, 777, 890, 901] map(num, dttc convert(num)) should == ["one hundred", "two hundred ninety-eight", "three hundred twenty-one", "four hundred", "five hundred fifty-five", "six hundred fifty-four", "seven hundred seventy-seven", "eight hundred ninety", "nine hundred one"]
)

it("should convert amounts larger than 1000 to the correct text",
[1000, 12000, 231000, 567000, 999999, 123456789] map(num, dttc convert(num)) should ==
["one thousand", "twelve thousand", "two hundred thirty-one thousand", "five hundred sixty-seven thousand", "nine hundred ninety-nine thousand nine hundred ninety-nine", "one hundred twenty-three million four hundred fifty-six thousand seven hundred eighty-nine"]
)
)

A look at Ioke

This will be a short look at Ioke (ioke.org) a programming language created by Ola Bini. Ioke code is read from left to right. You can read about the execution model in Ioke here. The first symbol implicit on each line is called Origin. "kind" is a method that tells you the kind of the symbol it is evaluated on.
iik> kind
+> "Origin"

iik> 4 kind
+> "Number Integer"

Addition works as you would expect.
iik> 1 + 1
+> 2

+ is actually a method defined in the kind Number Integer so we can also call it as
iik> 1 +(1)
+> 2

Create variables by assigning a value to a new name. What you actually are doing is creating a cell in the receiving object, which is Origin in this case.
iik> x = 1

You can see what cells are defined on Origin withe the cells method. This returns a Dict with names and values. To get a nicer view we can use the each method and tell it to apply println to each pair in the Dict
iik> cells each(println)

You will see x as the last cell in the list of cells on Origin.

Ioke does some operator shuffling between readable syntax as above and a canonical form. So the above assignment can also be written as.
iik> =(x, 1)

Which is the form Ioke will print parsed code in. With this syntax it becomes clear that = is just another function that takes two arguments. You define methods with the method keyword. To see this in practice we create a method with an assignment in it.
iik> a = method(x=1) 
+> a:method(=(x, 1))

The value returned from a function is the last evaluated expression.
iik> myFunction = method("Hello there")
+> myFunction:method("Hello there")

iik> myFunction println
Hello there
+> nil

To define a method that takes some arguments:
iik> myAdd = method(num1, num2, num1 + num2)

iik> myAdd(3, 4)
+> 7

We can also choose to add our new function to the Ioke kind Number Integer.
iik> Number Integer add = method(num, num + self)
+> add:method(num, num + self)

iik> 3 add(2)
+> 5

self (or @) refers to the current object, like this in Java. We are also free to redefine + on Number Integer if we want to.
iik> Number Integer + = method(num, self - num)

iik> 3 + 2
+> 1

Let's try some conditions in our function. The if function will evaluate its second argument if the first argument is true and the third argument otherwise. One or both of the branches can be left out. There are also conditional functions called unless, cond and case. Go look them up in the ioke reference.
iik> Number Integer add2IfTrue = method(boolean, if(boolean, self + 2, self))
+> add2IfTrue:method(boolean, if(boolean, self +(2), self))

iik> 2 add2IfTrue(4>3)
+> 4

iik> 2 add2IfTrue(3>3)
+> 2

Looping over stuff can be done with loop, while, until, times, each and some other. Here are some examples from the ioke guide.
iik> loop("hello" println) ;; An infinite loop

iik> x = 0
iik> loop(if(x > 10, break) . x++ println)
iik> 3 times("hello" println)

iik> [1, 2, 3] each(println)

[] creates arrays and {} creates Dict's or hashes.
iik> nums = {1 => "one", 2 => "two"}
+> {1 => "one", 2 => "two"}

iik> nums[1]
+> "one"