From bac1ccaeaa574d1baa66b3b479575a6e153d3cdb Mon Sep 17 00:00:00 2001 From: Luke Boswell Date: Thu, 23 Nov 2023 17:11:47 +1100 Subject: [PATCH] initial Abilities language reference --- www/content/abilities.md | 193 +++++++++++++++++++++++++++++++++++++++ www/content/docs.md | 9 +- 2 files changed, 200 insertions(+), 2 deletions(-) create mode 100644 www/content/abilities.md diff --git a/www/content/abilities.md b/www/content/abilities.md new file mode 100644 index 00000000000..46998f77d17 --- /dev/null +++ b/www/content/abilities.md @@ -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" +``` diff --git a/www/content/docs.md b/www/content/docs.md index f026e2234e1..4ff047e8071 100644 --- a/www/content/docs.md +++ b/www/content/docs.md @@ -1,3 +1,4 @@ + # Documentation - [builtins](/builtins) - docs for modules built into the language—`Str`, `Num`, etc. @@ -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)