Primitive Types
Before taking a stroll through Par’s types, let’s stop by the values that are not built out of the ordinary type connectives: the primitives.
Par currently has seven primitive types:
Nat– natural numbers, starting from zero, arbitrary size. They are a subtype ofInt.Int– positive and negative whole numbers, arbitrary size.Float– IEEE-754 double-precision floating-point numbers.String– UTF-8 encoded text. It is a subtype ofBytes.Char– a single Unicode character. It is a subtype ofString.Byte– a single 8-bit value. It is a subtype ofBytes.Bytes– a contiguous sequence of bytes.
Primitives are the one place where Par is not fully structural. User-defined types are aliases: their shape is their meaning. Primitive types are opaque, because they need efficient runtime representations and special operations.
Literals
Primitive literals are always available. You do not need to import anything to write them.
def Natural = 42 // Nat
def Integer = -7 // Int
def Floating = 3.14 // Float
def Text = "Hello" // String
def Character = "H" // Char
def OneByte = <<65>> // Byte
def ManyBytes = <<65 66 67>> // Bytes
Integer and natural number literals may use underscores for readability:
def Million = 1_000_000
Float literals have a fractional part, and may use scientific notation:
def Piish = 3.14
def Half = 0.5
def Avogadroish = 6.02e23
Strings use double quotes and normal escape sequences:
def Greeting = "Hello\nWorld"
A Char literal is just a string literal containing exactly one character:
def Letter = "a"
def Newline = "\n"
Byte and bytes literals use double angle brackets. A single byte literal is inferred as Byte;
multiple bytes, or the empty literal, are inferred as Bytes.
def A = <<65>>
def ABC = <<65 66 67>>
def Empty = <<>>
Byte values are stored modulo 256, so out-of-range byte literal values wrap around.
Operators
Numbers are usually manipulated with operators, not with imported helper functions.
def Arithmetic = 1 + 2 * 3 // = 7
def Grouped = {1 + 2} * 3 // = 9
def Ratio = 22.0 / 7.0
def Difference = 10 - 3
def Negative = neg 5
The operators +, *, and / work on Nat, Int, and Float.
The operators - and neg work on signed numbers: Int and Float.
Comparisons work on primitive values too:
def Smaller = 3 < 10 // = .true!
def SameText = "hi" == "hi" // = .true!
def Different = "a" != "b" // = .true!
They produce a Bool, which is described below. Comparisons also chain:
def InRange = 0 <= 5 < 10
Chained comparisons behave as you would expect: the expression above means 0 <= 5 and 5 < 10,
with the middle expression evaluated only once.
Reassigning versions of the arithmetic operators are additionally available in the process syntax, specifically +=, -=, *=, and /=:
def Five = do {
let n = 2
n += 3 // equivalent to `let n = n + 3`
} in n
Booleans
Bool is not a primitive type. It is an ordinary either type from
@core/Bool:
type Bool = either {
.false!,
.true!,
}
Boolean values are written as .true! and .false!.
def Yes = .true!
def No = .false!
Boolean expressions use and, or, and not:
def Both = .true! and .false!
def Either = .true! or .false!
def Neither = not .true!
These same words have extra power in conditions: they short-circuit, and can carry bindings from
matches. That is covered in Conditions & if.
Template Strings
Backtick strings are template strings. They are still String values, but they can contain
interpolation.
Use ${...} to splice in an expression that already has type String:
def Name = "Ada"
def Greeting = `Hello, ${Name}!`
Use #{...} to splice in any value that can be displayed as data:
def Count = 3
def Message = `You have #{Count} messages.`
The #{...} form uses @core/Data.ToString under the hood. It works for primitives and for
ordinary data structures such as pairs, eithers, and lists of data.
Template strings may span multiple lines, and support the usual string escapes. To write syntax that would otherwise start or end template behavior, escape it:
def LiteralPieces = `Use \` for backticks, \${ for string interpolation, and \#{ for data.`
Naming Primitive Types
Literals do not need imports, but explicit type names do.
module Main
import {
@core/Int
@core/String
}
def Age: Int = 42
def Name: String = "Ada"
The same imports give access to helper functions from those modules.
module Main
import {
@core/Int
@core/Nat
}
def Magnitude = Int.Abs(-1000)
def Remainder = Int.Mod(-13, 5)
def Numbers = Nat.Range(0, 5) // *(0, 1, 2, 3, 4)
Every package automatically depends on @core, but its modules are not imported automatically.
Useful Primitive Modules
The primitive modules contain operations that are not just generic arithmetic or comparison.
The examples below are only a taste; use par doc to browse the full built-in API.
@core/Nat
Nat has helpers for finite repetition and natural ranges:
module Main
import @core/Nat
def ThreeSteps = Nat.Repeat(3)
def ZeroToFour = Nat.Range(0, 5)
Nat.Repeat(n) produces a recursive value with exactly n steps. It shows up often when you need
to loop a known number of times.
@core/Int
Int has helpers where the result type is not just “another number”.
module Main
import @core/Int
def Absolute: Nat = Int.Abs(-12)
def Modulo: Nat = Int.Mod(-13, 5)
def FromTo = Int.Range(-2, 3)
@core/Float
Float has constants, conversions, predicates, and math functions:
module Main
import @core/Float
def Tau = 2.0 * Float.Pi
def Root = Float.Sqrt(9.0)
def Rounded = Float.Round(3.6)
def CloseEnough = Float.Equals(1.0, 1.05, 0.1)
Float.Equals remains useful because it compares with a tolerance. The == operator compares data
directly.
@core/String
Strings can be built incrementally:
module Main
import @core/String
def Hello = String.Builder
.add("Hello")
.add(", ")
.add("world!")
.build
They can also be parsed through String.Parser. The parser is a larger tool, useful when you want
to read characters or match patterns:
module Main
import @core/String
def ParserFromText = String.Parser("abc")
@core/Char and @core/Byte
Char.Is and Byte.Is check membership in character or byte classes:
module Main
import {
@core/Byte
@core/Char
}
def Space = Char.Is(" ", .whitespace!)
def HighByte = Byte.Is(<<192>>, .range(<<128>>, <<255>>)!)
@core/Bytes
Bytes has readers, parsers, and builders for byte-oriented protocols:
module Main
import @core/Bytes
def EmptyReader = Bytes.Reader(<<>>)
def Size = Bytes.Length(<<65 66 67>>)
That is enough about primitives for now. The rest of the docs will introduce the structural types that most Par programs are built from.