mirror of
https://github.com/roc-lang/roc.git
synced 2025-08-04 12:18:19 +00:00
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:
commit
ee3c71dfe6
766 changed files with 20515 additions and 34868 deletions
|
@ -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`.
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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.");
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
@ -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)
|
||||
|
|
|
@ -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
|
@ -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 {})
|
|
@ -11,5 +11,4 @@ package [
|
|||
Hash,
|
||||
Box,
|
||||
Inspect,
|
||||
Task,
|
||||
] {}
|
||||
|
|
|
@ -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");
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue