Appearance
Values & Functions
At the top level, AIVI keeps values and functions separate:
valuedeclares a named constant expressionfuncdeclares a named pure function
That split keeps intent obvious in larger modules and matches the current surface language directly.
Values
A value binds a name to a single expression:
aivi
value answer : Int = 42
value greeting : Text = "Hello"
value isReady : Bool = TrueType annotations are optional when the compiler can infer them, but they are useful in public modules and documentation.
Values can refer to earlier values:
aivi
value width : Int = 24
value height : Int = 20
value cellCount : Int = width * heightValues can also hold records and lists:
aivi
type BoardSize = {
width: Int,
height: Int
}
value boardSize : BoardSize = {
width: 24,
height: 20
}
value checkpoints : List Int = [
4,
8,
12
]Functions
A func declaration names a pure function:
aivi
type Int -> Int -> Int
func add = x y =>
x + y
value total = add 3 4Function signatures can live on a preceding type line, and the func header keeps parameters unannotated.
aivi
type Text -> Text
func greet = name =>
"Hello, {name}!"
value greetingText = greet "Ada"Contextual function inference
Local same-module functions can sometimes be inferred from nearby usage even without a standalone type line:
aivi
func keep = value =>
value
value chosen : Int = keep 1This first slice is intentionally conservative. It only infers same-module monomorphic func declarations when nearby callsites, partial applications, parameter annotations, or result expectations provide enough information to prove one concrete signature. Underconstrained helpers like func id = x => x still need an explicit type line.
Multiple parameters
Parameters are separated by spaces, not commas:
aivi
type Int -> Int -> Int -> Bool
func between = low high n =>
n >= low and n <= high
value scoreAllowed = between 0 100 42Multi-line bodies
Function bodies are still just expressions, so multi-line definitions usually lean on pipes:
aivi
type Int -> Text
func describeScore = arg1 => arg1 >= 50
T|> "good"
F|> "keep going"
value scoreLabel = describeScore 88When a stage needs both the old and new value, use #name instead of breaking out into a helper just to simulate a local binding. If a stage only needs its current subject once, prefer . and keep the pipe simple.
aivi
type Int -> Int
func compareWithPrevious = value => value
|> #before before + 1 #after
|> after - beforeUnary subject sugar
When a unary function starts from its argument directly, you can omit the explicit parameter and use . as the implicit subject:
For example, func statusLineFor = .status projects directly from the implicit subject, and func scoreLineFor = "Score: {.}" interpolates it without introducing an explicit parameter.
The same shorthand also works for pipe-rooted bodies. When the unary body starts with a pipe operator, you can omit the explicit . head and start directly with the stages:
aivi
type Text -> Text
func trimStatus =
||> " ready " -> "ready"
||> _ -> .If the unary input is intentionally ignored, write _ => ... explicitly:
aivi
type Int -> Text
func constantLabel = _ =>
"ready"Selected subject headers
When a function has multiple parameters but its body should immediately continue from one specific argument, mark that argument with ! and omit =>:
aivi
type Int -> Int -> Int
func add = left right =>
left + right
type Int -> Int -> Int
func addFrom = amount value => value
|> add amount
value total = addFrom 2 40The marked parameter becomes the subject for the following continuation, so the next line can start with |> directly. The same sugar also works for patch-rooted bodies:
aivi
type Counter = {
total: Int,
ready: Bool
}
type Counter -> Int -> Counter
func bump = counter amount =>
counter <| {
total: counter.total + amount,
ready: True,
}
value bumped = bump ({ total: 2, ready: False }) 3You can also select a projection from the preceding named parameter:
aivi
type Z = { z: Int }
type Y = { y: Z }
type X = { x: Y }
type Int -> Int
func addOne = value =>
value + 1
type X -> Int
func readNested = state => state.x.y.z
|> addOne
value nestedTotal =
readNested (
{
x: { y: { z: 40 } }
}
){ x.y.z! } means "use state.x.y.z as the subject for the continuation." It is header sugar for subject selection, not a general parameter-destructuring form.
Calling functions
Call a function by writing the function name followed by its arguments:
aivi
type Int -> Int -> Int
func area = width height =>
width * height
value roomArea = area 5 8If an argument is itself an expression, wrap it in parentheses:
aivi
type Int -> Int -> Int
func area = width height =>
width * height
value adjustedArea = area (2 + 3) (4 * 2)That includes negative literals in call position, for example abs (-3) rather than abs -3.
Partial application
Functions can be partially applied. Supplying fewer arguments returns another function:
aivi
type Int -> Int -> Int
func multiply = left right =>
left * right
value double = multiply 2
value ten = double 5Inline lambdas
Anonymous lambdas are ordinary expressions. Explicit lambdas can take one or more named parameters. Use them when you want an inline adapter or predicate:
aivi
type Coord = Coord Int Int
value items : List Coord = [
Coord 0 0,
Coord 1 1
]
value cell : Coord = Coord 1 1
value explicitMatch : Bool = any (coord => coord == cell) items
value next : Int = 0
|> x => x + 1For short unary predicates, AIVI also accepts a narrow shorthand based on the existing subject syntax:
aivi
type Score = { score: Int }
value items : List Int = [
1,
2,
3
]
value threshold : Int = 2
value shorthandMatch : Bool = any (. == threshold) items
value highScore : Score -> Bool = .score >= 10That shorthand is always unary and only applies to composed dot-rooted expressions such as . == cell or .score >= 10. Bare . and .field keep their established ambient-subject meaning so existing pipe, patch, and unary-subject code keeps reading the same way.
Named helpers and inline lambdas
Inline lambdas now work, but named helpers are still better when logic is reused or deserves a stable name:
aivi
type Text -> Text
func trimStatus =
||> " ready " -> "ready"
||> _ -> .
type Text -> Text
func decorateStatus = status =>
"[{status}]"
value shownStatus = " ready "
|> trimStatus
|> decorateStatusThat style gives each step a reusable name and often reads better in longer pipes.
When a unary helper body starts with a pipe operator, the leading-pipe form is equivalent to the same helper written with an explicit unary subject head such as func trimStatus = . followed by the same stages.
Structural patches
Use <| to produce an updated value without mutating the original:
aivi
type User = {
name: Text,
isAdmin: Bool
}
value user : User = {
name: "Ada",
isAdmin: False
}
value promoted : User =
user <| {
name: "Grace",
isAdmin: True,
}patch { ... } builds a reusable same-shape update function:
aivi
value promote : (User -> User) =
patch {
isAdmin: True,
}Selectors are relative to the current focus:
- record roots accept either
fieldor.<field>; nested field steps still use dots, as inprofile.name [*]traversesListelements orMapvalues[predicate]filtersListelements["key"]or[.key == "id-1"]selectsMapentries
Inside patch predicates, use dot-prefixed projections such as .active, .price, .key, and .value.
The current checked slice also accepts constructor focus through Some, Ok, Err, Valid, Invalid, and same-module constructors with exactly one payload field.
:= stores a function value as data instead of applying it during patch execution.
aivi
type Int -> Int
func increment = n =>
n + 1
type Counter = {
step: Int -> Int
}
value keepCounter : (Counter -> Counter) =
patch {
step: := increment,
}Structural removal syntax (field: -) removes a field from the result type. See Record Patterns § Patch removal for details.
Type annotations
Both value and func use : for type annotations:
aivi
value count : Int = 0
type Int -> Int
func negate = n =>
0 - nSummary
| Form | Example |
|---|---|
| Value | value answer : Int = 42 |
| Function | type Int -> Int -> Int / func add = x y => x + y |
| Function call | add 3 4 |
| Partial application | value double = multiply 2 |
| Patch apply | `value promoted = user < |