Entries About Feed

Exploring Io

I first heard about the Io programming language quite a while ago – I believe I saw it in Steve Yegge’s1 sidebar when I was still in high school and just starting to get interested in different programming languages. Recently, it came up again on Lobsters and I decided to have another look at it.

The first programming language I really got into when I first started learning to code was Scheme, so I really appreciate the minimalist “pure” feel of Io. The way that everything is just sending messages to objects, with “syntax” like assignment operators just being macros for messages (e.g. foo = bar is sugar for updateSlot("foo", bar)) really appeals to me. I also have a soft spot for the RPN-like notation that Io code tends to have – the first open-source project I contributed to was the Factor programming language, so it’s a nice dose of nostalgia for me.

To try make something real in Io, I decided to write a little command-line client for Pinboard. Pinboard has a very simple API, so it seemed like a good target for making a little toy program.

Confusing things I ran in to:

I think Io is a neat language, but I found it a little annoying to try to make standalone things with it. Next time I need an embedded language, I think it’d be fun to try using Io instead of Lua though.

Here’s the little pinboard client I made:

Post := Object clone do(
url ::= nil
description ::= nil
time ::= nil

fromXML := method(xml,
url = xml attributes at("href")
description = xml attributes at("description")
time = Date clone fromString(xml attributes at("time"),

asString := method(
"Post: url=#{url} desc=#{description} time=#{time}" interpolate


SGMLElement tagText := method(tag, elementsWithName(tag) first allText)

Sequence xmlDecodeEntities := method(
asMutable replaceMap(Map clone with(""", "\"", "&", "&"))

Note := Object clone do(
id ::= nil
title ::= nil
hash ::= nil
created ::= nil
updated ::= nil
length ::= nil
body ::= nil

fromXML := method(xml,
id = xml attributes at("id")
title = xml tagText("title")
hash = xml tagText("hash")
created = Date clone fromString(xml tagText("created_at"), "%Y-%m-%d %H:%M:%S")
updated = Date clone fromString(xml tagText("updated_at"), "%Y-%m-%d %H:%M:%S")
length = xml tagText("length")

fetchBody := method(api,
if(body == nil,
    fetchedXml := api apiRequest("/notes/#{id}/" interpolate)
    body = self extractTextCdata(fetchedXml)

extractTextCdata := method(xmlText,
reader := XmlReader clone
reader parseString(xmlText)
reader next
reader moveToElement
while(reader name != "text", reader read)
reader readString xmlDecodeEntities


Pinboard := Object clone do(
authToken ::= nil

apiURL := "https://api.pinboard.in/v1"

apiRequest := method(path,
// TODO: error handling
HCRequest with(
    HCUrl with("#{apiURL}#{path}?auth_token=#{authToken}" interpolate)
) connection sendRequest response content

recent := method(
apiRequest("/posts/recent") asXML elementsWithName("post") \
    map(post, Post clone fromXML(post))

notes := method(
apiRequest("/notes/list") asXML elementsWithName("note") \
    map(note, Note clone fromXML(note))


So using it looks something like


pin := Pinboard clone

pin setAuthToken("pinboard-username:secret-token")

recentBookmarks := pin recent

notes := pin notes

notes println

firstNote := notes first

firstNote fetchBody(pin) println

firstNote println



I owe a huge debt of gratitude to Steve Yegge: his original blog was a huge influence on me when I first started programming and has really shaped the sort of developer I am today.