Getting Started

A quick introduction to Atomo

Atomo is a unique combination of many programming ideas - prototyping, message-passing, pattern matching, multiple dispatch, and message-passing concurrency being the primary ones. It is eagerly evaluated, and does not have a null type. It supports multiple inheritance via delegation. And it's mind-bogglingly dynamic.

This guide will get you up and running with the very basics of the Atomo programming language. It assumes you have some prior programming experience, though not necessarily with a language like Atomo.

Installation

Atomo may be installed through the Cabal package managing system:

cabal install atomo

Alternatively, you can install it from the repository at darcsden:

darcs get http://darcsden.com/alex/atomo
cd atomo
cabal install

Once that's all done, just make sure ~/.cabal/bin is in your $PATH, and you should be all set!

Fire it up!

To start up the Atomo REPL, just type in "atomo" at your prompt:

$ atomo
>

Now you're in the Atomo read-evaluate-print loop, which - as you may have guessed - takes in Atomo code, evaluates it, and prints the result, repeating this loop until you exit. You probably won't be able to do much since we haven't gotten very far yet, but here's some basic math, just for a quick taste:

> 1 + 1
2
> 1 + 2 * 3
7
> 20 sqrt
4.47213595499958

The REPL is readline-style, with history navigation. To exit the REPL, hit Ctrl+D or Ctrl+C, and hit y. It asks before you leave, because running servers from the REPL is a common task, and it shouldn't be too easy to just shut down everything by accident.

Define & Dispatch

There are two primary concepts in Atomo that lay the foundation for the entire language: defining and dispatching.

Defining sets a value or expression to respond to some message. Dispatching is sending a message to get a value back.

> a = 1
1
> a
1

Here we're inserting a slot - a kind of method - on the toplevel object, which responds to the message a by yielding the integet 1. We then dispatch that message and get the value back.

There are two operators for defining methods: = and :=. The = operator performs pattern-matching and slot insertion, while := defines a method that responds to a message by evaluating an expression. Generally you'll use := for defining methods, and = for setting values.

To illustrate this distinction, we'll define a method that simply prints "Hello, world!", using both operators. Note that print outputs the value and yields the same value.

> y := "Hello, world!" print
@ok
> x = "Goodbye, world!" print
Goodbye, world!
"Goodbye, world!"
> y
Hello, world!
"Hello, world!"
> x
"Goodbye, world!"

As you can see, Goodbye, world! was printed immediately after defining x, with subsequent dispatches for x simply returning the value that was printed. Conversely, y dutifully prints Hello, world! with every dispatch.

But = has a few more tricks up its sleeve. As mentioned before, it can also do pattern-matching. And since it returns the same value, it can be used to set multiple values at once:

> [a, _] = b = [1, 2]
[1, 2]
> a
1
> b
[1, 2]
> a = a + 1
2
> a
2

As you can see, first we set b to [1, 2], and then we immediately pattern-match [a, _] on b, which sets a to 1, the first element of the list. We then re-assign a as a + 1, increasing a's vlaue by 1.

Lexical Scoping via Delegation

You'll notice earlier I used the term top-level object for describing the target of the a definition. This is the entirety of Atomo's scoping system; it's objects all the way down. The nearest lexical scope to a point in your code (henceforth called the top-level object or top) is simply an object that delegates down the chain to the surrounding scope, and so on and son on until it hits rock bottom (Object). You can grab the current top-level object via the keyword this.

To illustrate the power of this sytem, let's try putting scope objects to use. Blocks provide a context method for grabbing the context in which they were written. Putting the pieces together, in-context is easily defined - which converts a block into one that is evalulated in its original scope:

Here we've defined a method on all Blocks. This method clones its target, yielding a new object that delegates to the block; any messages it doesn't understand go there. On this new block object, we've overridden the call and call: methods. These overrides use context for getting the original block's context, and join:s the block itself into its context.

What join: does is call a block with a given toplevel object, keeping the block's context in scope (at a higher priority than the target).

Example:

> a = 1
1
> 2 join: { + a }
3
> $a join: { 5 join: { - ord } }
-97
> ord = 10
10
> $a join: { 5 join: { - ord } }
-10

Thus, it is often used for playing around with the scope for some code, which is exactly what we want to do with in-context - create a block that is evaluated directly onto its context rather than a nested scope.

Now let's give it a shot:

> a = 1
1
> { a = a + 1 } in-context call
2
> a
2

Ta-da! Rather than setting a to 2 inside of the block, it was actually executed in the block's context, thus redefining a.

What with the scoping system being objects and all, this means you can go beyond just defining "variables". When you see x = 1, it's not saying "set the variable x to 1", it's saying "insert a slot on the top-level object that responds to x by yielding 1." But you aren't limited to single messages like x. Oh, no. Feel free to define a keyword method if you want, it works just fine:

> foo: x := ("fooing: " .. x show) print
@ok
> foo: @hi
fooing: @hi
"fooing: @hi"

Which brings me to...

Keywords!

In Atomo, you're either dispatching single messages, such as make-sandwich, or a keyword-delimited message with multiple values, known as keyword messages.

Example:

> [1, 2, 3] at: 0
1
> 1 + 1
2
> [1, 2, 3, 4] at: 3 put: 5
[1, 2, 3, 5]

The syntax for keyword dispatch is very flexible, to ease the flow of writing code that uses chains of them. For example, this syntax is perfectly valid, and is called chaining:

1 (+ 2) (divides?: 6) not

This gets parsed as regular ol' dispatching, as in:

((1 + 2) divides?: 6) not

Those parentheses at the front come at a huge cost to the flow of your writing - every time you want to add a dispatch, you would have to go back to the beginning, add a (, and find where you were. Keyword chains remedy that by allowing you to use parentheses wherever you are to effectively turn them into a single-dispatch syntax.

Messages & Multiple Dispatch

In Atomo, there is no "call a method Y on object X." It's all just sending messages. When you send a message to something, it may or may not understand it - it isn't a reference to a solid method definition in any sense. So if it doesn't know how to handle the message, you get "message not understood," not "method not found."

This way of thinking makes a huge difference in how you reason about things in Atomo, especially when it comes to multiple dispatch. In a nutshell, multiple dispatch means you define your methods in terms of everything involved - not just one object. When you define a keyword method, it has a number of roles - objects that the method involves. If a message is not understood by one role, it moves on to the next, and so on.

If no roles understand the message, it sends either did-not-understand: (for single messages) or did-not-understand:at: (for keyword messages) to each object involved until one handles it. If none of them respond, you get an error.

Example:

> X = Object clone
<object (delegates to 1 object)>
> X did-not-understand: m := "I have no idea what you mean by this: " .. m show
@ok
> X make-me-a-sandwich
"I have no idea what you mean by this: <message <object> make-me-a-sandwich>"

That's a lot to take in at once, so let's just dive in and play around with this thing. We'll write a small Rock-Paper-Scissors game. First we'll define the three different objects involved in the game:

Rock = Object clone
Paper = Object clone
Scissors = Object clone

Pretty simple - to make a new object just clone Object. Here we have Rock, Paper, and Scissors defined and ready for action. Now comes the fun part - defining how the game works!

Rock beats?: Scissors := True
Paper beats?: Rock := True
Scissors beats?: Paper := True
_ beats?: _ := False

The beats?: method has two roles - the first one is being checked to see if it "beats" the second one in our game. In this case, the standard rules apply - rock beats scissors, paper beats rock, and scissors beats paper. Let's try it out!

> Rock beats?: Paper
False
> Rock beats?: Scissors
True

Hooray! It works! Above we defined the three winning cases, and left all the other cases to fall into the catch-all - the one defined with _ in place of both roles. This method is the least precise out of all of them, so it only gets chosen if none of the other, more specific, definitions match the dispatch. Hence, all losing combinations yield False.

You may be wondering where these methods actually end up when they involve multiple roles. Let's take a peek:

> Rock
<object (delegates to 1 object)>
  beats?: <object> := True

> Paper
<object (delegates to 1 object)>
  beats?: <object> := True

> Scissors
<object (delegates to 1 object)>
  beats?: <object> := True

As you can see, our definitions were only placed on the first role involved, rather than duplicated for each object. This is because searching for a method always begins with the first role, and from then on it's just pattern-matching for the rest of the roles. It's not very clear here, since it just says <object> for the other roles, but the second roles are Scissors, Rock, and Paper, respectively. Our catch-all, _ beats?: _ ended up being placed on Object, which is a bit too large to just "take a peek."

In a cleaner system you would use something like RPS as the "catch-all", and define each object in the game as a clone of that, rather than Object.

Wrapping Up

That's about all there is to it; Atomo is a simple language, so once you know everything here you can go quite far. I recommend moving on to Syntax so you know what you're typing, and then perhaps to The Ecosystem if you plan on sharing your code or using other libraries. The other sections are mostly references for the various systems and types of values in Atomo; you should check those out too!