Types
Ranger has static types. The compiler finds the type of a variable from the value, or the program declares the type after a colon.
def count 10 ; the compiler finds the type intdef ratio:double 0.5 ; the program declares the typePrimitive types
Section titled “Primitive types”| Type | Content | Example of a value |
|---|---|---|
int |
A whole number. Width is 64 bits or 32 bits, by target. | 42 |
double |
A number with a fraction. | 3.14 |
string |
Text. | "Ranger" |
char |
One character. | 'a' |
boolean |
true or false. |
true |
The compiler also has fixed width integer types: int8, int16, int32,
int64 and the unsigned types. Use them when the program writes a binary
format or when it must agree with a C structure. C++, C#, Java and Kotlin
write a 32 bit integer for int. JavaScript, Python, PHP, Go and Rust write
a 64 bit integer. See Target languages.
Collections
Section titled “Collections”| Type | Content | Declaration |
|---|---|---|
| Array | Values of one type, in order. | def items:[int] |
| Hash map | Values with a key. | def ages:[string:int] |
An array and a hash map are ready for use after the declaration. The compiler initializes them.
def items:[int]push items 10print ("count " + (size items))
def ages:[string:int]set ages "ada" 36 ; write a keyif (has ages "ada") { } ; test a keydef age (get ages "ada") ; read a keyThe get operator gives an optional value, because the key can be absent.
Optional values describes how the program
reads it.
The array operators and the map operators hold the complete list.
An Enum declares a set of names. The compiler gives each name an integer
value, and it checks the type of each use.
Enum LineJoin ( Undefined Miter Round Bevel)
class Pen { def lineType:LineJoin LineJoin.Undefined}The target language receives the type int. The type check is in the compiler.
An Enum gives a name no fields. Use a shape
when each name must carry its own data.
Shapes
Section titled “Shapes”A shape is a closed family of cases. Groups name restricted views of that
family; match checks that every reachable case is handled.
shape Value { group Printable { fn render:string () } case Num does Printable { def n:double 0.0 fn render:string () { return (to_string n) } } case Text does Printable { def s:string "" fn render:string () { return s } }}The page Shapes covers groups, match,
required operations, value/reference semantics and per-target representation.
Buffers
Section titled “Buffers”A buffer holds binary data. The types are buffer, int_buffer,
double_buffer and charbuffer. A buffer compiles to the binary type of the
target: Uint8Array in JavaScript, []byte in Go, Vec<u8> in Rust.
Optional types
Section titled “Optional types”An optional value is a value that can be empty. The annotation @(optional)
marks it:
def name@(optional):stringOptional values describes the operators that read an optional value.
Generic types
Section titled “Generic types”The letter T in an operator signature is a type parameter. The operator
accepts a value of any type, and the compiler holds the type through the call.
The generic operators use this.
A class takes type parameters with the @params(...) annotation. A reference
gives the arguments with @(...).
class History @params(Op) { def ops:[Op]
fn record:void (op:Op) { push ops op } fn newest:Op () { def v:Op (last ops) return v }}
def ints:History@(int) (new History@(int) ())def texts:History@(string) (new History@(string) ())A type parameter is usable as an array element, as a map value, as a parameter type and as a return type. A generic class can hold another generic class at its own parameter, can have a constructor with arguments, and can extend a plain class.
An instantiation is an ordinary type. It can be the element type of a collection, and a generic class can name itself.
class Tree @params(T) { def held:[T] def kids:[Tree@(T)]
fn adopt:void (k:Tree@(T)) { push kids k }}
def byName:[string:Tree@(int)]| The argument can be | Example |
|---|---|
| A primitive | History@(int) |
| A class or a record | History@(SlideOp) |
| A shape | History@(EditOp) |
| An array | History@([string]) |
| A map | Store@([string:int]) |
There are no bounds and no constraints. Nothing is asked of the argument type. When a generic class must compare two values, give it the comparison function at construction.
The compiler makes one concrete class for each set of arguments before it
writes the target code. History@(int) becomes the class History_int. Two
instantiations are two separate classes. A target language does not need
generics of its own, and no target writes the type parameter.
A generic class has no static side. Only the instantiations exist, so a sfn
in a generic class is not reachable. Put the static functions in a plain class.
A trait takes @params(...) in the same way. See
Structure.
Ranger 3.5.1 · commit f323fd4 · development build