Merge branch 'main' into ayaz/error-on-invalid-generalized-types

Signed-off-by: Ayaz <20735482+ayazhafiz@users.noreply.github.com>
This commit is contained in:
Ayaz 2025-01-10 14:36:48 -05:00 committed by GitHub
commit ee3c71dfe6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
766 changed files with 20515 additions and 34868 deletions

View file

@ -16,14 +16,14 @@ You can run your new tests locally using `cargo test-gen-llvm`. You can add a fi
### module/src/symbol.rs
Towards the bottom of `symbol.rs` there is a `define_builtins!` macro being used that takes many modules and function names. The first level (`List`, `Int` ..) is the module name, and the second level is the function or value name (`reverse`, `rem` ..). If you wanted to add a `Int` function called `addTwo` go to `2 Int: "Int" => {` and inside that case add to the bottom `38 INT_ADD_TWO: "addTwo"` (assuming there are 37 existing ones).
Towards the bottom of `symbol.rs` there is a `define_builtins!` macro being used that takes many modules and function names. The first level (`List`, `Int` ..) is the module name, and the second level is the function or value name (`reverse`, `rem` ..). If you wanted to add a `Int` function called `add_two` go to `2 Int: "Int" => {` and inside that case add to the bottom `38 INT_ADD_TWO: "add_two"` (assuming there are 37 existing ones).
Some of these have `#` inside their name (`first#list`, `#lt` ..). This is a trick we are doing to hide implementation details from Roc programmers. To a Roc programmer, a name with `#` in it is invalid, because `#` means everything after it is parsed to a comment. We are constructing these functions manually, so we are circumventing the parsing step and don't have such restrictions. We get to make functions and values with `#` which as a consequence are not accessible to Roc programmers. Roc programmers simply cannot reference them.
But we can use these values and some of these are necessary for implementing builtins. For example, `List.get` returns tags, and it is not easy for us to create tags when composing LLVM. What is easier however, is:
- ..writing `List.#getUnsafe` that has the dangerous signature of `List elem, U64 -> elem` in LLVM
- ..writing `List elem, U64 -> Result elem [OutOfBounds]*` in a type safe way that uses `getUnsafe` internally, only after it checks if the `elem` at `U64` index exists.
- ..writing `List.#get_unsafe` that has the dangerous signature of `List elem, U64 -> elem` in LLVM
- ..writing `List elem, U64 -> Result elem [OutOfBounds]*` in a type safe way that uses `get_unsafe` internally, only after it checks if the `elem` at `U64` index exists.
### can/src/builtins.rs
@ -123,5 +123,5 @@ But replace `Num.atan`, the return value, and the return type with your new buil
When implementing a new builtin, it is often easy to copy and paste the implementation for an existing builtin. This can take you quite far since many builtins are very similar, but it also risks forgetting to change one small part of what you copy and pasted and losing a lot of time later on when you cant figure out why things don't work. So, speaking from experience, even if you are copying an existing builtin, try and implement it manually without copying and pasting. Two recent instances of this (as of September 7th, 2020):
- `List.keepIf` did not work for a long time because in builtins its `LowLevel` was `ListMap`. This was because I copy and pasted the `List.map` implementation in `builtins.rs
- `List.walkBackwards` had mysterious memory bugs for a little while because in `unique.rs` its return type was `list_type(flex(b))` instead of `flex(b)` since it was copy and pasted from `List.keepIf`.
- `List.keep_if` did not work for a long time because in builtins its `LowLevel` was `ListMap`. This was because I copy and pasted the `List.map` implementation in `builtins.rs
- `List.walk_backwards` had mysterious memory bugs for a little while because in `unique.rs` its return type was `list_type(flex(b))` instead of `flex(b)` since it was copy and pasted from `List.keep_if`.

View file

@ -144,7 +144,7 @@ pub const RocDec = extern struct {
return (c -% 48) <= 9;
}
pub fn toStr(self: RocDec) RocStr {
pub fn to_str(self: RocDec) RocStr {
// Special case
if (self.num == 0) {
return RocStr.init("0.0", 3);
@ -1031,97 +1031,97 @@ test "fromStr: .123.1" {
try expectEqual(dec, null);
}
test "toStr: 100.00" {
test "to_str: 100.00" {
var dec: RocDec = .{ .num = 100000000000000000000 };
var res_roc_str = dec.toStr();
var res_roc_str = dec.to_str();
const res_slice: []const u8 = "100.0"[0..];
try expectEqualSlices(u8, res_slice, res_roc_str.asSlice());
}
test "toStr: 123.45" {
test "to_str: 123.45" {
var dec: RocDec = .{ .num = 123450000000000000000 };
var res_roc_str = dec.toStr();
var res_roc_str = dec.to_str();
const res_slice: []const u8 = "123.45"[0..];
try expectEqualSlices(u8, res_slice, res_roc_str.asSlice());
}
test "toStr: -123.45" {
test "to_str: -123.45" {
var dec: RocDec = .{ .num = -123450000000000000000 };
var res_roc_str = dec.toStr();
var res_roc_str = dec.to_str();
const res_slice: []const u8 = "-123.45"[0..];
try expectEqualSlices(u8, res_slice, res_roc_str.asSlice());
}
test "toStr: 123.0" {
test "to_str: 123.0" {
var dec: RocDec = .{ .num = 123000000000000000000 };
var res_roc_str = dec.toStr();
var res_roc_str = dec.to_str();
const res_slice: []const u8 = "123.0"[0..];
try expectEqualSlices(u8, res_slice, res_roc_str.asSlice());
}
test "toStr: -123.0" {
test "to_str: -123.0" {
var dec: RocDec = .{ .num = -123000000000000000000 };
var res_roc_str = dec.toStr();
var res_roc_str = dec.to_str();
const res_slice: []const u8 = "-123.0"[0..];
try expectEqualSlices(u8, res_slice, res_roc_str.asSlice());
}
test "toStr: 0.45" {
test "to_str: 0.45" {
var dec: RocDec = .{ .num = 450000000000000000 };
var res_roc_str = dec.toStr();
var res_roc_str = dec.to_str();
const res_slice: []const u8 = "0.45"[0..];
try expectEqualSlices(u8, res_slice, res_roc_str.asSlice());
}
test "toStr: -0.45" {
test "to_str: -0.45" {
var dec: RocDec = .{ .num = -450000000000000000 };
var res_roc_str = dec.toStr();
var res_roc_str = dec.to_str();
const res_slice: []const u8 = "-0.45"[0..];
try expectEqualSlices(u8, res_slice, res_roc_str.asSlice());
}
test "toStr: 0.00045" {
test "to_str: 0.00045" {
var dec: RocDec = .{ .num = 450000000000000 };
var res_roc_str = dec.toStr();
var res_roc_str = dec.to_str();
const res_slice: []const u8 = "0.00045"[0..];
try expectEqualSlices(u8, res_slice, res_roc_str.asSlice());
}
test "toStr: -0.00045" {
test "to_str: -0.00045" {
var dec: RocDec = .{ .num = -450000000000000 };
var res_roc_str = dec.toStr();
var res_roc_str = dec.to_str();
const res_slice: []const u8 = "-0.00045"[0..];
try expectEqualSlices(u8, res_slice, res_roc_str.asSlice());
}
test "toStr: -111.123456" {
test "to_str: -111.123456" {
var dec: RocDec = .{ .num = -111123456000000000000 };
var res_roc_str = dec.toStr();
var res_roc_str = dec.to_str();
const res_slice: []const u8 = "-111.123456"[0..];
try expectEqualSlices(u8, res_slice, res_roc_str.asSlice());
}
test "toStr: 123.1111111" {
test "to_str: 123.1111111" {
var dec: RocDec = .{ .num = 123111111100000000000 };
var res_roc_str = dec.toStr();
var res_roc_str = dec.to_str();
const res_slice: []const u8 = "123.1111111"[0..];
try expectEqualSlices(u8, res_slice, res_roc_str.asSlice());
}
test "toStr: 123.1111111111111 (big str)" {
test "to_str: 123.1111111111111 (big str)" {
var dec: RocDec = .{ .num = 123111111111111000000 };
var res_roc_str = dec.toStr();
var res_roc_str = dec.to_str();
errdefer res_roc_str.decref();
defer res_roc_str.decref();
@ -1129,9 +1129,9 @@ test "toStr: 123.1111111111111 (big str)" {
try expectEqualSlices(u8, res_slice, res_roc_str.asSlice());
}
test "toStr: 123.111111111111444444 (max number of decimal places)" {
test "to_str: 123.111111111111444444 (max number of decimal places)" {
var dec: RocDec = .{ .num = 123111111111111444444 };
var res_roc_str = dec.toStr();
var res_roc_str = dec.to_str();
errdefer res_roc_str.decref();
defer res_roc_str.decref();
@ -1139,9 +1139,9 @@ test "toStr: 123.111111111111444444 (max number of decimal places)" {
try expectEqualSlices(u8, res_slice, res_roc_str.asSlice());
}
test "toStr: 12345678912345678912.111111111111111111 (max number of digits)" {
test "to_str: 12345678912345678912.111111111111111111 (max number of digits)" {
var dec: RocDec = .{ .num = 12345678912345678912111111111111111111 };
var res_roc_str = dec.toStr();
var res_roc_str = dec.to_str();
errdefer res_roc_str.decref();
defer res_roc_str.decref();
@ -1149,9 +1149,9 @@ test "toStr: 12345678912345678912.111111111111111111 (max number of digits)" {
try expectEqualSlices(u8, res_slice, res_roc_str.asSlice());
}
test "toStr: std.math.maxInt" {
test "to_str: std.math.maxInt" {
var dec: RocDec = .{ .num = std.math.maxInt(i128) };
var res_roc_str = dec.toStr();
var res_roc_str = dec.to_str();
errdefer res_roc_str.decref();
defer res_roc_str.decref();
@ -1159,9 +1159,9 @@ test "toStr: std.math.maxInt" {
try expectEqualSlices(u8, res_slice, res_roc_str.asSlice());
}
test "toStr: std.math.minInt" {
test "to_str: std.math.minInt" {
var dec: RocDec = .{ .num = std.math.minInt(i128) };
var res_roc_str = dec.toStr();
var res_roc_str = dec.to_str();
errdefer res_roc_str.decref();
defer res_roc_str.decref();
@ -1169,9 +1169,9 @@ test "toStr: std.math.minInt" {
try expectEqualSlices(u8, res_slice, res_roc_str.asSlice());
}
test "toStr: 0" {
test "to_str: 0" {
var dec: RocDec = .{ .num = 0 };
var res_roc_str = dec.toStr();
var res_roc_str = dec.to_str();
const res_slice: []const u8 = "0.0"[0..];
try expectEqualSlices(u8, res_slice, res_roc_str.asSlice());
@ -1443,8 +1443,8 @@ pub fn fromStr(arg: RocStr) callconv(.C) num_.NumParseResult(i128) {
}
}
pub fn toStr(arg: RocDec) callconv(.C) RocStr {
return @call(.always_inline, RocDec.toStr, .{arg});
pub fn to_str(arg: RocDec) callconv(.C) RocStr {
return @call(.always_inline, RocDec.to_str, .{arg});
}
pub fn fromF64C(arg: f64) callconv(.C) i128 {

View file

@ -50,7 +50,7 @@ comptime {
exportDecFn(dec.toF64, "to_f64");
exportDecFn(dec.toI128, "to_i128");
exportDecFn(dec.fromI128, "from_i128");
exportDecFn(dec.toStr, "to_str");
exportDecFn(dec.to_str, "to_str");
for (INTEGERS) |T| {
dec.exportFromInt(T, ROC_BUILTINS ++ ".dec.from_int.");

View file

@ -1,9 +1,9 @@
module [Bool, Eq, true, false, and, or, not, isEq, isNotEq]
module [Bool, Eq, true, false, and, or, not, is_eq, is_not_eq]
## Defines a type that can be compared for total equality.
##
## Total equality means that all values of the type can be compared to each
## other, and two values `a`, `b` are identical if and only if `isEq a b` is
## other, and two values `a`, `b` are identical if and only if `isEq(a, b)` is
## `Bool.true`.
##
## Not all types support total equality. For example, [`F32`](../Num#F32) and [`F64`](../Num#F64) can
@ -14,9 +14,9 @@ Eq implements
## Returns `Bool.true` if the input values are equal. This is
## equivalent to the logic
## [XNOR](https://en.wikipedia.org/wiki/Logical_equality) gate. The infix
## operator `==` can be used as shorthand for `Bool.isEq`.
## operator `==` can be used as shorthand for `Bool.is_eq`.
##
## **Note** that when `isEq` is determined by the Roc compiler, values are
## **Note** that when `is_eq` is determined by the Roc compiler, values are
## compared using structural equality. The rules for this are as follows:
##
## 1. Tags are equal if their name and also contents are equal.
@ -24,25 +24,25 @@ Eq implements
## 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`
## inputs are *NaN* then `is_eq` returns `Bool.false`. Refer to `Num.is_nan`
## for more detail.
## 5. Functions cannot be compared for structural equality, therefore Roc
## cannot derive `isEq` for types that contain functions.
isEq : a, a -> Bool where a implements Eq
## cannot derive `is_eq` for types that contain functions.
is_eq : a, a -> Bool where a implements Eq
## Represents the boolean true and false using an opaque type.
## `Bool` implements the `Eq` ability.
Bool := [True, False] implements [Eq { isEq: boolIsEq }]
Bool := [True, False] implements [Eq { is_eq: bool_is_eq }]
boolIsEq = \@Bool b1, @Bool b2 -> structuralEq b1 b2
bool_is_eq = \@Bool(b1), @Bool(b2) -> structural_eq(b1, b2)
## The boolean true value.
true : Bool
true = @Bool True
true = @Bool(True)
## The boolean false value.
false : Bool
false = @Bool False
false = @Bool(False)
## Returns `Bool.true` when both inputs are `Bool.true`. This is equivalent to
## the logic [AND](https://en.wikipedia.org/wiki/Logical_conjunction)
@ -50,7 +50,7 @@ false = @Bool False
## `Bool.and`.
##
## ```roc
## expect (Bool.and Bool.true Bool.true) == Bool.true
## expect Bool.and(Bool.true, Bool.true) == Bool.true
## expect (Bool.true && Bool.true) == Bool.true
## expect (Bool.false && Bool.true) == Bool.false
## expect (Bool.true && Bool.false) == Bool.false
@ -63,10 +63,10 @@ false = @Bool False
## other function. However, in some languages `&&` and `||` are special-cased.
## In these languages the compiler will skip evaluating the expression after the
## first operator under certain circumstances. For example an expression like
## `enablePets && likesDogs user` would compile to.
## `enable_pets && likes_dogs(user)` would compile to.
## ```roc
## if enablePets then
## likesDogs user
## if enable_pets then
## likes_dogs(user)
## else
## Bool.false
## ```
@ -79,7 +79,7 @@ and : Bool, Bool -> Bool
## the logic [OR](https://en.wikipedia.org/wiki/Logical_disjunction) gate.
## The infix operator `||` can also be used as shorthand for `Bool.or`.
## ```roc
## expect (Bool.or Bool.false Bool.true) == Bool.true
## expect Bool.or(Bool.false, Bool.true) == Bool.true
## expect (Bool.true || Bool.true) == Bool.true
## expect (Bool.false || Bool.true) == Bool.true
## expect (Bool.true || Bool.false) == Bool.true
@ -97,30 +97,30 @@ or : Bool, Bool -> Bool
## equivalent to the logic [NOT](https://en.wikipedia.org/wiki/Negation)
## gate. The operator `!` can also be used as shorthand for `Bool.not`.
## ```roc
## expect (Bool.not Bool.false) == Bool.true
## expect (!Bool.false) == Bool.true
## expect Bool.not(Bool.false) == Bool.true
## expect !Bool.false == Bool.true
## ```
not : Bool -> Bool
## This will call the function `Bool.isEq` on the inputs, and then `Bool.not`
## This will call the function `Bool.is_eq` on the inputs, and then `Bool.not`
## on the result. The is equivalent to the logic
## [XOR](https://en.wikipedia.org/wiki/Exclusive_or) gate. The infix operator
## `!=` can also be used as shorthand for `Bool.isNotEq`.
## `!=` can also be used as shorthand for `Bool.is_not_eq`.
##
## **Note** that `isNotEq` does not accept arguments whose types contain
## **Note** that `is_not_eq` does not accept arguments whose types contain
## functions.
## ```roc
## expect (Bool.isNotEq Bool.false Bool.true) == Bool.true
## expect Bool.is_not_eq(Bool.false, Bool.true) == Bool.true
## expect (Bool.false != Bool.false) == Bool.false
## expect "Apples" != "Oranges"
## ```
isNotEq : a, a -> Bool where a implements Eq
isNotEq = \a, b -> structuralNotEq a b
is_not_eq : a, a -> Bool where a implements Eq
is_not_eq = \a, b -> structural_not_eq(a, b)
# INTERNAL COMPILER USE ONLY: used to lower calls to `isEq` to structural
# INTERNAL COMPILER USE ONLY: used to lower calls to `is_eq` to structural
# equality via the `Eq` low-level for derived types.
structuralEq : a, a -> Bool
structural_eq : a, a -> Bool
# INTERNAL COMPILER USE ONLY: used to lower calls to `isNotEq` to structural
# INTERNAL COMPILER USE ONLY: used to lower calls to `is_not_eq` to structural
# inequality via the `NotEq` low-level for derived types.
structuralNotEq : a, a -> Bool
structural_not_eq : a, a -> Bool

View file

@ -9,13 +9,13 @@ module [box, unbox]
## optimization for advanced use cases with large values. A platform may require
## that some values are boxed.
## ```roc
## expect Box.unbox (Box.box "Stack Faster") == "Stack Faster"
## expect Box.unbox(Box.box("Stack Faster")) == "Stack Faster"
## ```
box : a -> Box a
## Returns a boxed value.
## ```roc
## expect Box.unbox (Box.box "Stack Faster") == "Stack Faster"
## expect Box.unbox(Box.box("Stack Faster") == "Stack Faster"
## ```
unbox : Box a -> a

View file

@ -24,10 +24,10 @@ module [
record,
tuple,
custom,
decodeWith,
fromBytesPartial,
fromBytes,
mapResult,
decode_with,
from_bytes_partial,
from_bytes,
map_result,
]
import List
@ -55,13 +55,13 @@ DecodeError : [TooShort]
## Return type of a [Decoder].
##
## This can be useful when creating a [custom](#custom) decoder or when
## using [fromBytesPartial](#fromBytesPartial). For example writing unit tests,
## using [from_bytes_partial](#from_bytes_partial). For example writing unit tests,
## such as;
## ```roc
## expect
## input = "\"hello\", " |> Str.toUtf8
## actual = Decode.fromBytesPartial input Json.json
## expected = Ok "hello"
## actual = Decode.from_bytes_partial(input, Json.json)
## expected = Ok("hello")
##
## actual.result == expected
## ```
@ -95,51 +95,51 @@ DecoderFormatting implements
string : Decoder Str fmt where fmt implements DecoderFormatting
list : Decoder elem fmt -> Decoder (List elem) fmt where fmt implements DecoderFormatting
## `record state stepField finalizer` decodes a record field-by-field.
## `record(state, step_field, finalizer)` decodes a record field-by-field.
##
## `stepField` returns a decoder for the given field in the record, or
## `step_field` returns a decoder for the given field in the record, or
## `Skip` if the field is not a part of the decoded record.
##
## `finalizer` should produce the record value from the decoded `state`.
record : state, (state, Str -> [Keep (Decoder state fmt), Skip]), (state, fmt -> Result val DecodeError) -> Decoder val fmt where fmt implements DecoderFormatting
## `tuple state stepElem finalizer` decodes a tuple element-by-element.
## `tuple(state, step_elem, finalizer)` decodes a tuple element-by-element.
##
## `stepElem` returns a decoder for the nth index in the tuple, or
## `step_elem` returns a decoder for the nth index in the tuple, or
## `TooLong` if the index is larger than the expected size of the tuple. The
## index passed to `stepElem` is 0-indexed.
## index passed to `step_elem` is 0-indexed.
##
## `finalizer` should produce the tuple value from the decoded `state`.
tuple : state, (state, U64 -> [Next (Decoder state fmt), TooLong]), (state -> Result val DecodeError) -> Decoder val fmt where fmt implements DecoderFormatting
## Build a custom [Decoder] function. For example the implementation of
## `decodeBool` could be defined as follows;
## `decode_bool` could be defined as follows;
##
## ```roc
## decodeBool = Decode.custom \bytes, @Json {} ->
## decode_bool = Decode.custom \bytes, @Json({}) ->
## when bytes is
## ['f', 'a', 'l', 's', 'e', ..] -> { result: Ok Bool.false, rest: List.dropFirst bytes 5 }
## ['t', 'r', 'u', 'e', ..] -> { result: Ok Bool.true, rest: List.dropFirst bytes 4 }
## _ -> { result: Err TooShort, rest: bytes }
## ['f', 'a', 'l', 's', 'e', ..] -> { result: Ok(Bool.false), rest: List.drop_first(bytes, 5) }
## ['t', 'r', 'u', 'e', ..] -> { result: Ok Bool.true, rest: List.drop_first(bytes, 4) }
## _ -> { result: Err(TooShort), rest: bytes }
## ```
custom : (List U8, fmt -> DecodeResult val) -> Decoder val fmt where fmt implements DecoderFormatting
custom = \decode -> @Decoder decode
custom = \decode -> @Decoder(decode)
## Decode a `List U8` utf-8 bytes using a specific [Decoder] function
decodeWith : List U8, Decoder val fmt, fmt -> DecodeResult val where fmt implements DecoderFormatting
decodeWith = \bytes, @Decoder decode, fmt -> decode bytes fmt
decode_with : List U8, Decoder val fmt, fmt -> DecodeResult val where fmt implements DecoderFormatting
decode_with = \bytes, @Decoder(decode), fmt -> decode(bytes, fmt)
## Decode a `List U8` utf-8 bytes and return a [DecodeResult](#DecodeResult)
## ```roc
## expect
## input = "\"hello\", " |> Str.toUtf8
## actual = Decode.fromBytesPartial input Json.json
## expected = Ok "hello"
## actual = Decode.from_bytes_partial(input Json.json)
## expected = Ok("hello")
##
## actual.result == expected
## ```
fromBytesPartial : List U8, fmt -> DecodeResult val where val implements Decoding, fmt implements DecoderFormatting
fromBytesPartial = \bytes, fmt -> decodeWith bytes decoder fmt
from_bytes_partial : List U8, fmt -> DecodeResult val where val implements Decoding, fmt implements DecoderFormatting
from_bytes_partial = \bytes, fmt -> decode_with(bytes, decoder, fmt)
## Decode a `List U8` utf-8 bytes and return a [Result] with no leftover bytes
## expected. If successful returns `Ok val`, however, if there are bytes
@ -147,22 +147,22 @@ fromBytesPartial = \bytes, fmt -> decodeWith bytes decoder fmt
## ```roc
## expect
## input = "\"hello\", " |> Str.toUtf8
## actual = Decode.fromBytes input Json.json
## expected = Ok "hello"
## actual = Decode.from_bytes(input, Json.json)
## expected = Ok("hello")
##
## actual == expected
## ```
fromBytes : List U8, fmt -> Result val [Leftover (List U8)]DecodeError where val implements Decoding, fmt implements DecoderFormatting
fromBytes = \bytes, fmt ->
when fromBytesPartial bytes fmt is
from_bytes : List U8, fmt -> Result val [Leftover (List U8)]DecodeError where val implements Decoding, fmt implements DecoderFormatting
from_bytes = \bytes, fmt ->
when from_bytes_partial(bytes, fmt) is
{ result, rest } ->
if List.isEmpty rest then
if List.is_empty(rest) then
when result is
Ok val -> Ok val
Err TooShort -> Err TooShort
Ok(val) -> Ok(val)
Err(TooShort) -> Err(TooShort)
else
Err (Leftover rest)
Err(Leftover(rest))
## Transform the `val` of a [DecodeResult]
mapResult : DecodeResult a, (a -> b) -> DecodeResult b
mapResult = \{ result, rest }, mapper -> { result: Result.map result mapper, rest }
map_result : DecodeResult a, (a -> b) -> DecodeResult b
map_result = \{ result, rest }, mapper -> { result: Result.map(result, mapper), rest }

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,7 @@
module [
Encoder,
Encoding,
toEncoder,
to_encoder,
EncoderFormatting,
u8,
u16,
@ -23,9 +23,9 @@ module [
tag,
tuple,
custom,
appendWith,
append_with,
append,
toBytes,
to_bytes,
]
import Num exposing [
@ -48,7 +48,7 @@ import Bool exposing [Bool]
Encoder fmt := List U8, fmt -> List U8 where fmt implements EncoderFormatting
Encoding implements
toEncoder : val -> Encoder fmt where val implements Encoding, fmt implements EncoderFormatting
to_encoder : val -> Encoder fmt where val implements Encoding, fmt implements EncoderFormatting
EncoderFormatting implements
u8 : U8 -> Encoder fmt where fmt implements EncoderFormatting
@ -76,41 +76,41 @@ EncoderFormatting implements
## ```roc
## expect
## # Appends the byte 42
## customEncoder = Encode.custom (\bytes, _fmt -> List.append bytes 42)
## custom_encoder = Encode.custom(\bytes, _fmt -> List.append(bytes, 42))
##
## actual = Encode.appendWith [] customEncoder Core.json
## actual = Encode.append_with([], custom_encoder, Core.json)
## expected = [42] # Expected result is a list with a single byte, 42
##
## actual == expected
## ```
custom : (List U8, fmt -> List U8) -> Encoder fmt where fmt implements EncoderFormatting
custom = \encoder -> @Encoder encoder
custom = \encoder -> @Encoder(encoder)
appendWith : List U8, Encoder fmt, fmt -> List U8 where fmt implements EncoderFormatting
appendWith = \lst, @Encoder doEncoding, fmt -> doEncoding lst fmt
append_with : List U8, Encoder fmt, fmt -> List U8 where fmt implements EncoderFormatting
append_with = \lst, @Encoder(do_encoding), fmt -> do_encoding(lst, fmt)
## Appends the encoded representation of a value to an existing list of bytes.
##
## ```roc
## expect
## actual = Encode.append [] { foo: 43 } Core.json
## expected = Str.toUtf8 """{"foo":43}"""
## actual = Encode.append([], { foo: 43 }, Core.json)
## expected = Str.to_utf8("""{"foo":43}""")
##
## actual == expected
## ```
append : List U8, val, fmt -> List U8 where val implements Encoding, fmt implements EncoderFormatting
append = \lst, val, fmt -> appendWith lst (toEncoder val) fmt
append = \lst, val, fmt -> append_with(lst, to_encoder(val), fmt)
## Encodes a value to a list of bytes (`List U8`) according to the specified format.
##
## ```roc
## expect
## fooRec = { foo: 42 }
## foo_rec = { foo: 42 }
##
## actual = Encode.toBytes fooRec Core.json
## expected = Str.toUtf8 """{"foo":42}"""
## actual = Encode.to_bytes(foo_rec, Core.json)
## expected = Str.to_utf8("""{"foo":42}""")
##
## actual == expected
## ```
toBytes : val, fmt -> List U8 where val implements Encoding, fmt implements EncoderFormatting
toBytes = \val, fmt -> appendWith [] (toEncoder val) fmt
to_bytes : val, fmt -> List U8 where val implements Encoding, fmt implements EncoderFormatting
to_bytes = \val, fmt -> append_with([], to_encoder(val), fmt)

View file

@ -2,23 +2,23 @@ module [
Hash,
Hasher,
hash,
addBytes,
addU8,
addU16,
addU32,
addU64,
addU128,
hashBool,
hashI8,
hashI16,
hashI32,
hashI64,
hashI128,
hashDec,
add_bytes,
add_u8,
add_u16,
add_u32,
add_u64,
add_u128,
hash_bool,
hash_i8,
hash_i16,
hash_i32,
hash_i64,
hash_i128,
hash_dec,
complete,
hashStrBytes,
hashList,
hashUnordered,
hash_str_bytes,
hash_list,
hash_unordered,
]
import Bool exposing [Bool]
@ -52,86 +52,90 @@ Hash implements
## cryptographically-secure hashing.
Hasher implements
## Adds a list of bytes to the hasher.
addBytes : a, List U8 -> a where a implements Hasher
add_bytes : a, List U8 -> a where a implements Hasher
## Adds a single U8 to the hasher.
addU8 : a, U8 -> a where a implements Hasher
add_u8 : a, U8 -> a where a implements Hasher
## Adds a single U16 to the hasher.
addU16 : a, U16 -> a where a implements Hasher
add_u16 : a, U16 -> a where a implements Hasher
## Adds a single U32 to the hasher.
addU32 : a, U32 -> a where a implements Hasher
add_u32 : a, U32 -> a where a implements Hasher
## Adds a single U64 to the hasher.
addU64 : a, U64 -> a where a implements Hasher
add_u64 : a, U64 -> a where a implements Hasher
## Adds a single U128 to the hasher.
addU128 : a, U128 -> a where a implements Hasher
add_u128 : a, U128 -> a where a implements Hasher
## Completes the hasher, extracting a hash value from its
## accumulated hash state.
complete : a -> U64 where a implements Hasher
## Adds a string into a [Hasher] by hashing its UTF-8 bytes.
hashStrBytes = \hasher, s ->
addBytes hasher (Str.toUtf8 s)
hash_str_bytes = \hasher, s ->
add_bytes(hasher, Str.to_utf8(s))
## Adds a list of [Hash]able elements to a [Hasher] by hashing each element.
hashList = \hasher, lst ->
List.walk lst hasher \accumHasher, elem ->
hash accumHasher elem
hash_list = \hasher, lst ->
List.walk(
lst,
hasher,
\accum_hasher, elem ->
hash(accum_hasher, elem),
)
## Adds a single [Bool] to a hasher.
hashBool : a, Bool -> a where a implements Hasher
hashBool = \hasher, b ->
asU8 = if b then 1 else 0
addU8 hasher asU8
hash_bool : a, Bool -> a where a implements Hasher
hash_bool = \hasher, b ->
as_u8 = if b then 1 else 0
add_u8(hasher, as_u8)
## Adds a single I8 to a hasher.
hashI8 : a, I8 -> a where a implements Hasher
hashI8 = \hasher, n -> addU8 hasher (Num.toU8 n)
hash_i8 : a, I8 -> a where a implements Hasher
hash_i8 = \hasher, n -> add_u8(hasher, Num.to_u8(n))
## Adds a single I16 to a hasher.
hashI16 : a, I16 -> a where a implements Hasher
hashI16 = \hasher, n -> addU16 hasher (Num.toU16 n)
hash_i16 : a, I16 -> a where a implements Hasher
hash_i16 = \hasher, n -> add_u16(hasher, Num.to_u16(n))
## Adds a single I32 to a hasher.
hashI32 : a, I32 -> a where a implements Hasher
hashI32 = \hasher, n -> addU32 hasher (Num.toU32 n)
hash_i32 : a, I32 -> a where a implements Hasher
hash_i32 = \hasher, n -> add_u32(hasher, Num.to_u32(n))
## Adds a single I64 to a hasher.
hashI64 : a, I64 -> a where a implements Hasher
hashI64 = \hasher, n -> addU64 hasher (Num.toU64 n)
hash_i64 : a, I64 -> a where a implements Hasher
hash_i64 = \hasher, n -> add_u64(hasher, Num.to_u64(n))
## Adds a single I128 to a hasher.
hashI128 : a, I128 -> a where a implements Hasher
hashI128 = \hasher, n -> addU128 hasher (Num.toU128 n)
hash_i128 : a, I128 -> a where a implements Hasher
hash_i128 = \hasher, n -> add_u128(hasher, Num.to_u128(n))
## Adds a single [Dec] to a hasher.
hashDec : a, Dec -> a where a implements Hasher
hashDec = \hasher, n -> hashI128 hasher (Num.withoutDecimalPoint n)
hash_dec : a, Dec -> a where a implements Hasher
hash_dec = \hasher, n -> hash_i128(hasher, Num.without_decimal_point(n))
## Adds a container of [Hash]able elements to a [Hasher] by hashing each element.
## The container is iterated using the walk method passed in.
## The order of the elements does not affect the final hash.
hashUnordered = \hasher, container, walk ->
walk
container
0
(\accum, elem ->
hash_unordered = \hasher, container, walk ->
walk(
container,
0,
\accum, elem ->
x =
# Note, we intentionally copy the hasher in every iteration.
# Having the same base state is required for unordered hashing.
hasher
|> hash elem
|> hash(elem)
|> complete
nextAccum = Num.addWrap accum x
next_accum = Num.add_wrap(accum, x)
if nextAccum < accum then
if next_accum < accum then
# we don't want to lose a bit of entropy on overflow, so add it back in.
Num.addWrap nextAccum 1
Num.add_wrap(next_accum, 1)
else
nextAccum
)
|> \accum -> addU64 hasher accum
next_accum,
)
|> \accum -> add_u64(hasher, accum)

View file

@ -31,8 +31,8 @@ module [
dec,
custom,
apply,
toInspector,
toStr,
to_inspector,
to_str,
]
import Bool exposing [Bool]
@ -81,24 +81,24 @@ InspectFormatter implements
Inspector f := f -> f where f implements InspectFormatter
custom : (f -> f) -> Inspector f where f implements InspectFormatter
custom = \fn -> @Inspector fn
custom = \fn -> @Inspector(fn)
apply : Inspector f, f -> f where f implements InspectFormatter
apply = \@Inspector fn, fmt -> fn fmt
apply = \@Inspector(fn), fmt -> fn(fmt)
Inspect implements
toInspector : val -> Inspector f where val implements Inspect, f implements InspectFormatter
to_inspector : val -> Inspector f where val implements Inspect, f implements InspectFormatter
inspect : val -> f where val implements Inspect, f implements InspectFormatter
inspect = \val ->
(@Inspector valFn) = toInspector val
valFn (init {})
@Inspector(val_fn) = to_inspector(val)
val_fn(init({}))
toStr : val -> Str where val implements Inspect
toStr = \val ->
to_str : val -> Str where val implements Inspect
to_str = \val ->
val
|> inspect
|> toDbgStr
|> to_dbg_str
# The current default formatter for inspect.
# This just returns a simple string for debugging.
@ -106,239 +106,260 @@ toStr = \val ->
DbgFormatter := { data : Str }
implements [
InspectFormatter {
init: dbgInit,
list: dbgList,
set: dbgSet,
dict: dbgDict,
tag: dbgTag,
tuple: dbgTuple,
record: dbgRecord,
bool: dbgBool,
str: dbgStr,
opaque: dbgOpaque,
function: dbgFunction,
u8: dbgU8,
i8: dbgI8,
u16: dbgU16,
i16: dbgI16,
u32: dbgU32,
i32: dbgI32,
u64: dbgU64,
i64: dbgI64,
u128: dbgU128,
i128: dbgI128,
f32: dbgF32,
f64: dbgF64,
dec: dbgDec,
init: dbg_init,
list: dbg_list,
set: dbg_set,
dict: dbg_dict,
tag: dbg_tag,
tuple: dbg_tuple,
record: dbg_record,
bool: dbg_bool,
str: dbg_str,
opaque: dbg_opaque,
function: dbg_function,
u8: dbg_u8,
i8: dbg_i8,
u16: dbg_u16,
i16: dbg_i16,
u32: dbg_u32,
i32: dbg_i32,
u64: dbg_u64,
i64: dbg_i64,
u128: dbg_u128,
i128: dbg_i128,
f32: dbg_f32,
f64: dbg_f64,
dec: dbg_dec,
},
]
dbgInit : {} -> DbgFormatter
dbgInit = \{} -> @DbgFormatter { data: "" }
dbg_init : {} -> DbgFormatter
dbg_init = \{} -> @DbgFormatter({ data: "" })
dbgList : list, ElemWalker (DbgFormatter, Bool) list elem, (elem -> Inspector DbgFormatter) -> Inspector DbgFormatter
dbgList = \content, walkFn, toDbgInspector ->
custom \f0 ->
dbgWrite f0 "["
|> \f1 ->
walkFn content (f1, Bool.false) \(f2, prependSep), elem ->
dbg_list : list, ElemWalker (DbgFormatter, Bool) list elem, (elem -> Inspector DbgFormatter) -> Inspector DbgFormatter
dbg_list = \content, walk_fn, to_dbg_inspector ->
custom_list_dbg = \f0 ->
f1 = dbg_write(f0, "[")
(f5, _) = walk_fn(
content,
(f1, Bool.false),
\(f2, prepend_sep), elem ->
f3 =
if prependSep then
dbgWrite f2 ", "
if prepend_sep then
dbg_write(f2, ", ")
else
f2
elem
|> toDbgInspector
|> apply f3
|> \f4 -> (f4, Bool.true)
|> .0
|> dbgWrite "]"
|> to_dbg_inspector
|> apply(f3)
|> \f4 -> (f4, Bool.true),
)
dbgSet : set, ElemWalker (DbgFormatter, Bool) set elem, (elem -> Inspector DbgFormatter) -> Inspector DbgFormatter
dbgSet = \content, walkFn, toDbgInspector ->
custom \f0 ->
dbgWrite f0 "{"
|> \f1 ->
walkFn content (f1, Bool.false) \(f2, prependSep), elem ->
dbg_write(f5, "]")
custom(custom_list_dbg)
dbg_set : set, ElemWalker (DbgFormatter, Bool) set elem, (elem -> Inspector DbgFormatter) -> Inspector DbgFormatter
dbg_set = \content, walk_fn, to_dbg_inspector ->
custom_dbg_set = \f0 ->
f1 = dbg_write(f0, "{")
(f5, _) = walk_fn(
content,
(f1, Bool.false),
\(f2, prepend_sep), elem ->
f3 =
if prependSep then
dbgWrite f2 ", "
if prepend_sep then
dbg_write(f2, ", ")
else
f2
elem
|> toDbgInspector
|> apply f3
|> \f4 -> (f4, Bool.true)
|> .0
|> dbgWrite "}"
|> to_dbg_inspector
|> apply(f3)
|> \f4 -> (f4, Bool.true),
)
dbgDict : dict, KeyValWalker (DbgFormatter, Bool) dict key value, (key -> Inspector DbgFormatter), (value -> Inspector DbgFormatter) -> Inspector DbgFormatter
dbgDict = \d, walkFn, keyToInspector, valueToInspector ->
custom \f0 ->
dbgWrite f0 "{"
|> \f1 ->
walkFn d (f1, Bool.false) \(f2, prependSep), key, value ->
dbg_write(f5, "}")
custom(custom_dbg_set)
dbg_dict : dict, KeyValWalker (DbgFormatter, Bool) dict key value, (key -> Inspector DbgFormatter), (value -> Inspector DbgFormatter) -> Inspector DbgFormatter
dbg_dict = \d, walk_fn, key_to_inspector, value_to_inspector ->
custom_dbg_dict = \f0 ->
f1 = dbg_write(f0, "{")
(f5, _) = walk_fn(
d,
(f1, Bool.false),
\(f2, prepend_sep), key, value ->
f3 =
if prependSep then
dbgWrite f2 ", "
if prepend_sep then
dbg_write(f2, ", ")
else
f2
apply (keyToInspector key) f3
|> dbgWrite ": "
|> \x -> apply (valueToInspector value) x
|> \f4 -> (f4, Bool.true)
|> .0
|> dbgWrite "}"
apply(key_to_inspector(key), f3)
|> dbg_write(": ")
|> \x -> apply(value_to_inspector(value), x)
|> \f4 -> (f4, Bool.true),
)
dbgTag : Str, List (Inspector DbgFormatter) -> Inspector DbgFormatter
dbgTag = \name, fields ->
if List.isEmpty fields then
custom \f0 ->
dbgWrite f0 name
dbg_write(f5, "}")
custom(custom_dbg_dict)
dbg_tag : Str, List (Inspector DbgFormatter) -> Inspector DbgFormatter
dbg_tag = \name, fields ->
if List.is_empty(fields) then
custom(\f0 -> dbg_write(f0, name))
else
custom \f0 ->
dbgWrite f0 "("
|> dbgWrite name
|> \f1 ->
List.walk fields f1 \f2, inspector ->
dbgWrite f2 " "
|> \x -> apply inspector x
|> dbgWrite ")"
custom_dbg_tag = \f0 ->
f1 =
dbg_write(f0, "(")
|> dbg_write(name)
dbgTuple : List (Inspector DbgFormatter) -> Inspector DbgFormatter
dbgTuple = \fields ->
custom \f0 ->
dbgWrite f0 "("
|> \f1 ->
List.walk fields (f1, Bool.false) \(f2, prependSep), inspector ->
f3 = List.walk(
fields,
f1,
\f2, inspector ->
dbg_write(f2, " ")
|> \x -> apply(inspector, x),
)
dbg_write(f3, ")")
custom(custom_dbg_tag)
dbg_tuple : List (Inspector DbgFormatter) -> Inspector DbgFormatter
dbg_tuple = \fields ->
custom_dbg_tuple = \f0 ->
f1 = dbg_write(f0, "(")
(f5, _) = List.walk(
fields,
(f1, Bool.false),
\(f2, prepend_sep), inspector ->
f3 =
if prependSep then
dbgWrite f2 ", "
if prepend_sep then
dbg_write(f2, ", ")
else
f2
apply inspector f3
|> \f4 -> (f4, Bool.true)
|> .0
|> dbgWrite ")"
apply(inspector, f3)
|> \f4 -> (f4, Bool.true),
)
dbgRecord : List { key : Str, value : Inspector DbgFormatter } -> Inspector DbgFormatter
dbgRecord = \fields ->
custom \f0 ->
dbgWrite f0 "{"
|> \f1 ->
List.walk fields (f1, Bool.false) \(f2, prependSep), { key, value } ->
dbg_write(f5, ")")
custom(custom_dbg_tuple)
dbg_record : List { key : Str, value : Inspector DbgFormatter } -> Inspector DbgFormatter
dbg_record = \fields ->
custom_dbg_record = \f0 ->
f1 = dbg_write(f0, "{")
(f5, _) = List.walk(
fields,
(f1, Bool.false),
\(f2, prepend_sep), { key, value } ->
f3 =
if prependSep then
dbgWrite f2 ", "
if prepend_sep then
dbg_write(f2, ", ")
else
f2
dbgWrite f3 key
|> dbgWrite ": "
|> \x -> apply value x
|> \f4 -> (f4, Bool.true)
|> .0
|> dbgWrite "}"
dbg_write(f3, key)
|> dbg_write(": ")
|> \x -> apply(value, x)
|> \f4 -> (f4, Bool.true),
)
dbgBool : Bool -> Inspector DbgFormatter
dbgBool = \b ->
if b then
custom \f0 ->
dbgWrite f0 "Bool.true"
else
custom \f0 ->
dbgWrite f0 "Bool.false"
dbg_write(f5, "}")
dbgStr : Str -> Inspector DbgFormatter
dbgStr = \s ->
custom \f0 ->
f0
|> dbgWrite "\""
|> dbgWrite s # TODO: Should we be escaping strings for dbg/logging?
|> dbgWrite "\""
custom(custom_dbg_record)
dbgOpaque : * -> Inspector DbgFormatter
dbgOpaque = \_ ->
custom \f0 ->
dbgWrite f0 "<opaque>"
dbg_bool : Bool -> Inspector DbgFormatter
dbg_bool = \b ->
text = if b then "Bool.true" else "Bool.false"
custom(\f0 -> dbg_write(f0, text))
dbgFunction : * -> Inspector DbgFormatter
dbgFunction = \_ ->
custom \f0 ->
dbgWrite f0 "<function>"
dbg_str : Str -> Inspector DbgFormatter
dbg_str = \s ->
# escape invisible unicode characters as in fmt_str_body crates/compiler/fmt/src/expr.rs
escape_s =
Str.replace_each(s, "\u(feff)", "\\u(feff)")
|> Str.replace_each("\u(200b)", "\\u(200b)")
|> Str.replace_each("\u(200c)", "\\u(200c)")
|> Str.replace_each("\u(200d)", "\\u(200d)")
dbgU8 : U8 -> Inspector DbgFormatter
dbgU8 = \num ->
custom \f0 ->
dbgWrite f0 (num |> Num.toStr)
custom_dbg_str = \f0 ->
dbg_write(f0, "\"")
|> dbg_write(escape_s)
|> dbg_write("\"")
dbgI8 : I8 -> Inspector DbgFormatter
dbgI8 = \num ->
custom \f0 ->
dbgWrite f0 (num |> Num.toStr)
custom(custom_dbg_str)
dbgU16 : U16 -> Inspector DbgFormatter
dbgU16 = \num ->
custom \f0 ->
dbgWrite f0 (num |> Num.toStr)
dbg_opaque : * -> Inspector DbgFormatter
dbg_opaque = \_ ->
custom(\f0 -> dbg_write(f0, "<opaque>"))
dbgI16 : I16 -> Inspector DbgFormatter
dbgI16 = \num ->
custom \f0 ->
dbgWrite f0 (num |> Num.toStr)
dbg_function : * -> Inspector DbgFormatter
dbg_function = \_ ->
custom(\f0 -> dbg_write(f0, "<function>"))
dbgU32 : U32 -> Inspector DbgFormatter
dbgU32 = \num ->
custom \f0 ->
dbgWrite f0 (num |> Num.toStr)
dbg_u8 : U8 -> Inspector DbgFormatter
dbg_u8 = \num ->
custom(\f0 -> dbg_write(f0, Num.to_str(num)))
dbgI32 : I32 -> Inspector DbgFormatter
dbgI32 = \num ->
custom \f0 ->
dbgWrite f0 (num |> Num.toStr)
dbg_i8 : I8 -> Inspector DbgFormatter
dbg_i8 = \num ->
custom(\f0 -> dbg_write(f0, Num.to_str(num)))
dbgU64 : U64 -> Inspector DbgFormatter
dbgU64 = \num ->
custom \f0 ->
dbgWrite f0 (num |> Num.toStr)
dbg_u16 : U16 -> Inspector DbgFormatter
dbg_u16 = \num ->
custom(\f0 -> dbg_write(f0, Num.to_str(num)))
dbgI64 : I64 -> Inspector DbgFormatter
dbgI64 = \num ->
custom \f0 ->
dbgWrite f0 (num |> Num.toStr)
dbg_i16 : I16 -> Inspector DbgFormatter
dbg_i16 = \num ->
custom(\f0 -> dbg_write(f0, Num.to_str(num)))
dbgU128 : U128 -> Inspector DbgFormatter
dbgU128 = \num ->
custom \f0 ->
dbgWrite f0 (num |> Num.toStr)
dbg_u32 : U32 -> Inspector DbgFormatter
dbg_u32 = \num ->
custom(\f0 -> dbg_write(f0, Num.to_str(num)))
dbgI128 : I128 -> Inspector DbgFormatter
dbgI128 = \num ->
custom \f0 ->
dbgWrite f0 (num |> Num.toStr)
dbg_i32 : I32 -> Inspector DbgFormatter
dbg_i32 = \num ->
custom(\f0 -> dbg_write(f0, Num.to_str(num)))
dbgF32 : F32 -> Inspector DbgFormatter
dbgF32 = \num ->
custom \f0 ->
dbgWrite f0 (num |> Num.toStr)
dbg_u64 : U64 -> Inspector DbgFormatter
dbg_u64 = \num ->
custom(\f0 -> dbg_write(f0, Num.to_str(num)))
dbgF64 : F64 -> Inspector DbgFormatter
dbgF64 = \num ->
custom \f0 ->
dbgWrite f0 (num |> Num.toStr)
dbg_i64 : I64 -> Inspector DbgFormatter
dbg_i64 = \num ->
custom(\f0 -> dbg_write(f0, Num.to_str(num)))
dbgDec : Dec -> Inspector DbgFormatter
dbgDec = \num ->
custom \f0 ->
dbgWrite f0 (num |> Num.toStr)
dbg_u128 : U128 -> Inspector DbgFormatter
dbg_u128 = \num ->
custom(\f0 -> dbg_write(f0, Num.to_str(num)))
dbgWrite : DbgFormatter, Str -> DbgFormatter
dbgWrite = \@DbgFormatter { data }, added ->
@DbgFormatter { data: Str.concat data added }
dbg_i128 : I128 -> Inspector DbgFormatter
dbg_i128 = \num ->
custom(\f0 -> dbg_write(f0, Num.to_str(num)))
toDbgStr : DbgFormatter -> Str
toDbgStr = \@DbgFormatter { data } -> data
dbg_f32 : F32 -> Inspector DbgFormatter
dbg_f32 = \num ->
custom(\f0 -> dbg_write(f0, Num.to_str(num)))
dbg_f64 : F64 -> Inspector DbgFormatter
dbg_f64 = \num ->
custom(\f0 -> dbg_write(f0, Num.to_str(num)))
dbg_dec : Dec -> Inspector DbgFormatter
dbg_dec = \num ->
custom(\f0 -> dbg_write(f0, Num.to_str(num)))
dbg_write : DbgFormatter, Str -> DbgFormatter
dbg_write = \@DbgFormatter({ data }), added ->
@DbgFormatter({ data: Str.concat(data, added) })
to_dbg_str : DbgFormatter -> Str
to_dbg_str = \@DbgFormatter({ data }) -> data

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,15 +1,15 @@
module [
Result,
isOk,
isErr,
is_ok,
is_err,
map,
mapErr,
mapBoth,
map_err,
map_both,
map2,
try,
onErr,
onErr!,
withDefault,
on_err,
on_err!,
with_default,
]
import Bool exposing [Bool]
@ -20,42 +20,42 @@ Result ok err : [Ok ok, Err err]
## Returns `Bool.true` if the result indicates a success, else returns `Bool.false`
## ```roc
## Result.isOk (Ok 5)
## Result.is_ok(Ok(5))
## ```
isOk : Result ok err -> Bool
isOk = \result ->
is_ok : Result ok err -> Bool
is_ok = \result ->
when result is
Ok _ -> Bool.true
Err _ -> Bool.false
Ok(_) -> Bool.true
Err(_) -> Bool.false
## Returns `Bool.true` if the result indicates a failure, else returns `Bool.false`
## ```roc
## Result.isErr (Err "uh oh")
## Result.is_err(Err("uh oh"))
## ```
isErr : Result ok err -> Bool
isErr = \result ->
is_err : Result ok err -> Bool
is_err = \result ->
when result is
Ok _ -> Bool.false
Err _ -> Bool.true
Ok(_) -> Bool.false
Err(_) -> Bool.true
## If the result is `Ok`, returns the value it holds. Otherwise, returns
## the given default value.
## ```roc
## Result.withDefault (Ok 7) 42
## Result.withDefault (Err "uh oh") 42
## Result.with_default(Ok(7), 42)
## Result.with_default(Err("uh oh"), 42)
## ```
withDefault : Result ok err, ok -> ok
withDefault = \result, default ->
with_default : Result ok err, ok -> ok
with_default = \result, default ->
when result is
Ok value -> value
Err _ -> default
Ok(value) -> value
Err(_) -> default
## If the result is `Ok`, transforms the value it holds by running a conversion
## function on it. Then returns a new `Ok` holding the transformed value. If the
## result is `Err`, this has no effect. Use [mapErr] to transform an `Err`.
## result is `Err`, this has no effect. Use [map_err] to transform an `Err`.
## ```roc
## Result.map (Ok 12) Num.neg
## Result.map (Err "yipes!") Num.neg
## Result.map(Ok(12), Num.neg)
## Result.map(Err("yipes!"), Num.neg)
## ```
##
## Functions like `map` are common in Roc; see for example [List.map],
@ -63,73 +63,75 @@ withDefault = \result, default ->
map : Result a err, (a -> b) -> Result b err
map = \result, transform ->
when result is
Ok v -> Ok (transform v)
Err e -> Err e
Ok(v) -> Ok(transform(v))
Err(e) -> Err(e)
## If the result is `Err`, transforms the value it holds by running a conversion
## function on it. Then returns a new `Err` holding the transformed value. If
## the result is `Ok`, this has no effect. Use [map] to transform an `Ok`.
## ```roc
## Result.mapErr (Err "yipes!") Str.isEmpty
## Result.mapErr (Ok 12) Str.isEmpty
## Result.map_err(Err("yipes!"), Str.is_empty)
## Result.map_err(Ok(12), Str.is_empty)
## ```
mapErr : Result ok a, (a -> b) -> Result ok b
mapErr = \result, transform ->
map_err : Result ok a, (a -> b) -> Result ok b
map_err = \result, transform ->
when result is
Ok v -> Ok v
Err e -> Err (transform e)
Ok(v) -> Ok(v)
Err(e) -> Err(transform(e))
## Maps both the `Ok` and `Err` values of a `Result` to new values.
mapBoth : Result ok1 err1, (ok1 -> ok2), (err1 -> err2) -> Result ok2 err2
mapBoth = \result, okTransform, errTransform ->
map_both : Result ok1 err1, (ok1 -> ok2), (err1 -> err2) -> Result ok2 err2
map_both = \result, ok_transform, err_transform ->
when result is
Ok val -> Ok (okTransform val)
Err err -> Err (errTransform err)
Ok(val) -> Ok(ok_transform(val))
Err(err) -> Err(err_transform(err))
## Maps the `Ok` values of two `Result`s to a new value using a given transformation,
## or returns the first `Err` value encountered.
map2 : Result a err, Result b err, (a, b -> c) -> Result c err
map2 = \firstResult, secondResult, transform ->
when (firstResult, secondResult) is
(Ok first, Ok second) -> Ok (transform first second)
(Err err, _) -> Err err
(_, Err err) -> Err err
map2 = \first_result, second_result, transform ->
when (first_result, second_result) is
(Ok(first), Ok(second)) -> Ok(transform(first, second))
(Err(err), _) -> Err(err)
(_, Err(err)) -> Err(err)
## If the result is `Ok`, transforms the entire result by running a conversion
## function on the value the `Ok` holds. Then returns that new result. If the
## result is `Err`, this has no effect. Use `onErr` to transform an `Err`.
## result is `Err`, this has no effect. Use `on_err` to transform an `Err`.
## ```roc
## Result.try (Ok -1) \num -> if num < 0 then Err "negative!" else Ok -num
## Result.try (Err "yipes!") \num -> if num < 0 then Err "negative!" else Ok -num
## Result.try(Ok(-1), (\num -> if num < 0 then Err("negative!") else Ok(-num)))
## Result.try(Err("yipes!"), (\num -> if num < 0 then Err("negative!") else Ok(-num)))
## ```
try : Result a err, (a -> Result b err) -> Result b err
try = \result, transform ->
when result is
Ok v -> transform v
Err e -> Err e
Ok(v) -> transform(v)
Err(e) -> Err(e)
## If the result is `Err`, transforms the entire result by running a conversion
## function on the value the `Err` holds. Then returns that new result. If the
## result is `Ok`, this has no effect. Use `try` to transform an `Ok`.
## ```roc
## Result.onErr (Ok 10) \errorNum -> Str.toU64 errorNum
## Result.onErr (Err "42") \errorNum -> Str.toU64 errorNum
## Result.on_err(Ok(10), (\error_num -> Str.to_u64(error_num)))
## Result.on_err(Err("42"), (\error_num -> Str.to_u64(error_num)))
## ```
onErr : Result a err, (err -> Result a otherErr) -> Result a otherErr
onErr = \result, transform ->
on_err : Result a err, (err -> Result a other_err) -> Result a other_err
on_err = \result, transform ->
when result is
Ok v -> Ok v
Err e -> transform e
Ok(v) -> Ok(v)
Err(e) -> transform(e)
## Like [onErr], but it allows the transformation function to produce effects.
## Like [on_err], but it allows the transformation function to produce effects.
##
## ```roc
## Result.onErr (Err "missing user") \msg ->
## try Stdout.line! "ERROR: $(msg)"
## Err msg
## Result.on_err(Err("missing user"), (\msg ->
## Stdout.line!("ERROR: $(msg)")?
##
## Err(msg)
## ))
## ```
onErr! : Result a err, (err => Result a otherErr) => Result a otherErr
onErr! = \result, transform! ->
on_err! : Result a err, (err => Result a other_err) => Result a other_err
on_err! = \result, transform! ->
when result is
Ok v -> Ok v
Err e -> transform! e
Ok(v) -> Ok(v)
Err(e) -> transform!(e)

View file

@ -1,27 +1,27 @@
module [
Set,
empty,
withCapacity,
with_capacity,
reserve,
releaseExcessCapacity,
release_excess_capacity,
single,
walk,
walkUntil,
keepIf,
dropIf,
walk_until,
keep_if,
drop_if,
insert,
len,
isEmpty,
is_empty,
capacity,
remove,
contains,
toList,
fromList,
to_list,
from_list,
union,
intersection,
difference,
map,
joinMap,
join_map,
]
import List
@ -36,154 +36,160 @@ import Inspect exposing [Inspect, Inspector, InspectFormatter]
Set k := Dict.Dict k {} where k implements Hash & Eq
implements [
Eq {
isEq,
is_eq,
},
Hash {
hash: hashSet,
hash: hash_set,
},
Inspect {
toInspector: toInspectorSet,
to_inspector: to_inspector_set,
},
]
isEq : Set k, Set k -> Bool
isEq = \xs, ys ->
if len xs != len ys then
is_eq : Set k, Set k -> Bool
is_eq = \xs, ys ->
if len(xs) != len(ys) then
Bool.false
else
walkUntil xs Bool.true \_, elem ->
if contains ys elem then
Continue Bool.true
else
Break Bool.false
walk_until(
xs,
Bool.true,
\_, elem ->
if contains(ys, elem) then
Continue(Bool.true)
else
Break(Bool.false),
)
hashSet : hasher, Set k -> hasher where hasher implements Hasher
hashSet = \hasher, @Set inner -> Hash.hash hasher inner
hash_set : hasher, Set k -> hasher where hasher implements Hasher
hash_set = \hasher, @Set(inner) -> Hash.hash(hasher, inner)
toInspectorSet : Set k -> Inspector f where k implements Inspect & Hash & Eq, f implements InspectFormatter
toInspectorSet = \set ->
Inspect.custom \fmt ->
Inspect.apply (Inspect.set set walk Inspect.toInspector) fmt
to_inspector_set : Set k -> Inspector f where k implements Inspect & Hash & Eq, f implements InspectFormatter
to_inspector_set = \set ->
Inspect.custom(
\fmt ->
Inspect.apply(Inspect.set(set, walk, Inspect.to_inspector), fmt),
)
## Creates a new empty `Set`.
## ```roc
## emptySet = Set.empty {}
## countValues = Set.len emptySet
## empty_set = Set.empty({})
## count_values = Set.len(empty_set)
##
## expect countValues == 0
## expect count_values == 0
## ```
empty : {} -> Set *
empty = \{} -> @Set (Dict.empty {})
empty = \{} -> @Set(Dict.empty({}))
## Return a set with space allocated for a number of entries. This
## may provide a performance optimization if you know how many entries will be
## inserted.
withCapacity : U64 -> Set *
withCapacity = \cap ->
@Set (Dict.withCapacity cap)
with_capacity : U64 -> Set *
with_capacity = \cap ->
@Set(Dict.with_capacity(cap))
## Enlarge the set for at least capacity additional elements
reserve : Set k, U64 -> Set k
reserve = \@Set dict, requested ->
@Set (Dict.reserve dict requested)
reserve = \@Set(dict), requested ->
@Set(Dict.reserve(dict, requested))
## Shrink the memory footprint of a set such that capacity is as small as possible.
## This function will require regenerating the metadata if the size changes.
## There will still be some overhead due to dictionary metadata always being a power of 2.
releaseExcessCapacity : Set k -> Set k
releaseExcessCapacity = \@Set dict ->
@Set (Dict.releaseExcessCapacity dict)
release_excess_capacity : Set k -> Set k
release_excess_capacity = \@Set(dict) ->
@Set(Dict.release_excess_capacity(dict))
## Creates a new `Set` with a single value.
## ```roc
## singleItemSet = Set.single "Apple"
## countValues = Set.len singleItemSet
## single_item_set = Set.single("Apple")
## count_values = Set.len(single_item_set)
##
## expect countValues == 1
## expect count_values == 1
## ```
single : k -> Set k
single = \key ->
Dict.single key {} |> @Set
Dict.single(key, {}) |> @Set
## Insert a value into a `Set`.
## ```roc
## fewItemSet =
## Set.empty {}
## |> Set.insert "Apple"
## |> Set.insert "Pear"
## |> Set.insert "Banana"
## few_item_set =
## Set.empty({})
## |> Set.insert("Apple")
## |> Set.insert("Pear")
## |> Set.insert("Banana")
##
## countValues = Set.len fewItemSet
## count_values = Set.len(few_item_set)
##
## expect countValues == 3
## expect count_values == 3
## ```
insert : Set k, k -> Set k
insert = \@Set dict, key ->
Dict.insert dict key {} |> @Set
insert = \@Set(dict), key ->
Dict.insert(dict, key, {}) |> @Set
# Inserting a duplicate key has no effect.
expect
actual =
empty {}
|> insert "foo"
|> insert "bar"
|> insert "foo"
|> insert "baz"
empty({})
|> insert("foo")
|> insert("bar")
|> insert("foo")
|> insert("baz")
expected =
empty {}
|> insert "foo"
|> insert "bar"
|> insert "baz"
empty({})
|> insert("foo")
|> insert("bar")
|> insert("baz")
expected == actual
## Counts the number of values in a given `Set`.
## ```roc
## fewItemSet =
## Set.empty {}
## |> Set.insert "Apple"
## |> Set.insert "Pear"
## |> Set.insert "Banana"
## few_item_set =
## Set.empty({})
## |> Set.insert("Apple")
## |> Set.insert("Pear")
## |> Set.insert("Banana")
##
## countValues = Set.len fewItemSet
## count_values = Set.len(few_item_set)
##
## expect countValues == 3
## expect count_values == 3
## ```
len : Set * -> U64
len = \@Set dict ->
Dict.len dict
len = \@Set(dict) ->
Dict.len(dict)
## Returns the max number of elements the set can hold before requiring a rehash.
## ```roc
## foodSet =
## Set.empty {}
## |> Set.insert "apple"
## food_set =
## Set.empty({})
## |> Set.insert("apple")
##
## capacityOfSet = Set.capacity foodSet
## capacity_of_set = Set.capacity(food_set)
## ```
capacity : Set * -> U64
capacity = \@Set dict ->
Dict.capacity dict
capacity = \@Set(dict) ->
Dict.capacity(dict)
## Check if the set is empty.
## ```roc
## Set.isEmpty (Set.empty {} |> Set.insert 42)
## Set.is_empty(Set.empty({}) |> Set.insert(42))
##
## Set.isEmpty (Set.empty {})
## Set.is_empty(Set.empty({}))
## ```
isEmpty : Set * -> Bool
isEmpty = \@Set dict ->
Dict.isEmpty dict
is_empty : Set * -> Bool
is_empty = \@Set(dict) ->
Dict.is_empty(dict)
# Inserting a duplicate key has no effect on length.
expect
actual =
empty {}
|> insert "foo"
|> insert "bar"
|> insert "foo"
|> insert "baz"
empty({})
|> insert("foo")
|> insert("bar")
|> insert("foo")
|> insert("baz")
|> len
actual == 3
@ -191,20 +197,20 @@ expect
## Removes the value from the given `Set`.
## ```roc
## numbers =
## Set.empty {}
## |> Set.insert 10
## |> Set.insert 20
## |> Set.remove 10
## Set.empty({})
## |> Set.insert(10)
## |> Set.insert(20)
## |> Set.remove(10)
##
## has10 = Set.contains numbers 10
## has20 = Set.contains numbers 20
## has10 = Set.contains(numbers, 10)
## has20 = Set.contains(numbers, 20)
##
## expect has10 == Bool.false
## expect has20 == Bool.true
## ```
remove : Set k, k -> Set k
remove = \@Set dict, key ->
Dict.remove dict key |> @Set
remove = \@Set(dict), key ->
Dict.remove(dict, key) |> @Set
## Test if a value is in the `Set`.
## ```roc
@ -212,47 +218,47 @@ remove = \@Set dict, key ->
##
## fruit : Set Fruit
## fruit =
## Set.single Apple
## |> Set.insert Pear
## Set.single(Apple)
## |> Set.insert(Pear)
##
## hasApple = Set.contains fruit Apple
## hasBanana = Set.contains fruit Banana
## has_apple = Set.contains(fruit, Apple)
## has_banana = Set.contains(fruit, Banana)
##
## expect hasApple == Bool.true
## expect hasBanana == Bool.false
## expect has_apple == Bool.true
## expect has_banana == Bool.false
## ```
contains : Set k, k -> Bool
contains = \@Set dict, key ->
Dict.contains dict key
contains = \@Set(dict), key ->
Dict.contains(dict, key)
## Retrieve the values in a `Set` as a `List`.
## ```roc
## numbers : Set U64
## numbers = Set.fromList [1,2,3,4,5]
## numbers = Set.from_list([1,2,3,4,5])
##
## values = [1,2,3,4,5]
##
## expect Set.toList numbers == values
## expect Set.to_list(numbers) == values
## ```
toList : Set k -> List k
toList = \@Set dict ->
Dict.keys dict
to_list : Set k -> List k
to_list = \@Set(dict) ->
Dict.keys(dict)
## Create a `Set` from a `List` of values.
## ```roc
## values =
## Set.empty {}
## |> Set.insert Banana
## |> Set.insert Apple
## |> Set.insert Pear
## Set.empty({})
## |> Set.insert(Banana)
## |> Set.insert(Apple)
## |> Set.insert(Pear)
##
## expect Set.fromList [Pear, Apple, Banana] == values
## expect Set.from_list([Pear, Apple, Banana]) == values
## ```
fromList : List k -> Set k
fromList = \list ->
from_list : List k -> Set k
from_list = \list ->
list
|> List.map \k -> (k, {})
|> Dict.fromList
|> List.map(\k -> (k, {}))
|> Dict.from_list
|> @Set
## Combine two `Set` collection by keeping the
@ -260,234 +266,242 @@ fromList = \list ->
## of all the values pairs. This means that all of the values in both `Set`s
## will be combined.
## ```roc
## set1 = Set.single Left
## set2 = Set.single Right
## set1 = Set.single(Left)
## set2 = Set.single(Right)
##
## expect Set.union set1 set2 == Set.fromList [Left, Right]
## expect Set.union(set1, set2) == Set.from_list([Left, Right])
## ```
union : Set k, Set k -> Set k
union = \@Set dict1, @Set dict2 ->
Dict.insertAll dict1 dict2 |> @Set
union = \@Set(dict1), @Set(dict2) ->
Dict.insert_all(dict1, dict2) |> @Set
## Combine two `Set`s by keeping the [intersection](https://en.wikipedia.org/wiki/Intersection_(set_theory))
## of all the values pairs. This means that we keep only those values that are
## in both `Set`s.
## ```roc
## set1 = Set.fromList [Left, Other]
## set2 = Set.fromList [Left, Right]
## set1 = Set.from_list([Left, Other])
## set2 = Set.from_list([Left, Right])
##
## expect Set.intersection set1 set2 == Set.single Left
## expect Set.intersection(set1, set2) == Set.single(Left)
## ```
intersection : Set k, Set k -> Set k
intersection = \@Set dict1, @Set dict2 ->
Dict.keepShared dict1 dict2 |> @Set
intersection = \@Set(dict1), @Set(dict2) ->
Dict.keep_shared(dict1, dict2) |> @Set
## Remove the values in the first `Set` that are also in the second `Set`
## using the [set difference](https://en.wikipedia.org/wiki/Complement_(set_theory)#Relative_complement)
## of the values. This means that we will be left with only those values that
## are in the first and not in the second.
## ```roc
## first = Set.fromList [Left, Right, Up, Down]
## second = Set.fromList [Left, Right]
## first = Set.from_list([Left, Right, Up, Down])
## second = Set.from_list([Left, Right])
##
## expect Set.difference first second == Set.fromList [Up, Down]
## expect Set.difference(first, second) == Set.from_list([Up, Down])
## ```
difference : Set k, Set k -> Set k
difference = \@Set dict1, @Set dict2 ->
Dict.removeAll dict1 dict2 |> @Set
difference = \@Set(dict1), @Set(dict2) ->
Dict.remove_all(dict1, dict2) |> @Set
## Iterate through the values of a given `Set` and build a value.
## ```roc
## values = Set.fromList ["March", "April", "May"]
## values = Set.from_list(["March", "April", "May"])
##
## startsWithLetterM = \month ->
## when Str.toUtf8 month is
## starts_with_letter_m = \month ->
## when Str.to_utf8(month) is
## ['M', ..] -> Bool.true
## _ -> Bool.false
##
## reduce = \state, k ->
## if startsWithLetterM k then
## if starts_with_letter_m(k) then
## state + 1
## else
## state
##
## result = Set.walk values 0 reduce
## result = Set.walk(values, 0, reduce)
##
## expect result == 2
## ```
walk : Set k, state, (state, k -> state) -> state
walk = \@Set dict, state, step ->
Dict.walk dict state (\s, k, _ -> step s k)
walk = \@Set(dict), state, step ->
Dict.walk(dict, state, \s, k, _ -> step(s, k))
## Convert each value in the set to something new, by calling a conversion
## function on each of them which receives the old value. Then return a
## new set containing the converted values.
map : Set a, (a -> b) -> Set b
map = \set, transform ->
init = withCapacity (capacity set)
init = with_capacity(capacity(set))
walk set init \answer, k ->
insert answer (transform k)
walk(
set,
init,
\answer, k ->
insert(answer, transform(k)),
)
## Like [Set.map], except the transformation function wraps the return value
## in a set. At the end, all the sets get joined together
## (using [Set.union]) into one set.
##
## You may know a similar function named `concatMap` in other languages.
joinMap : Set a, (a -> Set b) -> Set b
joinMap = \set, transform ->
init = withCapacity (capacity set) # Might be a pessimization
## You may know a similar function named `concat_map` in other languages.
join_map : Set a, (a -> Set b) -> Set b
join_map = \set, transform ->
init = with_capacity(capacity(set)) # Might be a pessimization
walk set init \answer, k ->
union answer (transform k)
walk(
set,
init,
\answer, k ->
union(answer, transform(k)),
)
## Iterate through the values of a given `Set` and build a value, can stop
## iterating part way through the collection.
## ```roc
## numbers = Set.fromList [1,2,3,4,5,6,42,7,8,9,10]
## numbers = Set.from_list([1,2,3,4,5,6,42,7,8,9,10])
##
## find42 = \state, k ->
## if k == 42 then
## Break FoundTheAnswer
## Break(FoundTheAnswer)
## else
## Continue state
## Continue(state)
##
## result = Set.walkUntil numbers NotFound find42
## result = Set.walk_until(numbers, NotFound, find42)
##
## expect result == FoundTheAnswer
## ```
walkUntil : Set k, state, (state, k -> [Continue state, Break state]) -> state
walkUntil = \@Set dict, state, step ->
Dict.walkUntil dict state (\s, k, _ -> step s k)
walk_until : Set k, state, (state, k -> [Continue state, Break state]) -> state
walk_until = \@Set(dict), state, step ->
Dict.walk_until(dict, state, \s, k, _ -> step(s, k))
## Run the given function on each element in the `Set`, and return
## a `Set` with just the elements for which the function returned `Bool.true`.
## ```roc
## expect Set.fromList [1,2,3,4,5]
## |> Set.keepIf \k -> k >= 3
## |> Bool.isEq (Set.fromList [3,4,5])
## expect Set.from_list([1,2,3,4,5])
## |> Set.keep_if(\k -> k >= 3)
## |> Bool.is_eq(Set.from_list([3,4,5]))
## ```
keepIf : Set k, (k -> Bool) -> Set k
keepIf = \@Set dict, predicate ->
@Set (Dict.keepIf dict (\(k, _v) -> predicate k))
keep_if : Set k, (k -> Bool) -> Set k
keep_if = \@Set(dict), predicate ->
@Set(Dict.keep_if(dict, \(k, _v) -> predicate(k)))
## Run the given function on each element in the `Set`, and return
## a `Set` with just the elements for which the function returned `Bool.false`.
## ```roc
## expect Set.fromList [1,2,3,4,5]
## |> Set.dropIf \k -> k >= 3
## |> Bool.isEq (Set.fromList [1,2])
## expect Set.from_list [1,2,3,4,5]
## |> Set.drop_if(\k -> k >= 3)
## |> Bool.is_eq(Set.from_list([1,2]))
## ```
dropIf : Set k, (k -> Bool) -> Set k
dropIf = \@Set dict, predicate ->
@Set (Dict.dropIf dict (\(k, _v) -> predicate k))
drop_if : Set k, (k -> Bool) -> Set k
drop_if = \@Set(dict), predicate ->
@Set(Dict.drop_if(dict, \(k, _v) -> predicate(k)))
expect
first =
single "Keep Me"
|> insert "And Me"
|> insert "Remove Me"
single("Keep Me")
|> insert("And Me")
|> insert("Remove Me")
second =
single "Remove Me"
|> insert "I do nothing..."
single("Remove Me")
|> insert("I do nothing...")
expected =
single "Keep Me"
|> insert "And Me"
single("Keep Me")
|> insert("And Me")
difference first second == expected
difference(first, second) == expected
expect
first =
single "Keep Me"
|> insert "And Me"
|> insert "Remove Me"
single("Keep Me")
|> insert("And Me")
|> insert("Remove Me")
second =
single "Remove Me"
|> insert "I do nothing..."
single("Remove Me")
|> insert("I do nothing...")
expected =
single "Keep Me"
|> insert "And Me"
single("Keep Me")
|> insert("And Me")
difference first second == expected
difference(first, second) == expected
expect
first =
single 1
|> insert 2
single(1)
|> insert(2)
second =
single 1
|> insert 3
|> insert 4
single(1)
|> insert(3)
|> insert(4)
expected =
single 1
|> insert 2
|> insert 3
|> insert 4
single(1)
|> insert(2)
|> insert(3)
|> insert(4)
union first second == expected
union(first, second) == expected
expect
base =
single "Remove Me"
|> insert "Keep Me"
|> insert "And Me"
single("Remove Me")
|> insert("Keep Me")
|> insert("And Me")
expected =
single "Keep Me"
|> insert "And Me"
single("Keep Me")
|> insert("And Me")
remove base "Remove Me" == expected
remove(base, "Remove Me") == expected
expect
x =
single 0
|> insert 1
|> insert 2
|> insert 3
|> insert 4
|> insert 5
|> insert 6
|> insert 7
|> insert 8
|> insert 9
single(0)
|> insert(1)
|> insert(2)
|> insert(3)
|> insert(4)
|> insert(5)
|> insert(6)
|> insert(7)
|> insert(8)
|> insert(9)
x == fromList (toList x)
x == from_list(to_list(x))
expect
orderOne : Set U64
orderOne =
single 1
|> insert 2
order_one : Set U64
order_one =
single(1)
|> insert(2)
orderTwo : Set U64
orderTwo =
single 2
|> insert 1
order_two : Set U64
order_two =
single(2)
|> insert(1)
wrapperOne : Set (Set U64)
wrapperOne =
single orderOne
|> insert orderTwo
wrapper_one : Set (Set U64)
wrapper_one =
single(order_one)
|> insert(order_two)
wrapperTwo : Set (Set U64)
wrapperTwo =
single orderTwo
|> insert orderOne
wrapper_two : Set (Set U64)
wrapper_two =
single(order_two)
|> insert(order_one)
wrapperOne == wrapperTwo
wrapper_one == wrapper_two
expect
Set.fromList [1, 2, 3, 4, 5]
|> Set.keepIf \k -> k >= 3
|> Bool.isEq (Set.fromList [3, 4, 5])
Set.from_list([1, 2, 3, 4, 5])
|> Set.keep_if(\k -> k >= 3)
|> Bool.is_eq(Set.from_list([3, 4, 5]))
expect
Set.fromList [1, 2, 3, 4, 5]
|> Set.dropIf \k -> k >= 3
|> Bool.isEq (Set.fromList [1, 2])
Set.from_list([1, 2, 3, 4, 5])
|> Set.drop_if(\k -> k >= 3)
|> Bool.is_eq(Set.from_list([1, 2]))

File diff suppressed because it is too large Load diff

View file

@ -1,275 +0,0 @@
module [
Task,
ok,
err,
await,
map,
mapErr,
onErr,
attempt,
forever,
loop,
fromResult,
batch,
combine,
sequence,
forEach,
result,
]
import List
import Result exposing [Result]
## A Task represents an effect; an interaction with state outside your Roc
## program, such as the terminal's standard output, or a file.
Task ok err := {} -> Result ok err
## Run a task repeatedly, until it fails with `err`. Note that this task does not return a success value.
forever : Task a err -> Task * err
forever = \@Task task ->
looper = \{} ->
when task {} is
Err e -> Err e
Ok _ -> looper {}
@Task \{} -> looper {}
## Run a task repeatedly, until it fails with `err` or completes with `done`.
##
## ```
## sum =
## Task.loop! 0 \total ->
## numResult =
## Stdin.line
## |> Task.result!
## |> Result.try Str.toU64
##
## when numResult is
## Ok num -> Task.ok (Step (total + num))
## Err (StdinErr EndOfFile) -> Task.ok (Done total)
## Err InvalidNumStr -> Task.err NonNumberGiven
## ```
loop : state, (state -> Task [Step state, Done done] err) -> Task done err
loop = \state, step ->
looper = \current ->
(@Task next) = step current
when next {} is
Err e -> Err e
Ok (Done newResult) -> Ok newResult
Ok (Step newState) -> looper (newState)
@Task \{} -> looper state
## Create a task that always succeeds with the value provided.
##
## ```
## # Always succeeds with "Louis"
## getName : Task.Task Str *
## getName = Task.ok "Louis"
## ```
##
ok : a -> Task a *
ok = \a -> @Task \{} -> Ok a
## Create a task that always fails with the error provided.
##
## ```
## # Always fails with the tag `CustomError Str`
## customError : Str -> Task.Task {} [CustomError Str]
## customError = \err -> Task.err (CustomError err)
## ```
##
err : a -> Task * a
err = \a -> @Task \{} -> Err a
## Transform a given Task with a function that handles the success or error case
## and returns another task based on that. This is useful for chaining tasks
## together or performing error handling and recovery.
##
## Consider the following task:
##
## `canFail : Task {} [Failure, AnotherFail, YetAnotherFail]`
##
## We can use [attempt] to handle the failure cases using the following:
##
## ```
## Task.attempt canFail \result ->
## when result is
## Ok Success -> Stdout.line "Success!"
## Err Failure -> Stdout.line "Oops, failed!"
## Err AnotherFail -> Stdout.line "Ooooops, another failure!"
## Err YetAnotherFail -> Stdout.line "Really big oooooops, yet again!"
## ```
##
## Here we know that the `canFail` task may fail, and so we use
## `Task.attempt` to convert the task to a `Result` and then use pattern
## matching to handle the success and possible failure cases.
attempt : Task a b, (Result a b -> Task c d) -> Task c d
attempt = \@Task task, transform ->
@Task \{} ->
(@Task transformed) = transform (task {})
transformed {}
## Take the success value from a given [Task] and use that to generate a new [Task].
##
## We can [await] Task results with callbacks:
##
## ```
## Task.await (Stdin.line "What's your name?") \name ->
## Stdout.line "Your name is: $(name)"
## ```
##
## Or we can more succinctly use the `!` bang operator, which desugars to [await]:
##
## ```
## name = Stdin.line! "What's your name?"
## Stdout.line "Your name is: $(name)"
## ```
await : Task a b, (a -> Task c b) -> Task c b
await = \@Task task, transform ->
@Task \{} ->
when task {} is
Ok a ->
(@Task transformed) = transform a
transformed {}
Err b ->
Err b
## Take the error value from a given [Task] and use that to generate a new [Task].
##
## ```
## # Prints "Something went wrong!" to standard error if `canFail` fails.
## canFail
## |> Task.onErr \_ -> Stderr.line "Something went wrong!"
## ```
onErr : Task a b, (b -> Task a c) -> Task a c
onErr = \@Task task, transform ->
@Task \{} ->
when task {} is
Ok a ->
Ok a
Err b ->
(@Task transformed) = transform b
transformed {}
## Transform the success value of a given [Task] with a given function.
##
## ```
## # Succeeds with a value of "Bonjour Louis!"
## Task.ok "Louis"
## |> Task.map (\name -> "Bonjour $(name)!")
## ```
map : Task a c, (a -> b) -> Task b c
map = \@Task task, transform ->
@Task \{} ->
when task {} is
Ok a -> Ok (transform a)
Err b -> Err b
## Transform the error value of a given [Task] with a given function.
##
## ```
## # Ignore the fail value, and map it to the tag `CustomError`
## canFail
## |> Task.mapErr \_ -> CustomError
## ```
mapErr : Task c a, (a -> b) -> Task c b
mapErr = \@Task task, transform ->
@Task \{} ->
when task {} is
Ok a -> Ok a
Err b -> Err (transform b)
## Use a Result among other Tasks by converting it into a [Task].
fromResult : Result a b -> Task a b
fromResult = \res ->
@Task \{} -> res
## Apply a task to another task applicatively.
##
## DEPRECATED: Modern record builders use [combine].
batch : Task a c -> (Task (a -> b) c -> Task b c)
batch = \current ->
\next ->
await next \f ->
map current f
## Combine the values of two tasks with a custom combining function.
##
## This is primarily used with record builders.
##
## ```
## { a, b, c } =
## { Task.combine <-
## a: Task.ok 123,
## b: File.read "file.txt",
## c: Http.get "http://api.com/",
## }!
## ```
combine : Task a err, Task b err, (a, b -> c) -> Task c err
combine = \@Task leftTask, @Task rightTask, combiner ->
@Task \{} ->
left = try leftTask {}
right = try rightTask {}
Ok (combiner left right)
## Apply each task in a list sequentially, and return a list of the resulting values.
## Each task will be awaited before beginning the next task.
##
## ```
## fetchAuthorTasks : List (Task Author [DbError])
##
## getAuthors : Task (List Author) [DbError]
## getAuthors = Task.sequence fetchAuthorTasks
## ```
##
sequence : List (Task ok err) -> Task (List ok) err
sequence = \taskList ->
Task.loop (taskList, List.withCapacity (List.len taskList)) \(tasks, values) ->
when tasks is
[task, .. as rest] ->
Task.map task \value ->
Step (rest, List.append values value)
[] ->
Task.ok (Done values)
## Apply a task repeatedly for each item in a list
##
## ```
## authors : List Author
## saveAuthor : Author -> Task {} [DbError]
##
## saveAuthors : Task (List Author) [DbError]
## saveAuthors = Task.forEach authors saveAuthor
## ```
##
forEach : List a, (a -> Task {} b) -> Task {} b
forEach = \items, fn ->
List.walk items (ok {}) \state, item ->
state |> await \_ -> fn item
## Transform a task that can either succeed with `ok`, or fail with `err`, into
## a task that succeeds with `Result ok err`.
##
## This is useful when chaining tasks using the `!` suffix. For example:
##
## ```
## # Path.roc
## checkFile : Str -> Task [Good, Bad] [IOError]
##
## # main.roc
## when checkFile "/usr/local/bin/roc" |> Task.result! is
## Ok Good -> "..."
## Ok Bad -> "..."
## Err IOError -> "..."
## ```
##
result : Task ok err -> Task (Result ok err) *
result = \@Task task ->
@Task \{} ->
Ok (task {})

View file

@ -11,5 +11,4 @@ package [
Hash,
Box,
Inspect,
Task,
] {}

View file

@ -16,7 +16,6 @@ pub fn module_source(module_id: ModuleId) -> &'static str {
ModuleId::DECODE => DECODE,
ModuleId::HASH => HASH,
ModuleId::INSPECT => INSPECT,
ModuleId::TASK => TASK,
_ => internal_error!(
"ModuleId {:?} is not part of the standard library",
module_id
@ -36,4 +35,3 @@ const ENCODE: &str = include_str!("../roc/Encode.roc");
const DECODE: &str = include_str!("../roc/Decode.roc");
const HASH: &str = include_str!("../roc/Hash.roc");
const INSPECT: &str = include_str!("../roc/Inspect.roc");
const TASK: &str = include_str!("../roc/Task.roc");