darcsden :: alex -> the -> browse

the programming language

root

readme

This document is a work-in-progress, outlining what I'd like this language to be. It'll be updated and expanded over time.

Parts of it may be out of date. There will probably be inconsistencies.

Key Words

Multiple dispatch, dynamic, strongly typed, functional, immutable, message-passing concurrency, prototyping

Influences

Scheme, Slate, Self, Io, Haskell, Ruby, Erlang

Syntax

Literals

Integer1
List[1, 2]
Char'a'
String"foo"
Double1.0
Particle@foo (+ more)
Block{ a b | a + b }

Messages

1 negate
    => -1

1 negate negate
    => 1

1 + 1
    => 2

'a' as: Integer
    => 97 (ASCII code)

Note that operators must be surrounded by whitespace. This lets us use more symbols in identifiers, e.g. up-to.

Multiple keywords:

0 up-to: 10 do: { n | print n }

This is dispatched as one message, up-to:do:, with the values 0, 10, and { n | print n }

Chained keywords:

"foo/bar" (split-on: '/') (each: print)
    => foo
       bar

Currying:

[1, 2, 3] map: (+ 2)
['a', 'b', 'c'] map: (as: Integer)

Definitions

Simple assignment:

a = 1

This actually defines a method on the current scope which responds to the symbol a being sent by returning 1.

Pattern matching:

0 fact = 1
(n: Integer) fact = n * (n - 1) fact

These are two distinct methods that match on different messages being sent. While the first definiton matches only on the integer 0, the second matches on any Integer. The most precise match gets chosen for the dispatch.

Note that the second definition could also have been simply n fact = n * (n - 1) fact, but explicitly targeting Integer is better form.

Also note that, were Integers and Ints to have the same syntax, the first definiton would be ambiguous: is it on an Integer or an Int? Thus, the literal for Ints was born: 1i, mirroring the distinction between Float and Double literals (2.0f vs. 2.0).

Something more fancy:

(n: Integer) up-to: (end: Integer) do: action =
    if: (n == end)
        then: []
        else: (action n . ((n + 1) up-to: end do: action))

Data

New primitive forms of data can be defined with data.

For example:

data: Bool delegates: { True; False }

This defines the following constructor hierarchy:

    Bool
   /    \
True    False

Note that Bool is a valid constructor for itself.

The following is legal:

0 as: Bool = False
1 as: Bool = True

Constructors can have additional slots:

data: Result delegates: { Ok value; Error }

foo = Ok 1
foo value
    => 1

These can also be pattern matched:

(Ok (value = Int)) bar = "this OK is an int! " .. v as: String
(Ok (value = String)) bar = "this OK is a string! " .. v

Note that in pattern matches, the order and existence of the slots being matched on doesn't matter.

Messages

The language uses message-sending to objects, using multiple dispatch.

Defining "methods" on objects is really just defining patterns for objects to respond to.

For example, defining this nonsensical method which takes mutiple keywords:

(n: Integer) add: (d: Double) mul: (f: Float) =
    (n + d) * f

Really defines a method that responds to an add:mul: message sent along with 3 values (any Integer, any Double, and any Float).

Internally, the "matching" is done simply using pattern matching - the same kind of pattern matching nested inside of the message to match on n, d, and f.

This means that you can match on actual values being sent as messages to the object, as well:

Integer "foo" = "Integer responding to foo!"

1 "foo"
    => "Integer responding to foo!"

Combined with pattern matching, this can yield something akin to defining methods on individual integers:

1 foo = "fooing on one"
2 foo = "fooing on two"
(n: Integer) foo = "fooing on any old number"

Or, for that matter, any old message:

Integer _ = "unknown message" print

This will respond to any value being sent to any Integer by printing "unknown message". It will not override other messages as matches are sorted by precision.

Scoping ======= The current scope is just a prototype, delegating to one higher up on the chain, i.e. lexical scoping.

The toplevel target can be replaced. Defining variables is really just adding methods that the current scope responds to to yield the value:

foo = 1
foo
    => 1

bar: n = n + 2
bar: 3
    => 5

As blocks are introduced, usual lexical scoping rules apply; inner blocks can access outer values that were defined before the block's execution, but outer blocks cannot see values defined in the inner blocks.

Concurrency

The provides message-passing concurrency very similar to Erlang's model. The goal is to see what kind of overlap there can be with messages sent to/from processes and messages sent to/from objects (as is already inherent in the language).

Brainstorming:

pid = {
    {
        "waiting for message..." print;
        receive print;
        "got message!" print
    } repeat
} spawn
    => waiting for message...

pid do-something
    => :do-something
       got message!
       waiting for message...

pid do-something-with: 0
    => do-something-with: 0
       got message!
       waiting for message...

The value of using the native message-passing system will shine more with an Actor model; with that you'll get full multiple-dispatch and pattern-matching, just like normal message-passing.