Refingere has no classes and no inheritance. A type's data is its fields; what it can do is described by interfaces - lists of functions without bodies - which types promise to have with implement blocks. Code written against an interface works with every type that implements it, and generic code takes the type itself as a parameter. Interfaces are also how operators, loops and printing work on your own types.
Each member is a function declaration with no body. A member whose first parameter is self is called on a value. One without self is called on the type, like a constructor. Self stands for whichever type is implementing the interface. See Interface Definition.
implement Shape for Circle gives the bodies for one type, with self typed as that type. Every member has to be there. A type can implement any number of interfaces, each in its own block. See Implement an Interface.
An interface can be used in two ways. As a type, Array<Shape> holds values of different types together, and each call runs the member of the value's own type. As a constraint on a type parameter, S: Shape, every value is of one type, S, known where the function is called. The constraint also makes the type's own functions available, as S.unit(). Use the type form for mixed collections, and the constraint form when the caller's type should come back out, as in largest.
Structs, enums, unions, interfaces, type names and functions can all take type parameters in angle brackets. Labeled<T> attaches a label to a value of any type, and because describe returns a Labeled<S>, the value keeps its own type - labeled.value.radius works, since it's still a Circle. Type arguments are inferred from the values, and can be written out - largest<Circle>(...) - when they can't be.
An implement block is written apart from the type, in any file - so a type can be given an interface after the fact, including builtin types and types from another folder. The code that needs Describable doesn't have to own Duration to make it fit.
A method written for an interface, instead of for a type, is given to every type that implements the interface. It can only use what the interface declares, and each type gets it without writing anything. The builtin interfaces use this a lot: Iterable declares one member, and every iterable type gets map, filter, sum and the rest from it. See Implementations.
Operators and language features are backed by builtin interfaces, so implementing one makes them work on your type:
| Interface | Gives your type |
|---|---|
Equatable | ==, !=, in and contains |
Comparable | <, <=, >, >=, sorting, min and max |
Hashable | use as a Map key or a Set element |
ToString | toString(), and printing in #format |
FromString | String.parse<T>(), and HTTP route parameters |
Numeric | +, -, *, /, //, %, ^, and sum |
Iterable | for loops, .. spreading, and every collection method |
Indexable | value[key], subscripts and set value[key] := ... |
Concatenable | ++ |
Callable | calling a value like a function |
Castable | value as T |
The Interfaces reference lists all of them, with their members.
For interfaces with an obvious implementation - compare field by field, hash every field, print every field - @derive writes the implement block for you. It works on structs and enums, as long as every field supports the interface too. Write the block by hand when the obvious one isn't what you want, as with Money above. You can write derive macros of your own.
A struct combined from others with && gets their implementations as well as their fields: every implement block and method of a part is written again for it, with self and Self now the combined struct, and whatever was derived is derived again over all its fields. It still isn't one of its parts, though - a Sign can't be passed where a Named is wanted - so a copied body that would need that is an error on the combined struct.
A struct's own implement block, @derive or method replaces the one it would get. Two of the structs it's combined from implementing the same interface, or having a method of the same name, is an error, unless the combined struct writes its own.
An enum combined with || gets an interface or method when every part has it, and each call goes to the part the value's case came from. Members that have no single case to go by - one called on the type, like unit, or one taking a second Self, like compare - can't be passed on this way, so unless every part derives them the enum implements them itself. See Enum Definition for the exact rules.
An interface can take type parameters, and each implement block fills them in. The builtin Iterable<T> and Indexable<Key, Value> work this way, so a type says what it iterates over and what it's indexed by.