What & Why

Philosophy

Atomy is a language designed to grow. It achieves this goal by having a very simple grammar, much like Lisp. Rather than statements, everyting in Atomy is an expression. These expressions are not merely written, they are designed. Readability, concision, and flow are the primary goal, and this goal is most easily achieved by keeping the grammar small, and building everything up from there with macros and metaprogramming.

Time after time new languages are introduced, which are often snapshots of what we needed when it was designed. These languages have so many features built-in as syntax that countless libraries and applications hinge on version numbers solely to get past the parsing stage. Ruby and Python are examples of these.

Atomy instead hopes to introduce these features as libraries. The grammar in 1.0 should either be "the end" of its progression, or later generalized in a way that is backward-compatble.

Idioms & Freebies

Platform

Atomy's platform of choice is the Rubinius VM. Its syntax already scales upward to encompass every common Ruby construct (with its own flavor), and Ruby libraries can be used transparently, as long as they run on Rubinius.

It's Algebraic!

An integral part of Atomy's core is pattern-matching dispatch. This gives you immense expressive power, with a form of multiple dispatch. Atomy's style favors declaring your data structures first and defining methods in a functional style in terms of all of their "roles" (the receiver and the message's arguments).

To show this, I'll go through part of the the Doc system, used for pretty-printing, which exemplifies this beautifully.

Here we define each type of Doc there is, structurally. This creates classes and subclasses recursively, as well as constructors and accessors for the data they specify:

data(Doc):
  Empty
  Beside(@left, @right, @space?)
  Above(@above, @below, @overlap?)
  Text(@value)

One thing we'll want to do with a document is compute its width. This is trivial; just a single message sent to the Doc with no arguments. Note that we're defining each method alongside one another; not inside of a class body. This allows us to group related methods by meaning, not by their receiver. This better shows the relationships between each method:

Empty width := 0
Text width := @value size
Beside width :=
  if(@space?)
    then: 1 + @left width + @right width
    else: @left width + @right width
Above width := [@above width, @below width] max

A great example of pattern-matching and multiple dispatch comes into play when composing two documents with the <+> operator. This positions two Docs beside each other, separated by a space, unless either are Empty:

(l: Doc) <+> (r: Doc) := Beside new(l, r, true)
(d: Doc) <+> Empty := d
Empty    <+> (d: Doc) := d
(l: Doc) <+> (a: Above) := do:
  first = l <+> a above
  rest = a below nest(l width + 1)
  Above new(first, rest, a overlap?)

This is opposed to Ruby-style, where you open classes to add methods to them, and then handle the arguments from within the single method definition, often with the case statement. This form is also supported, but it is much more verbose.

A Handful of Shiny Toys

a syntax-oriented macro system

Atomy has a small grammar independent from its language semantics. For example, there is no "message send" primitive node, and there aren't class/global/instance variable primitives. These are defined using Atomy's all-powerful macro system.

You may be familiar with macros from Common Lisp, Scheme, or Clojure. In these languages a macro is a special named function that takes its arguments unevaluated, and builds another form for evaluating later. You may also know them from C/C++ as primitive text rewriting mechanisms.

Atomy goes somewhere neither really cover: its macros aren't named anything; they expand arbitrary expressions. I probably shouldn't be showing you this, but...

Example:

> macro(2 + 2): 5
#<Rubinius::CompiledMethod _expand_+ file=wrapper>
> 2 + 2
5
macro-quotes

Generalized string quotation. Examples include regular expressions, raw strings, and word-lists. It's easy to create new quoters for things other languages would have as literals.

Example:

> r"[a-z][\p{L}]*"(u)
/[a-z][\p{L}]*/u
> raw"s\up?"
"s\\up?"
> w"foo bar baz"
["foo", "bar", "baz"]
explicit mutation

Atomy has two "assignment"-ish operators; = and =!. The former will always pattern-match and introduce locals to the current immediate scope. The latter will also pattern-match, but it will only mutate existing locals, never introducing them.

This keeps your locals local and makes it clear when you really want to mutate something in-place.

Example:

> a = 0
0
> do: a = 1, a
1
> a
1
> do: a =! 1, a
1
> a
1
syntax as macros

Where Ruby has global, instance, and class variable syntax, Atomy actually implements these as prefix macros. The same goes for for symbols (.to-a), particles (.foo(1, _), splats (*args), block-passing (&foo), and a few others. If something can be a macro or regular method, it will be. This has been applied very broadly.

particles

Like Atomo, Atomy has a concept of "partial messages" which can act as ad-hoc data structures. They respond to call and to-proc, so you can use them as block shorthand.

Example:

> [1, 2, 3] collect .(* 3)
[3, 6, 9]

You can also pattern-match them, so they are often used for simple data structures, similar to how atoms and tuples are used in Erlang.

Example:

> .ok(x) = .ok(1)
.ok(1)
> x
1

In a particle, _ acts as a placeholder. Sending call will fill these values in order with the values you pass, and then send the message.

Example:

> .is-a(_)? call(1, Integer)
true

Note that they have very similar syntax as Symbols; in fact, Atomy also defines call on Symbol so you can generally use them interchangeably.

Example:

> .odd? call(1)
true
> .odd? call(2)
false
extensible pattern-matching

If you don't mind digging down into Rubinius bytecode, you can define your own pattern-matchers. It's very easy to do, and there is a very broad spectrum of pattern-matchers built-in because of this. See pattern-matching.

Influences & Thanks

Common Lisp & Clojure

extensive macro system, gensyms (via names), powerful string formatting system, conditions & restarts, thread-local dynamic environment (via dynamic), controlled evaluation (via evaluate-when and for-macro), let-macro, dynamic variable -based Input & Output, Clojure-style namespaces

Haskell

pattern-matching, algebraic data definition (via data), functional-style method defining, pretty-printing system, parens slaying with $, list comprehension macros

Ruby

functionality baseline, with desired improvements (selector namespacing, safer alternatives to instance_eval for DSLs, like onto)

Slate & Atomo

multiple dispatch (via pattern-matching, like Atomo; Slate doesn't work that way), particles, pseudo-keyword-messages via macros and pattern-matching

Potion & Poison

colon block syntax + commas

Erlang

message-sending concurrency