Chapter 1
Program structure
A program is one or more .rill files. Compilation starts at the file passed to rill build; import pulls in others. Declarations may appear in any order β a function can call one defined later in the file.
import "geometry" # loads geometry.rill next to this file type Shape # a data type Circle(r: Float) trait Describe(a) # an interface describe(a) -> Str impl Describe(Shape) # an implementation fn describe(s) = "a shape" fn main() = # the entry point println(describe(Circle(1.0)))
Every program needs a fn main(). It takes no parameters and returns nothing; the program exits when it returns. A file that is a library rather than a program says so on its first line β module date β and has no main: it is checked, typed, formatted and rill tested as it is, and rill run refuses it, saying to import it from a program. The name has to be the file's own (date.rill), so a file copied under another name is caught at once. A file with neither a module line nor a main is asked which it is.
An import brings the other file's definitions in under their own names. Adding an alias keeps them apart instead, which is how two modules can define the same name:
import "metric" as m import "imperial" as i fn main() = println(m.label(m.Meters(1.5)) + " " + i.label(i.Feet(2.0)))
m.label and i.label are different functions, and m.Unit and i.Unit are different types. A pattern names an aliased constructor the same way: match u m.Meters(v) -> v.
A function the file keeps to itself is written private fn. An importer does not see it, cannot call it β the error names the file β and may have a function of the same name, since the two never meet: the loader renames a private function to a spelling no program can write and rewrites the file's own references to match. Everything else a file defines is the importer's to use, which is the default a library written before private still has.
A file that is not beside the importer is looked for on the search path: RILL_PATH, a colon-separated list, and then every lib/ from the importing file's directory upwards, stopping at the one holding .git. So a library in the project's lib/ is import "data/json" from anywhere in the project, and a program's own files still win β a file beside the importer is never taken over by one on the path.
Layout
Blocks are written by indentation β two spaces per level, spaces only, no tabs.
An expression continues on the next line when that line begins with something no statement can begin with: a binary operator (+ * / % == != < <= > >= && || |>), a | between patterns, a ., or then or else. Not - or !, which begin a negation as readily as they continue a line. The continuation is indented past the line it continues:
total = xs |> map(\x -> x * 10) |> filter(\x -> x > 10) |> sum
Inside parentheses indentation carries no meaning, so a call may spread its arguments over lines freely. rill fmt keeps what was written: an operator, then or else that began a line stays at the head of one, and arguments written one to a line stay one to a line, each a level in.
Comments run from # to the end of the line. The # needs a space (or the line's end) after it, because written against something β #s β it is the length operator instead.