This page explains why Refingere looks the way it does: the problems each unusual choice is meant to solve, and what it costs. It's for readers who want the reasoning behind the rules the other pages describe, and for anyone coming from another language.
Refingere tries to have a small number of mechanisms that each do a lot, instead of a special feature for each need. Errors are values, and the same operators handle them everywhere. The outside world comes through the context, which is also how tests, configuration and dependency injection work. Attributes mark what's special about a function or struct, which covers entry points, tests, HTTP routes and UI components. Learning one of these pays off in many places.
Refingere is designed for test-driven development (TDD) and domain-driven design (DDD), and many of the choices on this page follow from those two. For TDD, writing a test first should cost as little as writing the code: a test is an ordinary function marked @test, usually right after the code it tests in the same file, with no assertion or mocking library to set up. It fails by ending in an error, and it replaces the clock, the file system or a request through the context, so the code under test is the real code. See Testing.
For DDD, the code should be written in the language of the problem it models. Structs and enums with data on their cases describe the domain directly, and match makes every case handled; errors that belong to the domain are part of a function's type; folders give each part of the domain its own boundary, with global types for what they share; and notation and macros let a formula or a query read the way the domain writes it. See Types.
Refingere is made for web development before anything else. A web project usually means two languages, or one language split across two toolchains, with the types of the data between server and browser kept in step by hand. In Refingere the server, the browser app and the code they share are written in one language and checked by one compiler: a route is a function marked @get, a component a struct marked @component, and the types both sides use can be global definitions in one folder that each reaches by path. Routes and components are tested like any other code, without a browser or a network. See Servers and Browser Apps.
That's also why it compiles to WebAssembly: every browser already runs it, so an app needs no separate toolchain, and WASI runtimes such as wasmtime run the same modules on a server or the command line - so a command-line tool is the same kind of output as an app. See Compiling to WebAssembly. Where a JavaScript module fits better - a library for a JavaScript project, a script for Node - the same program compiles to JavaScript instead, and behaves the same there - see Compiling to JavaScript.
With exceptions, any call might fail in ways its signature doesn't show, and the code that handles a failure can be far from where it happened. In Refingere, a function that can fail says so in its type, Error ! T, and its caller decides what to do right there. !! keeps the common case - pass it up - down to two characters, so being explicit doesn't mean being verbose. The cost is that every fallible call is marked, which is also the point. See Errors.
Code that reads the clock or writes to a file through global functions can't be tested without changing it, or without a mocking library that patches it from outside. Code that takes everything as parameters can be tested, but every function in between has to pass along values it doesn't use. The context sits between the two: values are read by name anywhere, and provided by the callers further up, for everything they call. A test replaces the clock with one inject, and the code under test is the real code. Since the context is the only way to reach the outside world, reading a function also shows what it touches. See Context.
There are no classes and no subclasses. Behaviour is shared through interfaces, implemented separately from the type - so a type can be given an interface later, by code that doesn't own it. Data is shared by combining structs with && and enums with ||, which copies fields and cases - and the implementations that go with them - without creating an "is a" relationship. Each relationship between types is spelled out, instead of inherited from a chain of parents. See Interfaces.
Every file sees every other file in its folder, and other folders are reached by path. A folder is the unit of code, so moving a function between files in it changes nothing, and there's no list of imports at the top of each file to keep up to date. local keeps a helper private to its file when that matters. See Files and Folders.
let introduces a name and set changes one, so every change to a variable starts with a word that says so, and a typo can't silently create a new variable. Conversions between numeric types are always written with as. Integer overflow is an error rather than a silent wrap or undefined behaviour, and code that means to wrap or saturate says so with an operator of its own, +% or +|. See Numbers.
Indentation is exact - two spaces per block - and a line's first word says what kind of line it is. Much of a file's layout is fixed by the grammar, so Refingere code looks alike from one project to the next, and there's less for a formatter or a reviewer to decide. Line comments take three slashes because // is an operator, floor division. See Syntax.
√x, x², xsᵢ, a ≤ b and chained comparisons let code be checked against the formula it came from. Other languages - SQL, HTML, regular expressions - are written as themselves in macros and checked while compiling, instead of being built from strings. In each case the aim is the same: code that looks like what it means. See Numbers and Macros.
Tables, graphs, vectors, matrices, dates, regular expressions, HTTP serving and browser UI are all built in. Most programs need several of these, and a language where each is a separate library choice starts every project with research and ends with incompatible types. A builtin Table or Instant is the same type in every program and every example. See the Types reference.
| If you know | In Refingere |
|---|---|
try / catch, throw | Error ! T return types, error, !! and match - closest to Rust's Result and ?, or Go's error returns |
null, Option | T? and undefined, with ?. and ?? as in TypeScript |
| classes and inheritance | structs with &&, and interfaces with implement - like Rust's traits or Go's interfaces, but implemented explicitly |
| sum types, tagged unions | enum with data on its cases, taken apart with match |
import, packages, modules | folders - see Files and Folders |
| dependency injection containers, mocking libraries | the context, inject and @injectLocally |
const x := ... / x := ... | let x := ... to introduce, set x := ... to change |
while (cond) | for _ in inf while cond, or a loop modifier on the collection itself |
switch | match, with patterns and guards |
| decorators, annotations | attributes - builtin ones, plain metadata, or attribute macros |
template strings, f"..." | #format"... #{ value } ..." |
// comment | /// comment - // is floor division |