The Tao gave birth to machine language. Machine language gave birth to the assembler.
The assembler gave birth to the compiler. Now there are ten thousand languages.
Each language has its purpose, however humble. Each language expresses the Yin and Yang of software. Each language has its place within the Tao.— The Master Programmer, The Tao of Programming by Geoffrey James (1987)
example: namespace = {
hello: void() = {
print("hello, word\n");
};
};
[Entrypoint]
main: void() = {
example.hello();
};
Patterns used in one place should be reused in others:
- Declarations will always be of form
name: type-sig
, read as: "name is-a type-sig". 1 - Types can be suffixed with obvious modifiers, like
int[]
(int array),void(int)
(void when-called-with an int). - Assignment will always follow the pattern of
name = value
.
Code should always clearly convey intent:
- Declarations and type signatures are always readable and unambiguous.
- Statements should always be readable from left to right when transcribed to spoken words, so
ordinals: str[](int[])
is read as "ordinals is-a string array when-called-with an int array". - Function types are
return-type(param-name: param-type[, ...])
, read as: "return-type, when-called-with: param-name is-a param-type". 2
Opt-Out Async3
Async/await patterned programming enables performant, block-free code:
- The entire program will be run in an async executor, unless you opt-out.4
- Async state is stored on the stack.
Concurrent Safety Aware3
Static analysis will be aware (to the extent it can be) of concurrent safety:
- Primitive containers and other data types provided by the standard library will be annotated with static type metadata exposing whether they are safe to use in concurrent programming.
Data passing will prefer references with explicit mutability over unnecessary copying.
C-style strings (null-terminated strings) are not supported. Instead:
- The language will have first-class support for views/ranges/slicing.
- String types will be lightweight views over byte buffers.
- Variable width Unicode support comes naturally with the use of performant iterators5.
foo: bar (foo is-a bar
)
Variables |
foo: str;
bar: int = 0; |
Read as: foo is-a str. |
example.impl: namespace = {
}; |
Read as: example.impl is-a namespace. |
|
Functions |
main: void(args: str[]) = {
}; |
Read as: main is-a void when-called-with: args is-a str-array. |
Types |
foo: type;
bar: type = {
this: foo;
};
|
Read as:
|
Footnotes
-
This pattern holds even when defining types, namespaces, and functions. ↩
-
Currently, all callable types' parameters must have names.
For example, you can say
foo: void(a: int)
, but notfoo: void(int)
.In the future3, names will still be required when assigning a function, but not when declaring one without defining it, or when specifying an inner type:
✅ foo: void(logger: void(str)) = { logger("message"); }; ❌ foo: void(void(str)) = {/* How do I know how to access the logger? */}; -
This is a future feature that is not yet supported. ↩ ↩2 ↩3 ↩4
-
In the future3 the
[Entrypoint]
metadata decorator will support something like[Entrypoint(async=false)]
. However, all blocking standard library functions will be async, so there will be additional work required to have a synchronous entrypoint. ↩ -
A Branchless UTF-8 Decoder
— Chris Wellons, null program (2017). ↩ -
Namespaces must be initialized with a body. ↩
-
Namespaces with dotted names is syntactically equivalent to defining them separately:
↩example.impl: namespace = {}; // Same as: example: namespace = { impl: namespace = {}; };