Operators
Operators in milang are ordinary functions with special syntax. Every operator can be used in prefix form by wrapping it in parentheses, and any function can be used infix with backtick syntax.
Arithmetic
| Operator | Meaning |
|---|---|
+ | Addition (also string concatenation) |
- | Subtraction |
* | Multiplication |
/ | Division (integer for ints, float for floats) |
% | Modulo (integers only) |
** | Exponentiation (integer exponent) |
a = 2 + 3
b = 10 - 4
c = 3 * 7
d = 10 / 3
e = 10 % 3
f = 2 ** 10
build = {target = c, os = linux, arch = x86_64}
a = 5
b = 6
c = 21
d = 3
e = 1
f = 1024
Float division produces a decimal result:
a = 7.0 / 2.0
b = 3.14 * 2.0
build = {target = c, os = linux, arch = x86_64}
a = 3.5
b = 6.28
Comparison
Comparison operators return 1 (true) or 0 (false). == and /= work
structurally on records, lists, and strings.
| Operator | Meaning |
|---|---|
== | Equal |
/= | Not equal |
< | Less than |
> | Greater than |
<= | Less than or equal |
>= | Greater than or equal |
a = 3 == 3
b = 3 /= 4
c = 5 > 2
d = [1, 2] == [1, 2]
e = "hello" == "hello"
build = {target = c, os = linux, arch = x86_64}
a = 1
b = 1
c = 1
d = 1
e = 1
Logical
Logical operators short-circuit and return 1 or 0. not is a function,
not an operator.
a = 1 && 1
b = 1 && 0
c = 0 || 1
d = 0 || 0
e = not 0
f = not 1
build = {target = c, os = linux, arch = x86_64}
a = 1
b = 0
c = 1
d = 0
e = 1
f = 0
Short-circuit evaluation means the right-hand side is never forced when the left side determines the result:
safe = 0 && (1 / 0) -- 0, right side never evaluated
String Concatenation
The + operator also concatenates strings:
greeting = "hello" + " " + "world"
build = {target = c, os = linux, arch = x86_64}
greeting = hello world
Cons
The : operator prepends an element to a list. It is right-associative.
xs = 1 : 2 : 3 : []
build = {target = c, os = linux, arch = x86_64}
xs = [1, 2, 3]
Pipe
x |> f is syntactic sugar for f x, enabling left-to-right data flow:
double x = x * 2
result = 5 |> double
build = {target = c, os = linux, arch = x86_64}
double = <closure>
result = 10
Composition
f >> g composes left-to-right (\x -> g (f x)).
f << g composes right-to-left (\x -> f (g x)).
double x = x * 2
inc x = x + 1
pipeline = double >> inc
a = pipeline 5
build = {target = c, os = linux, arch = x86_64}
double = <closure>
inc = <closure>
pipeline = <closure>
a = 11
Record Merge
a <- b produces a new record with all fields from a, overwritten by fields
from b:
base = {x = 1; y = 2; z = 3}
updated = base <- {x = 10; z = 30}
build = {target = c, os = linux, arch = x86_64}
base = {x = 1, y = 2, z = 3}
updated = {x = 10, y = 2, z = 30}
Block Argument
f => body passes body as the last argument to f, where body is an
indented block. This is useful for passing multi-line expressions cleanly:
values =>
1
2
3
The => operator is syntactic sugar — f => body is equivalent to f body.
The block is parsed as an indented scope, making it natural for DSLs and
builder patterns:
ann ffi ns = values =>
ffi.struct "Point" |> ffi.field "x" "int32" |> ffi.field "y" "int32"
ffi.out "decompose" |> ffi.param 1 "int32"
Operators as Functions
Wrap any operator in parentheses to use it in prefix (function) position:
a = (+) 3 4
b = (*) 5 6
total = fold (+) 0 [1, 2, 3, 4, 5]
build = {target = c, os = linux, arch = x86_64}
a = 7
b = 30
total = 15
Functions as Infix Operators
Surround a function name with backticks to use it as an infix operator:
bigger = 3 `max` 7
smaller = 3 `min` 7
build = {target = c, os = linux, arch = x86_64}
bigger = 7
smaller = 3
User-Defined Operators
You can define custom operators just like functions. Precedence and
associativity are set with the parse domain :!. See the
Parse Declarations and User Operators
chapters for details.
(<=>) a b = if (a == b) 0 (if (a > b) 1 (0 - 1))
(<=>) :! {prec = 30; assoc = Left}