Skip to content

Instantly share code, notes, and snippets.

@cararemixed
Created May 12, 2014 02:13
Show Gist options
  • Save cararemixed/3505d8c5194b164d9911 to your computer and use it in GitHub Desktop.
Save cararemixed/3505d8c5194b164d9911 to your computer and use it in GitHub Desktop.
old articles I've written on Io
Everyone who has used Ruby for a little while will quickly become familiar with its custom control structures. They are easy to use and very quick to build. Here is an example implementation of Enumerable#inject:
def inject(initial)
memo = initial # note that this line is only here for clarity. It could be shorter.
each {|x| memo = yield(memo,x)}
memo
end
Now that was easy! It is easy to use and works cleanly:
[1,2,3].inject(0) {|m,x| m+x} #=> 6
So you can see how things like this really catch onto Rubyists. I can say it is one thing that makes Ruby a great language.
Not long ago I decided to help out with some of Io's libraries. I decided to look for things that should be easy but aren't yet. One thing I felt was missing was inject... so I sat down and came up with this:
List inject := method(
memo := sender doMessage(thisMessage argAt(0))
memoName := thisMessage argAt(1) name
varName := thisMessage argAt(2) name
body := thisMessage argAt(3)
yield := block setMessage(body) setArguments(list(memoName,varName)) setScope(sender)
foreach(e, memo = yield(memo, e))
memo
)
Now this code is pretty neat. It may be longer than the Ruby one but that is because it is more flexible (simplicity often delays complexity rather than removing it).
The first thing you will see is the setting of the "inject" slot on List. Io doesn't have a first class collection library like Ruby yet (being worked on) but this will work for now. The Block object constructor being used is method. This creates a simple method without lexical closure very much the same way ruby works with "def".
The next thing you will notice is a bunch of lines the are using "thisMessage". Since I left of an arguments beyond the code of the method from method() it will default to a sort of dynamic mode where I can read things in as needed and decide what their semantics are. This is important as you will note how I treat each line. First I take thisMessage and pull a specific argument out (in this case by position). Then I might evaluate that in a specific context or transform it to something. The first argument is the initial seed value for inject. I evaluate the corresponding message in the sender's context (similar to how ruby would take an expression as an argument). The next to argument and variable names. I want these to just be strings so I transform then by sending "name" to each Message object. Last but not least is body which I preserve as a message for use in the following steps.
The next part is where Io gets interesting. Since Io doesn't have anything built-in for handling blocks we have to build on something else. We already have executable object types like Block so that is what I build. "block" will construct a new callable piece of code. I then tell it that it should execute body as the inner code. After that I specify that is has a list of arguments and I give their names. In this case I am explicitly calling them arguments so they will be strictly evaluated like most languages. The final part is something quite magical in Io. I set the scope of the Block object to the sender of the message we are currently in. This will build a sort of lexical closure very similar to how Ruby's blocks work.
The final touch is almost the same as Ruby: I take each element and iterate over each while yielding to the passed in code. There is also the trivial result being returned by putting memo at the end of the expression.
So it was longer (a whole 4 lines longer) but it shows how Io can be very powerful and makes no restriction on how things work. So without waiting any longer how does it look in Io?
list(1,2,3) inject(0, m, x, m+x)
The style is certainly different but it does most of the same things as the ruby version (with exception that break and the like won't work -- yet, it is being worked on).
So with the extra effort we can certainly accomplish almost anything. The question is whether or not this effort is worthwhile. There is also the possibility that some good helper objects and methods might make the Io example even easier. Time will tell. In the end I hope to see common idioms in Io like this one be wrapped up in some nice sugar libraries.
After a little discussion in #io today I decided to sit down for a short hacking session trying to simplify how we handle lazy arguments. I came up with a very simple proof of concept.
# Lazy method factory
lazy := method(
args := thisMessage arguments clone
base := args pop # Code is always at the tail.
args foreach(i,x,
code := x .. " := " .. "thisMessage argAt(" .. i .. ")"
base = code asMessage setNextMessage(base)
)
method setMessage(base)
)
# Lazy block factory
lazyBlock := method(
args := thisMessage arguments clone
base := args pop # Code is always at the tail.
args foreach(i,x,
code := x .. " := " .. "thisMessage argAt(" .. i .. ")"
base = code asMessage setNextMessage(base)
)
block setMessage(base) setScope(sender)
)
I am not going to discuss why I implemented the two like I did but I will show how it can be used. Reusing my last post's example code of inject:
List inject := method(
memo := sender doMessage(thisMessage argAt(0))
memoName := thisMessage argAt(1) name
varName := thisMessage argAt(2) name
body := thisMessage argAt(3)
yield := block setMessage(body) setArguments(list(memoName,varName)) setScope(sender)
foreach(e, memo = yield(memo, e))
memo
)
This is now written as:
List inject := lazy(memo, m, v, body,
memo = sender doMessage(memo)
yield := block setMessage(body) setArguments(list(m name, v name)) setScope(sender)
foreach(e, memo = yield(memo, e))
memo
)
Seems like Io is slowly becoming easier and more concise. It is still not as simple as the Ruby equivalent but it is getting very close.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment