the programming language
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 | 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
--------
---------- -----------------
`Integer` `1`
`List` `[1, 2]`
`Char` `'a'`
`String` `"foo"`
`Double` `1.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 `Integer`s and `Int`s to have the same syntax, the
first definiton would be ambiguous: is it on an `Integer` or an `Int`? Thus,
the literal for `Int`s 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.
|