Nomad
Paradigm(s) | functional, probabilistic |
---|---|
Designed by | Async Engineering |
Appeared in | 2018 |
Type system | static, structural, nominal, lattice |
Memory system | stack-based |
Computational class | Turing complete |
Reference implementation | nomad |
Influenced by | Standard ML, OCaml, Elm, TypeScript, Idris 2 |
File extension(s) | .nomad , .nmd , |
Nomad is a multi-paradigm language created from 2018 to 2021 with the goal of simplifying modern programming to as few concepts as possible, in order to facilitate complexity in other areas; Specifically, probabilistic programming.
Language overview
Nomad is designed to be a simple language. There are three primary concepts at the root of the language. These concepts are recursion, pattern matching, and algebraic data types.
History
Nomad was originally created in 2018 as an experiment in lattice based type systems and type-safe uncertainty through probability monads.
Etymology
Nomad was chosen to symbolise the portability of the language, principally the type system, which aims to provide type unification between many external type systems such as JSON, Protobuf, gRPC, and SQL. Nomad is also an anagram for Monad which is primary structure in functional programming.
Type System
Nomad has a structural type system that is based on a lattice. Types can be automatically generalized or specialized by moving up or down in the lattice.
Records are models as lattices as well. Accessing a field projects the element from the lattice. Records are extensible.
Records and types must be inflationary to be compatible. Records which share fields with the same name but different types can be generalized to accept either type through unions.
The most simple model for implementing a type system employs sets as types. All types can be considered to be sets of values. A lattice is a set with a preorder imposed on it. In Nomad, types are arranged into a lattice with a partial order. This order can be used to generalize or specialize a type. The language encodes a set of laws which govern this traversal.
Records
Records group related data together.
Point: type = {x: Int, y:Int} point: Point = {x: 0, y: 1}
Algebraic Data Types
Data and types can be joined together.
Point2D: type = {x: Int, y: Int} Point3D: type = Point2D & {z: Int} Points: type = Point2D | Point3D
In some cases, structural typing can be error prone. For example, if you have a type union specifying cardinal directions and another type union specifying suites of cards, these types will be compatible.
Direction: type = North | East | South | West Suite: type = Diamond | Spade | Club | Heart
These types will compile to the same type, which may be confusing to developers. To use types nominally, you can proceed the type with an octothorp.
Direction: type = North | East | South | West Suite: type = Diamond | Spade | Club | Heart directionHeading: (direction: #Direction) -> Nat directionHeading= direction ? | North => 0 | East => 90 | South => 180 | West => 270
Features
Pattern Matching
Pattern matching is a core concept in Nomad. Therefore, the syntax is super lightweight. subject ? pattern1 | pattern2
Pattern matching is equivalent to match CONDITION with
in ML style languages, and is similar to a switch
statement in C style languages.
The match expression can branch on abstract data types:
Bool: type = False | True toString: (bool: Bool) -> String = bool ? | False => "false" | True => "true"
You can also use the match expression to capture variables, and can match types:
toString: (a: type) -> String = a ? | num: Int => f"{num}:Int" | bool: Bool => f"{bool ? False => "false" | True => "true"}:Bool"
Recursion
Factorial implementation with recursion and pattern matching.
factorial: (n:Int) -> Int = n ? | (<= 1) => 1 | _ => n * factorial (n - 1)
Sum items in a list.
sum: (numbers :List[Int]) -> Int = numbers ? | [] => 0 | head :: tail => head + sum tail
Pipelines
numbers |> map *2 |> string
Examples
Hello World
main = writeLine "hello, world"
Cards
Card: type = { rank: Rank, suite: Suite } // Card: type = Rank & Suite Suite: type = Clubs | Hearts | Diamonds | Spades Rank: type = Two | Three | Four | Five | Six | Seven | Eight | Nine | Ten | Jack | Queen | King | Ace Name: String Hand: List[Card] Player: type = Name & Hand Deck: List[Card] Stack: List[Card] Game= { players: List[Player] } & Deck & Stack nextPlayer = cycle iterate Game.players
Tic Tac Toe
Mark: type = "O" | "X" Cell: type = Option Mark Board: [9]Cell = { None, None, None , None, None, None , None, None, None } viewBoard: (board: Board) -> String = board => zipWithIndex map \ c: Cell & Index => max c.Index c.Mark
The Math
Grammar
<privateLabel> ::= [a-z][a-zA-Z0-9]+ <exportedLabel> ::= [A-Z][a-zA-Z0-9]+ <Label> ::= <privateLabel> | <exportedLabel> <Type> ::= <Expr> <Definition> ::= <Label> [[:<Type>] | [=<Expr>] | [:<type>=<Expr>]] matchExpr ::= <Expr> ? [|]? [patternExpr]+ patternExpr ::= [|] <Expr> => <Expr>