Skip to content

Commit

Permalink
initial Abilities language reference
Browse files Browse the repository at this point in the history
  • Loading branch information
lukewilliamboswell committed Nov 23, 2023
1 parent e41df02 commit bac1cca
Show file tree
Hide file tree
Showing 2 changed files with 200 additions and 2 deletions.
193 changes: 193 additions & 0 deletions www/content/abilities.md
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"
```
9 changes: 7 additions & 2 deletions www/content/docs.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

# Documentation

- [builtins](/builtins) - docs for modules built into the language—`Str`, `Num`, etc.
Expand All @@ -8,7 +9,11 @@ In the future, a language reference will be on this page too.

## [Guides](#guides) {#guides}

- [FAQ (Frequently Asked Questions)](https://github.com/roc-lang/roc/blob/main/FAQ.md)
- [Platforms & Applications](/platforms)
- [Frequently Asked Questions](https://github.com/roc-lang/roc/blob/main/FAQ.md)
- [Roc for Elm Programmers](https://github.com/roc-lang/roc/blob/main/roc-for-elm-programmers.md)
- [Tutorial](/tutorial)

## [Language Reference](#language-reference) {#language-reference}

- [Platforms & Applications](/platforms)
- [Abilities](/abilities)

0 comments on commit bac1cca

Please sign in to comment.