The best way to understand Io is to use it. In this chapter, we'll install Io, explore its REPL (Read-Eval-Print Loop), and write our first programs. By the end, you'll have a feel for Io's syntax and flow.
If you're on macOS with Homebrew, installation is simple:
brew install ioOn most Linux distributions, you'll need to build from source:
git clone https://github.com/IoLanguage/io.git
cd io
mkdir build
cd build
cmake ..
make
sudo make installWindows users should use WSL (Windows Subsystem for Linux) and follow the Linux instructions, or use Docker:
docker run -it --rm stevedekorte/ioOnce installed, verify Io is working:
$ io --version
Io Programming Language, v. 20170906
$ io
Io> "Hello, World!" println
Hello, World!
==> Hello, World!
Io> ^CIo's REPL is where you'll spend most of your learning time. Unlike compiled languages where you write, compile, and run, Io lets you experiment immediately.
Start the REPL by typing io:
$ io
Io>The prompt Io> indicates Io is ready for input. Let's explore:
Io> 2 + 2
==> 4
Io> "Hello" .. " " .. "World"
==> Hello World
Io> 10 > 5
==> trueNotice the ==> prefix? That shows the return value of your expression. Everything in Io returns a value.
- Multi-line input: The REPL detects incomplete expressions:
Io> if(true,
... "yes" println,
... "no" println
... )
yes
==> yes- Previous result: Use
_to reference the last result:
Io> 100 * 2
==> 200
Io> _ + 50
==> 250- Getting help: The REPL has built-in documentation:
Io> Lobby slotNames
==> list(Protos, _, exit, forward, set_)
Io> Number slotNames sort
==> list(%, *, +, -, /, <, <=, ==, >, >=, abs, acos, asin, atan, between, ceil, cos, ...)Let's write the traditional "Hello, World!" program. Create a file called hello.io:
"Hello, World!" printlnRun it:
$ io hello.io
Hello, World!That's it. No class definitions, no main function, no boilerplate. Compare with Java:
public class Hello {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}Or even Python:
if __name__ == "__main__":
print("Hello, World!")Io just runs your code.
Io's syntax is minimal. Let's explore the basics:
In Io, everything is about sending messages to objects:
"hello" size // Send 'size' message to "hello"
==> 5
"hello" at(0) // Send 'at' message with argument 0
==> h
"hello" upper // Send 'upper' message
==> HELLOCompare with method calls in Python:
"hello".upper() # Python"hello" upper // Io - parentheses optional for no argumentsMessages can have arguments, passed in parentheses:
"hello" at(1) // One argument
==> e
"hello" slice(1, 3) // Two arguments
==> el
List append(1, 2, 3) // Multiple arguments
==> list(1, 2, 3)This is crucial: operators in Io are just messages with special precedence:
2 + 3 // Send message "+" to 2 with argument 3
2 +(3) // Exactly the same thing
2 send("+", 3) // Still the same thing!This uniformity means you can redefine operators:
Number + := method(n, self - n) // Redefine + to subtract!
5 + 3
==> 2(Don't actually do this in real code!)
In Io, variables are just slots on objects. By default, you're working with the Lobby object:
x := 10 // Create slot 'x' on Lobby with value 10
x println // Print it
==> 10
x = 20 // Update existing slot
x println
==> 20
y = 30 // Error! Slot doesn't exist
// Exception: Slot y not foundNote the distinction:
:=creates a new slot=updates an existing slot
This prevents accidental variable creation from typos:
counter := 0
countr = 1 // Error - probably a typo!Compare with JavaScript's similar issue:
let counter = 0;
countr = 1; // Creates global variable - probably a bug!Io's control structures are methods, not special syntax:
if(10 > 5,
"Yes" println,
"No" println
)
// Prints: YesCompare with Python:
if 10 > 5:
print("Yes")
else:
print("No")Since if is a method, you can even look at its implementation:
Io> if
==> method(...)// While loop
i := 0
while(i < 5,
i println
i = i + 1
)
// For loop
for(i, 0, 4,
i println
)
// Times loop
5 times(i,
i println
)Let's create our first custom object:
Person := Object clone
Person name := "Unknown"
Person greet := method(
("Hello, I'm " .. name) println
)
alice := Person clone
alice name = "Alice"
alice greet
// Prints: Hello, I'm AliceCompare with Python:
class Person:
def __init__(self):
self.name = "Unknown"
def greet(self):
print(f"Hello, I'm {self.name}")
alice = Person()
alice.name = "Alice"
alice.greet()The key difference: Io has no class definition. Person is just an object we're using as a prototype.
Methods in Io are created with the method function:
Calculator := Object clone
Calculator add := method(a, b, a + b)
Calculator multiply := method(a, b, a * b)
calc := Calculator clone
calc add(5, 3) println // 8
calc multiply(4, 7) println // 28Methods have access to self (the receiver):
Counter := Object clone
Counter count := 0
Counter increment := method(
count = count + 1
self // Return self for chaining
)
c := Counter clone
c increment increment increment
c count println // 3Lists are fundamental in Io:
numbers := list(1, 2, 3, 4, 5)
// Iteration
numbers foreach(n,
n println
)
// Map
squared := numbers map(n, n * n)
squared println // list(1, 4, 9, 16, 25)
// Select (filter)
evens := numbers select(n, n % 2 == 0)
evens println // list(2, 4)
// Reduce
sum := numbers reduce(+)
sum println // 15Compare with Python:
numbers = [1, 2, 3, 4, 5]
# Iteration
for n in numbers:
print(n)
# Map
squared = [n * n for n in numbers]
# Filter
evens = [n for n in numbers if n % 2 == 0]
# Reduce
from functools import reduce
sum = reduce(lambda a, b: a + b, numbers)Reading and writing files is straightforward:
// Write to file
file := File with("test.txt")
file openForWriting
file write("Hello, file!")
file close
// Read from file
file := File with("test.txt")
file openForReading
contents := file contents
contents println
file close
// Or more concisely
File with("test.txt") contents printlnLet's build something more substantial—a simple to-do list:
// todo.io - A simple to-do list manager
TodoList := Object clone
TodoList items := list()
TodoList add := method(task,
items append(task)
self
)
TodoList show := method(
if(items size == 0,
"No tasks!" println,
items foreach(i, task,
(" " .. (i + 1) .. ". " .. task) println
)
)
self
)
TodoList complete := method(index,
if(index > 0 and index <= items size,
task := items at(index - 1)
items removeAt(index - 1)
("Completed: " .. task) println,
"Invalid task number" println
)
self
)
TodoList save := method(filename,
File with(filename) openForWriting write(items asJson) close
"Saved!" println
self
)
TodoList load := method(filename,
if(File with(filename) exists,
items = Yajl parseJson(File with(filename) contents)
"Loaded!" println,
"File not found" println
)
self
)
// Usage
todo := TodoList clone
todo add("Learn Io") add("Build something cool") add("Share with friends")
todo show
// 1. Learn Io
// 2. Build something cool
// 3. Share with friends
todo complete(1)
// Completed: Learn Io
todo show
// 1. Build something cool
// 2. Share with friendsHaving written your first Io programs, you've probably noticed:
-
Minimal syntax: No keywords for defining classes, functions, or variables. Everything uses the same message-passing syntax.
-
Immediate feedback: The REPL makes experimentation effortless.
-
Uniform model: Whether you're doing arithmetic, defining methods, or creating objects, it's all message passing.
-
Flexibility: You can modify anything, even built-in types and operators.
-
Simplicity: Programs are often shorter than their equivalents in other languages.
Try these exercises to solidify your understanding:
-
Number methods: Add a
squaredmethod toNumberthat returns the square of a number. Test it with5 squared. -
String reversal: Create a method on
Sequence(Io's string type) calledreversethat returns the reversed string. -
Bank account: Create a
BankAccountobject withbalance,deposit, andwithdrawmethods. Include protection against negative balances. -
FizzBuzz: Implement FizzBuzz in Io (print numbers 1-100, but "Fizz" for multiples of 3, "Buzz" for multiples of 5, "FizzBuzz" for both).
-
Method chaining: Create a
StringBuilderobject that allows chaining:StringBuilder clone add("Hello") add(" ") add("World") toString
If you're coming from mainstream languages, here's what might feel strange:
- No compile step: Your code runs immediately
- No type declarations: Everything is dynamically typed
- No class keyword: Objects are created by cloning
- Operators aren't special: They're just messages
- Everything returns a value: Even assignments and control structures
These differences aren't arbitrary—they all flow from Io's core principle of uniform message passing.
You now have enough Io knowledge to explore the deeper concepts. You can:
- Create and manipulate objects
- Define methods
- Use control structures
- Work with collections
- Read and write files
In the next chapter, we'll dive deep into Io's object model and understand what "everything is an object" really means.