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.
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)

iik> y = fnx(z, z println)
iik> y(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)))

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.

No comments: