-
-
Notifications
You must be signed in to change notification settings - Fork 292
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
initial Abilities language reference
- Loading branch information
1 parent
e41df02
commit bac1cca
Showing
2 changed files
with
200 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,193 @@ | ||
|
||
# Abilities | ||
|
||
An Ability defines a set of functions that can be implemented by different types. | ||
|
||
Abilities are used to constrain the types of function arguments to only those which implement the required functions. | ||
|
||
The function `toJson` below is an example which uses the `Encoding` Ability. | ||
|
||
```roc | ||
toJson : a -> List U8 where a implements Encoding | ||
toJson = \val -> | ||
val |> Encode.toBytes JSON.encoder | ||
``` | ||
|
||
By specifying the type variable `a` implements the `Encoding` Ability, this function can make use of `Encode.toBytes` and `JSON.encoder` to serialise `val`, without knowing its specific type. | ||
|
||
All types which implement the `Encoding` Ability can therefore use the `Encode.toBytes` (and also `Encode.append`) functions, without any additional code or custom implementation. | ||
|
||
This is really useful as Roc's builtin types such as numbers, records, and tags, are automatically implemented for the builtin Abilities, and can be serialised to bytes by providng a Encoder. | ||
|
||
## Builtins | ||
|
||
### `Eq` Ability | ||
|
||
The `Eq` Ability defines the `isEq` function, which can be used to compare two values for structural equality. The infix operator `==` can be used as shorthand for `isEq`. | ||
|
||
**Example** showing the use of `isEq` and `==` to compare two values. | ||
|
||
```roc | ||
Colors : [Red, Green, Blue] | ||
red = Red | ||
blue = Blue | ||
expect isEq red Red # true | ||
expect red == blue # false | ||
``` | ||
|
||
**Definition** of the `Eq` Ability. | ||
|
||
```roc | ||
# Bool.roc | ||
Eq implements | ||
isEq : a, a -> Bool where a implements Eq | ||
``` | ||
|
||
**Structural equality** is defined as follows: | ||
1. Tags are equal if their name and also contents are equal. | ||
2. Records are equal if their fields are equal. | ||
3. The collections [Str], [List], [Dict], and [Set] are equal iff they are the same length and their elements are equal. | ||
4. [Num] values are equal if their numbers are equal. However, if both inputs are *NaN* then `isEq` returns `Bool.false`. Refer to `Num.isNaN` for more detail. | ||
5. Functions cannot be compared for structural equality, therefore Roc cannot derive `isEq` for types that contain functions. | ||
|
||
### `Hash` Ability | ||
|
||
```roc | ||
# Hash.roc | ||
Hash implements | ||
hash : hasher, a -> hasher where a implements Hash, hasher implements Hasher | ||
``` | ||
|
||
### `Sort` Ability | ||
|
||
**Implementation Status** - Design Proposal, implementation has not yet started. See [zulip discussion thread](https://roc.zulipchat.com/#narrow/stream/304641-ideas/topic/ordering.2Fsorting.20ability/near/395539545) for more information. | ||
|
||
### `Encoding` Ability | ||
|
||
```roc | ||
# Encode.roc | ||
Encoding implements | ||
toEncoder : val -> Encoder fmt where val implements Encoding, fmt implements EncoderFormatting | ||
``` | ||
|
||
### `Decoding` Ability | ||
|
||
```roc | ||
# Decode.roc | ||
Decoding implements | ||
decoder : Decoder val fmt where val implements Decoding, fmt implements DecoderFormatting | ||
``` | ||
|
||
### `Inspect` Ability | ||
|
||
```roc | ||
# Inspect.roc | ||
Inspect implements | ||
toInspector : val -> Inspector f where val implements Inspect, f implements InspectFormatter | ||
``` | ||
|
||
## Opaque Types | ||
|
||
### Derived Implementions | ||
|
||
Abilities can be automatically derived for Opaque Types where the type is an alias for a builtin, or it is composed of other types which also implement that ability. | ||
|
||
For example you can automatically derive the `Eq` and `Hash` abilities using `implements [ Eq, Hash ]`. | ||
|
||
**Example** showing how to automatically derive the `Eq` and `Hash` abilities for an Opaque Type. | ||
|
||
```roc | ||
StatsDB := Dict Str { score : Dec, average : Dec } implements [ Eq, Hash ] | ||
add : StatsDB, Str, { score : Dec, average : Dec } -> StatsDB | ||
add = \@StatsDB db, name, stats -> db |> Dict.insert name stats |> @StatsDB | ||
expect | ||
db1 = Dict.empty {} |> @StatsDB |> add "John" { score: 10, average: 10 } | ||
db2 = Dict.empty {} |> @StatsDB |> add "John" { score: 10, average: 10 } | ||
db1 == db2 | ||
``` | ||
|
||
### Custom Implementations | ||
|
||
You can provide a custom implementation for an ability. This may be useful if a type is composed of other types which do not implement an ability, or if you would like to override the default behaviour. | ||
|
||
**Example** showing how to provide a custom implementation for the `Eq` ability. | ||
|
||
```roc | ||
Color := [ | ||
RGBAu8 U8 U8 U8 U8, | ||
RGBAf32 F32 F32 F32 F32, | ||
] | ||
implements [ | ||
Eq { isEq: colorEquality }, | ||
] | ||
colorEquality : Color, Color -> Bool | ||
colorEquality = \a, b -> colorToU8 a == colorToU8 b | ||
colorToU8 : Color -> (U8, U8, U8, U8) | ||
colorToU8 = \@Color c-> | ||
when c is | ||
RGBAu8 r g b a -> (r, g, b, a) | ||
RGBAf32 r g b a -> (f32toU8 r, f32toU8 g, f32toU8 b, f32toU8 a) | ||
f32toU8 : F32 -> U8 | ||
f32toU8 = \f -> | ||
Num.floor (f * 255.0) | ||
fromU8 : U8, U8, U8, U8 -> Color | ||
fromU8 = \r, g, b, a -> @Color (RGBAu8 r g b a) | ||
fromI16 : I16, I16, I16, I16 -> Result Color [OutOfRange] | ||
fromI16 = \r, g, b, a -> | ||
if r < 0 || r > 255 || g < 0 || g > 255 || b < 0 || b > 255 || a < 0 || a > 255 then | ||
Err OutOfRange | ||
else | ||
Ok (@Color (RGBAu8 (Num.toU8 r) (Num.toU8 g) (Num.toU8 b) (Num.toU8 a))) | ||
fromF32 : F32, F32, F32, F32 -> Result Color [OutOfRange] | ||
fromF32 = \r, g, b, a -> | ||
if r < 0.0 || r > 1.0 || g < 0.0 || g > 1.0 || b < 0.0 || b > 1.0 || a < 0.0 || a > 1.0 then | ||
Err OutOfRange | ||
else | ||
Ok (@Color (RGBAf32 r g b a)) | ||
``` | ||
|
||
## Advanced Topic: Define a new Ability | ||
|
||
It is possible to define a new Ability in addition to those provided in builtins. This should be avoided if possible and only used in rare circumstances by package authors. | ||
|
||
**Example** showing how to define a new Ability. | ||
|
||
```roc | ||
CustomInspect implements | ||
inspectMe : val -> Str where val implements CustomInspect | ||
inspect : val -> Str where val implements CustomInspect | ||
inspect = \val -> inspectMe val | ||
Color := [Red, Green, Blue] | ||
implements [ | ||
Eq, | ||
CustomInspect { | ||
inspectMe: inspectColor, | ||
}, | ||
] | ||
inspectColor : Color -> Str | ||
inspectColor = \@Color color -> | ||
when color is | ||
Red -> "Red" | ||
Green -> "Green" | ||
Blue -> "Blue" | ||
expect | ||
[@Color Red, @Color Green, @Color Blue] | ||
|> List.map inspect | ||
|> Str.joinWith "," | ||
|> Bool.isEq "Red,Green,Blue" | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters