Milang Syntax Cheatsheet
Milang is a functional language with zero keywords, Haskell-like syntax, and partial evaluation as the core compilation model. Everything is an expression.
Literals
42 -- integer
3.14 -- float
"hello" -- string (supports \n \t \\ \")
"""
multi-line -- triple-quoted string (Swift-style margin stripping)
string -- closing """ indentation defines the margin
"""
[] -- empty list (Nil record)
[1, 2, 3] -- list literal (desugars to Cons/Nil chain)
Bindings
x = 42 -- value binding
f x y = x + y -- function binding (params before =)
lazy := expensive_calc -- lazy binding (thunk, evaluated on first use)
Functions & Application
f x y = x + y -- define: name params = body
f 3 4 -- apply: juxtaposition (left-associative)
(\x -> x + 1) -- lambda
(\x y -> x + y) -- multi-param lambda
f 3 |> g -- pipe: g (f 3)
f >> g -- compose left-to-right: \x -> g (f x)
f << g -- compose right-to-left: \x -> f (g x)
Operators
All operators are just functions. Standard arithmetic, comparison, logical:
+ - * / % ** -- arithmetic (** is power)
== /= < > <= >= -- comparison
&& || -- logical (short-circuit)
not x -- logical negation (function, not operator)
+ `+` -- string concat (use `+` for both numeric and string)
: -- cons (right-assoc): 1 : 2 : [] = [1, 2]
Operators as functions and functions as operators:
(+) 3 4 -- operator in prefix: 7
3 `add` 4 -- function in infix (backtick syntax)
Records
-- Anonymous record
point = {x = 3; y = 4}
-- Field access
point.x -- 3
-- Positional access (by declaration order)
point._0 -- 3 (first field)
-- Record update
point2 = point <- {x = 10} -- {x = 10, y = 4}
-- Nested access
world.io.println -- chained field access
-- Destructuring
{x; y} = point -- binds x=3, y=4
{myX = x; myY = y} = point -- binds myX=3, myY=4
-- Parsing gotcha
-- When passing a record literal directly as an argument you may need to parenthesize
-- the literal or bind it to a name to avoid parse ambiguity. For example:
-- -- may need parentheses
-- getField ({a = 1}) "a"
-- -- or bind first
-- r = {a = 1}
-- getField r "a"
ADTs (Algebraic Data Types)
Uppercase bindings with braces declare tagged constructors:
Shape = {Circle radius; Rect width height}
-- Creates constructors:
c = Circle 5 -- {radius = 5} tagged "Circle"
r = Rect 3 4 -- {width = 3, height = 4} tagged "Rect"
-- Named fields also work:
Shape = {Circle {radius}; Rect {width; height}}
Pattern Matching
The -> operator introduces match alternatives:
-- Inline alternatives (separated by ;)
f x = x -> 0 = "zero"; 1 = "one"; _ = "other"
-- Indented alternatives
f x = x ->
0 = "zero"
1 = "one"
_ = "other"
-- Pattern types
42 -- literal match
x -- variable (binds anything)
_ -- wildcard (match, don't bind)
Circle -- constructor tag match
Rect -- constructor tag match (fields accessible via .field)
[a, b, c] -- list pattern (exact length)
[first, ...rest] -- list pattern with spread
-- Guards (| condition = body)
abs x = x ->
n | n >= 0 = n
n = 0 - n
-- Pattern matching in case expressions
area s = s ->
Circle = 3.14 * s.radius * s.radius
Rect = s.width * s.height
Scopes & Multi-line Bodies
Indented lines under a binding form a scope:
-- With explicit body expression (body = the expression after =)
compute x = result
doubled = x * 2
result = doubled + 1
-- Returns: value of `result` (15 when x=7)
-- Without body expression (scope returns implicit record)
makeVec x y =
dx = x ** 2
dy = y ** 2
sumSquares = dx + dy
-- Returns: {dx = 49, dy = 9, sumSquares = 58}
-- Bare expressions in scopes evaluate for effect, not included in record
main world =
world.io.println "hello" -- effect: prints, result discarded
world.io.println "world" -- effect: prints, result discarded
Inline scopes use braces:
f x = result { doubled = x * 2; result = doubled + 1 }
IO & the World
IO uses capability-based design. main receives world:
main world =
world.io.println "hello" -- print line
world.io.print "no newline" -- print without newline
line = world.io.readLine -- read line from stdin
contents = world.fs.read.file "f" -- read file
world.fs.write.file "f" "data" -- write file
world.fs.write.append "f" "more" -- append to file
exists = world.fs.read.exists "f" -- check file exists
world.fs.write.remove "f" -- delete file
result = world.process.exec "ls" -- run shell command
world.process.exit 1 -- exit with code
args = world.argv -- command-line args (list)
val = world.getEnv "PATH" -- environment variable
0
-- Pass restricted capabilities to helpers
greet io = io.println "hello"
main world = greet world.io -- only give IO, not process/fs
hello
main’s return value is the process exit code (int -> exit code, non-int -> 0).
Imports
-- Local file import (result is a record of all top-level bindings)
math = import "lib/math.mi"
x = math.square 5
-- URL import (cached locally)
lib = import "https://example.com/lib.mi"
-- URL import with sha256 pinning (required for reproducibility)
lib = import' "https://example.com/lib.mi" ({sha256 = "a1b2c3..."})
-- C header import (auto-parses function signatures)
m = import "/usr/include/math.h"
x = m.sin 1.0
-- C header with source file linking
lib = import' "mylib.h" ({src = "mylib.c"})
-- C header with extended options
lib = import' "mylib.h" ({
sources = ["a.c", "b.c"]
flags = "-O2"
include = "include"
pkg = "libpng"
})
Binding names are always lowercase. Uppercase names are reserved for type declarations (union types, record constructors, type annotations).
Use milang pin <file> to auto-discover URL imports and add sha256 hashes.
Annotation Domains
Milang has five binding domains, each with its own operator:
-- Value domain (=) — what it computes
add a b = a + b
-- Type domain (::) — structural type annotation
add :: Num : Num : Num
-- Sized numeric types: Int', UInt', Float' take a bit width
-- Prelude aliases: Int = Int' 0, UInt = UInt' 0, Float = Float' 64, Byte = UInt' 8
add8 :: Int' 8 : Int' 8 : Int' 8
-- Traits domain (:~) — computational attributes / effect sets
add :~ pure -- pure = [] (no effects)
greet :~ [console] -- needs console capability
server :~ [console, fs.read, fs.write]
-- Documentation domain (:?) — human-readable docs
add :? "Add two numbers"
add :? {summary = "Add two numbers"; params = {a = "First"; b = "Second"}}
add :? """
Add two numbers together.
Returns their sum.
"""
-- Parse domain (:!) — operator precedence/associativity
(+) :! {prec = 6; assoc = Left}
-- All domains can coexist on one binding:
distance :? "Euclidean distance"
distance :: Point : Point : Num
distance :~ pure
distance p1 p2 = (p2.x - p1.x)**2 + (p2.y - p1.y)**2
Thunks & Laziness
~expr -- thunk: delays evaluation
x := expensive -- lazy binding: creates thunk, evaluates once on use
Metaprogramming
#expr -- quote: capture AST as a record
$expr -- splice: evaluate quoted AST back to code
f #param = $param -- auto-quote param: compiler quotes arg at call site
Comments
-- line comment
/* block comment (nestable) */
Built-in Functions
Core
if cond then else— conditional (auto-quotes branches via#-params)len xs— length of string or listtoString x— convert to stringtoInt s— parse string to int; returnsJuston success,Nothingon failuretoFloat s— parse string to float; returnsJuston success,Nothingon failure
String
charAt i s— character at index; returnsJustcharacter when index valid, otherwiseNothingslice start end s— substringindexOf needle haystack— find substring (-1 if not found)split sep s— split string by separatortrim s— strip whitespacetoUpper s/toLower s— case conversionreplace old new s— string replacement
List (prelude)
head xs/tail xs/last xs/init xs— returnMaybe(Justvalue orNothing)map f xs/filter f xs/fold f acc xsconcat xs ys/push xs x/reverse xstake n xs/drop n xs/slice start end xszip xs ys/enumerate xs/range start endsum xs/product xs/join sep xsany f xs/all f xs/contains x xsat lst i/at' i lst— element at index (returnsMaybe)sort xs/sortBy f xs
Record introspection
fields r— list of{name, value}recordsfieldNames r— list of field name stringstag r— constructortagstring (or “”)getField r name— dynamic field access; returnsJust valueif present, otherwiseNothing.setField r name val— return new record with field set
Utility
id x/const x y/flip f x yabs x/min a b/max a b
Compiler Modes
milang run file.mi # compile + run
milang compile file.mi o.c # emit C code
milang dump file.mi # show parsed AST
milang reduce file.mi # show partially-evaluated AST
milang repl # interactive REPL