Learn Mech in Fifteen Minutes

Comments

-- Single line comment.

// Also a single line comment.

Mech does not support multiline comments. Instead, Mech encourages literate programming through a Markdown-esque syntax called Mechdown, exemplified by this document.

Comments are therefore to be used primarily inside of code blocks.

Comments support paragraph formatting, such as bold, underline, links, etc. For example:

 -- Comments support **bold**,  __underline__, [links](/foo/bar), etc.
--

Comments support bold, underline, links, etc.

Identifiers

Identifiers start with letters or most UTF-8 encoded characters, and can contain alphanumeric, most emojis, /, *, +, -, and ^ characters.

  • Hello-Word

  • io/stdout

  • Δx^2

  • 🤖

  • A*

The preferred identifier case in Mech is kebab-case (words deliniated by dashes). Slashes also allow identifiers to be scoped to a particular namespace.

To maintain a consistent style, Mech does not support underscores in identifiers and they will result in a syntax error.

Kinds

A kind describes the type of data a value can hold. Every value in Mech has a kind, and kinds themselves are values.

Kinds describe the data type, not mutability, which is a property of the data binding.

Simple Kinds

Kinds are written inside angle brackets < >. The following kinds represent the built-in data types in a base Mech distribution:

Category

Examples

Signed Integers

<i8>, <i16>, <i32>, <i64>, <i128>

Unsigned Integers

<u8>, <u16>, <u32>, <u64>, <u128>

Complex Numbers

<c64>

Rational Numbers

<rs64>

Floats

<f32>, <f64>

Text

<string>

Logic

<bool>

Kind

<<kind>>

Empty

<_>

Compound Kinds

Kinds compose structurally to describe collections.

  • Matrix: <[T]:2,3> (A 2x3 matrix of T)

  • Table: <|x<T>,y<U>|:3> (A table with columns x and y of types T and U, and 3 rows)

  • Tuple: <(T,U)> (An ordered pair)

  • Map: <{K:V}> (Maps K to V)

  • Set: <{T}> (A set of unique T values)

Kind Annotations & Conversion

Annotations constrain variables or cast literals.

x:=42<u8>--

Literal annotation

y<u8>:=10--

Variable constraint

m:=[
1
2
3
4
5
6
]
--

[1]:1,6

n<[i32]:2,3>:=m--

Reshapes and converts to 2x3 i32 matrix

Custom Kinds & Enums

Aliases:

You can create aliases for semantic clarity.

<point3>:=<[f64]:3>p<point3>:=[
10
20
30
]

Enums:

Enums define a fixed set of variants using atoms.

<color>:=:red|:green|:bluemy-color<color>:=:color/green--

a valid color

other-color<color>:=:color/yellow--

error: invalid variant

Data Types

Number

Numbers in Mech can be classified into the following categories:

  • Integers - Whole numbers, which can be positive, negative, or zero

    • Signed - Can represent both negative and positive values

      • i8 - 8-bit signed integer

      • i16 - 16-bit signed integer

      • i32 - 32-bit signed integer

      • i64 - 64-bit signed integer

      • i128 - 128-bit signed integer

    • Unsigned - Can only represent zero and positive values

      • u8 - 8-bit unsigned integer

      • u16 - 16-bit unsigned integer

      • u32 - 32-bit unsigned integer

      • u64 - 64-bit unsigned integer

      • u128 - 128-bit unsigned integer

  • Float - Decimal numbers with fractional components

    • f32 - 32-bit floating-point number

    • f64 - 64-bit floating-point number

  • Rational - Numbers represented as fractions of integers

    • r64 - 64-bit rational number

  • Complex - Numbers with real and imaginary components

    • c64 - 64-bit complex number

Numeric literals can be expressed in various formats:

  • Floating-point numbers, expressed in standard decimal notation and scientific notation

  • Integers, expressed in decimal (base 10), hexadecimal (base 16), octal (base 8), and binary (base 2)

  • Rational numbers, expressed as fractions a/b, where a is the numerator and b is the denominator

  • Complex numbers, expressed in the form a + bi or a + bj, where a is the real part and b is the imaginary part

Floating-Point Literals

Floating-point literals are used to represent decimal numbers with fractional components. They are flexible in that they can represent both very large and very small numbers, and also can represent integer values between -1.7e308 and 1.7e308 for f64, and between -3.4e38 and 3.4e38 for f32.

Floating-point literals are expressed in standard decimal notation or scientific notation:

42--

inferred as f64

3.14-0.0012.5e+3.-1.2e-2.

Each value is inferred as f64 unless otherwise specified. To specify a specific type, floating-point literals can also be suffixed with a specific kind:

42<f32>3.14<f32>-0.001<f64>2.5e+3.<f32>-1.2e-2.<f64>

Integer Literals

Integer literals can be written in the following formats:

0d42--

decimal

0x2ABC--

hexadecimal

0o654--

octal

0b101010--

binary

Decimals without a prefix are inferred as floating-point numbers (f64), while those with the 0d prefix are inferred as signed integers (i64). Each of the other formats are also inferred as i64 unless otherwise specified. Any integer literal can be suffixed with a specific kind to indicate its type:

42u8--

unsigned 8-bit integer

Decimal literals can be signed or unsigned. Signed literal types include i8, i16, i32, i64, and i128. Unsigned literal types include u8, u16, u32, u64, and u128.

Rational Literals

Rational numbers are used to represent fractions exactly as ratios of two integers. They are expressed in the form a/b, where a is the numerator and b is the denominator.

Rational numbers are written as follows:

3/4-5/27/7--

A whole number represented as a rational

Two rational literals can represent the same numeric value but have different representations:

x:=1/2--

Represents 0.5

y:=2/4--

Also represents 0.5

xy--

Evaluates to true

Complex Literals

Complex numbers are used to represent numbers with both real and imaginary components. They are expressed in the form a + bi or a + bj, where a is the real part and b is the imaginary part.

Complex numbers are written as follows:

3+4i1+-2i0+1i

The imaginary unit can be represented by either i or j. Both representations are equivalent and can be used interchangeably.

String

String literals are written using double quotes ".

"""Hello, world!""Multi Line String""Unicode: 🤖🦾🦿"

Strings may contain spaces and punctuation. Whitespace inside a string literal is preserved.

Escape sequences may be used for special characters:

"line 1\nline 2"
"quote: \""
"tab\tseparated"
"backslash: \\"

When rendered:

"line 1 line 2""quote: """tab separated""backslash: \"

Raw string literals can be created using triple double quotes """. In this form, escape sequences are not processed, and all characters are taken literally, including backslashes and quotes.

"""C:\Users\Name\Documents"""
"""This is a "raw" \string\ literal."""

When rendered:

"C:\Users\Name\Documents""This is a "raw" \string\ literal."

String Concatenation

Strings can be concatenated using the + operator:

first:="Hello"second:="World"greeting:=first+", "+second+"!"

String concatentation is broadcast element-wise over matrices of strings:

prefix:="Item"letters:=[
"A"
"B"
"C"
]
labels:=prefix+letters

If both operands are matrices of strings, they must have compatible dimensions:

m1:=[
"a" "c"
"b" "d"
]
m2:=[
"1" "3"
"2" "4"
]
result:=m1+m2

Conversion to String

All values can be converted to strings explicitly.

x:=42s<string>:=x

Use <[string]> to create a matrix of strings:

m<[string]:2,2>:=[
1
2
3
4
]

This converts a <[f64]:1,4> (row matrix of floats) to a <[string]:2,2> (square matrix of strings).

Boolean

Boolean literals are written using the keywords true and false.

truefalse

Several alterantive forms are also accepted:

--

true

--

false

Logical Operations

Boolean values support the following logical operations:

¬true--

false

true&&false--

false

true||false--

true

Comparison Operations

Comparison operators produce boolean results:

55342<10

Filtering

An array of logical values can be used as an index to filter another array:

x:=[
1
2
3
4
]
ix:=x>2x[ix]--

selects elements where ix is true

Atom

Atoms are written using a leading colon : followed by an identifier:

:foo:help:my-atom:🐦:Δt

Atom names may be any valid identifier, including Unicode characters.

Two atoms with the same name are identical:

a:=:foob:=:fooab--

evaluates to true

Empty

An empty value is written using one or more underscores _:

____

Empty is used to represent missing values in tables and skipped elements in tuple destructuring.

Variables

Variables are created using the := operator:

x:=123

By default, all variables are immutable. To create a mutable variable, prefix the variable name with ~:

~y:=456 y = y+1 --

valid

Semicolons can be used to write multiple statements on one line:

a:=123b:=456

You can use a kind annotation in the variable declaration to constrain the variable to a specific kind:

z<u8>:=255

Data Structures

Matrix

Declare a matrix using square brackets [].

Use semicolons ; or newlines to separate rows.

Use commas , or spaces to separate columns.

[1,2,3]   -- A row vector (1x3)
[1 2 3]   -- The same row vector

[1;2;3]   -- A column vector (3x1)
[1        
 2
 3]       -- The same column vector

[1,2;3,4] -- A matrix (2x2)
[1 2
 3 4]     -- The same matrix
[1 + 2; 3 + 4] -- Elements can be expressions

By default, all matrices are rank 2, meaning they have rows and columns. Elementes are indexed in a [row, column] format.

When parsed and formatted, they look like this:

--

Row Vector

[
1
2
3
]
--

Column Vector

[
1 2 3
]
--

Matrix

[
1 3
2 4
]
--

Vector from expressions

[
1+2 3+4
]

Broadcast Operations

If matrices have the same dimension, standard arithmetic operations are performed element-wise:

m1:=[
1 4
2 5
3 6
]
m2:=[
7 10
8 11
9 12
]
m1+m2--

Adds the two matrices element-wise

If one of the operands is a scalar, it will be added or subtracted from each element of the matrix:

m:=[
1
2
3
4
5
]
m+10--

Adds 10 to each element of the vector

Matrix Access

m:=[
1 4 7
2 5 8
3 6 9
]
m[2,3]--

row/column access => 6

m[2]--

single index (column-major)

m[1,:]--

first row

m[:,2]--

second column

m[1..=2,:]--

first two rows

m[[
1
3
]
,:]
--

rows 1 and 3

Logical Indexing

m:=[
1 4 7
2 5 8
3 6 9
]
mask:=m>5m[mask]--

[6 7 8 9]

rows:=[
true
false
true
]
cols:=[
false
true
true
]
m[rows,:]--

keep rows where mask is true

m[:,cols]--

keep cols where mask is true

m[rows,cols]--

logical row+col selection

Operators

A+B--

element-wise addition

A-B--

element-wise subtraction

A*B--

element-wise multiplication

A/B--

element-wise division

A**B--

matrix multiplication

A'--

transpose

A*B--

cross product

mech:disabled

(1,true)--

two-tuple of f64 and bool

("Hello",42,false)--

three-tuple of string, f64, and bool

(1,("Hello",false))--

nested tuple

(6.3.1) Element Access

Tuple elements are accessed by their 1-based index using the `.` operator.

mech:ex 6.3.1

q:=(10,"b",true)q.1
(6.3.2) Destructuring

Tuples can be destructured into variables:

mech:ex 6.3.2

a:=(10,11,12)(x, y, z):=ax+y+z
(6.4) Record

Records are written using curly braces `{}` with named fields. Each field is written as `name: value` and fields are separated by commas.

mech:disabled

{x:1, y:true}{name:"Alice", age:42, active:true}
Records may be written inline or across multiple lines for readability:

{x:1.2, y:true, label:"pt1"}
(6.5) Table 

Tables are defined using the pipe `|` character. Each column is defined with a name and a kind, which describes the type of data it holds.

x<f32> y<bool>

1.2 true

1.3 false

This creates a table with two columns, `x` of kind `f32` and `y` of kind `bool`:

mech:disabled

x<f32>
y<bool>
1.2 true
1.3 false
You can write tables inline as well:

x<f32> y<bool>

1.2 true

1.3 false

(6.5.1) Coulmn Access

Access table columns as vectors with the `.` operator:

mech:disabled

t.x--

column vector of x values

t.y--

column vector of y values

Row Access

Access table rows as records using the single index operator []:

t[1]--

first row as a record

t[2]--

second row as a record

Table Operators

ab--

inner join

ab--

left outer join

ab--

right outer join

ab--

full outer join

ab--

left semi join

ab--

left anti join

Set

A set is an unordered collection of unique elements. They are defined using curly braces {}.

Elements are separated by commas ,. Duplicate elements are automatically removed.

{}--

An empty set

{1, 2, 3}--

A set of numbers

{"a", "b", "c"}--

A set of strings

{1, 1, 2, 3}--

Duplicates are removed

{1, 2, 3}--

Spaces can also separate elements

Set Operators

A:={1, 2, 3}B:={3, 4, 5}AB--

union

AB--

intersection

AB--

difference

AΔB--

symmetric difference

2A--

membership

9A--

non-membership

Map

You can define maps using curly braces {} with a colon : to separate keys from values.

{"a":1}--

A single key-value pair

{"a":10, "b":20}--

Multiple key-value pairs

{"a":{"b":2}}--

Nested maps

{"a":10, "b":20, "c":30}--

Multi-line format

Functions and Patterns

Functions are defined with a signature and one or more pattern branches.

add(x<f64>, y<f64>) <f64>
(x, y) x+y
.
add(2, 3)

Pattern branches are matched top-to-bottom:

head(xs<[u64]>) <u64>
[x …] x
* 0u64
.

Guarded branches:

max3(t<(u64,u64,u64)>) <u64>
* 0u64
.

Pattern Varieties

  • Wildcard: * matches any value

  • Variable: x matches any value and binds it to x

  • Tuple: (x, y) matches a tuple and binds its elements to x and y

  • Array: [first ... last] matches an array and binds its elements to first and last

  • Slice [head | tail] matches a non-empty array, binding the head to head and the tail to tail

Enums and Match

Define enums using atoms:

<door>:=:open|:closed|:lockedstate<door>:=:open

Use ? to match on values:

label:=state?
:open "open"
:closed "closed"
:locked "locked"
* "unknown".

Tagged variants (payloads):

<result>:=:ok<u64>|:err<string>r<result>:=:ok(42u64)msg:=r?
:ok(n) "ok: "+n<string>
:err(txt) "err: "+txt
* "?".

State Machines

State machines use #Name(...), explicit states, and transitions.

#Counter(n<u64>)<u64>:=
:Count(n<u64>)
:Done(n<u64>)
.
# Counter ( n<u64> ) :Count(n)
:Count(n)
n>0u64 :Count(n-1u64)
n0u64 :Done(0u64)
:Done(n) n
.
#Counter(5u64)

Pattern-based state transitions:

#Echo(xs<[u64]>)<u64>:=
:Start(xs<[u64]>)
:Done(out<u64>)
.
# Echo ( xs<[u64]> ) :Start(xs)
:Start([x …]) :Done(x)
:Done(out) out
.
#Echo([
5u64
3u64
8u64
1u64
]
)