Merge remote-tracking branch 'remote/main' into str-dropping

This commit is contained in:
Luke Boswell 2024-09-29 14:31:04 +10:00
commit 760af4a04e
No known key found for this signature in database
GPG key ID: F6DB3C9DB47377B0
243 changed files with 7592 additions and 6576 deletions

View file

@ -618,6 +618,16 @@ pub fn listDropAt(
) callconv(.C) RocList {
const size = list.len();
const size_u64 = @as(u64, @intCast(size));
// NOTE
// we need to return an empty list explicitly,
// because we rely on the pointer field being null if the list is empty
// which also requires duplicating the utils.decref call to spend the RC token
if (size <= 1) {
list.decref(alignment, element_width, elements_refcounted, dec);
return RocList.empty();
}
// If droping the first or last element, return a seamless slice.
// For simplicity, do this by calling listSublist.
// In the future, we can test if it is faster to manually inline the important parts here.
@ -638,25 +648,16 @@ pub fn listDropAt(
// were >= than `size`, and we know `size` fits in usize.
const drop_index: usize = @intCast(drop_index_u64);
if (elements_refcounted) {
const element = source_ptr + drop_index * element_width;
dec(element);
}
// NOTE
// we need to return an empty list explicitly,
// because we rely on the pointer field being null if the list is empty
// which also requires duplicating the utils.decref call to spend the RC token
if (size < 2) {
list.decref(alignment, element_width, elements_refcounted, dec);
return RocList.empty();
}
if (list.isUnique()) {
var i = drop_index;
const copy_target = source_ptr;
if (elements_refcounted) {
const element = source_ptr + drop_index * element_width;
dec(element);
}
const copy_target = source_ptr + (drop_index * element_width);
const copy_source = copy_target + element_width;
std.mem.copyForwards(u8, copy_target[i..size], copy_source[i..size]);
const copy_size = (size - drop_index - 1) * element_width;
std.mem.copyForwards(u8, copy_target[0..copy_size], copy_source[0..copy_size]);
var new_list = list;

View file

@ -129,8 +129,8 @@ hashDict = \hasher, dict -> Hash.hashUnordered hasher (toList dict) List.walk
toInspectorDict : Dict k v -> Inspector f where k implements Inspect & Hash & Eq, v implements Inspect, f implements InspectFormatter
toInspectorDict = \dict ->
fmt <- Inspect.custom
Inspect.apply (Inspect.dict dict walk Inspect.toInspector Inspect.toInspector) fmt
Inspect.custom \fmt ->
Inspect.apply (Inspect.dict dict walk Inspect.toInspector Inspect.toInspector) fmt
## Return an empty dictionary.
## ```roc
@ -509,33 +509,33 @@ removeHelper = \buckets, bucketIndex, distAndFingerprint, data, key ->
## is missing. This is more efficient than doing both a `Dict.get` and then a
## `Dict.insert` call, and supports being piped.
## ```roc
## alterValue : [Present Bool, Missing] -> [Present Bool, Missing]
## alterValue : Result Bool [Missing] -> Result Bool [Missing]
## alterValue = \possibleValue ->
## when possibleValue is
## Missing -> Present Bool.false
## Present value -> if value then Missing else Present Bool.true
## Err -> Ok Bool.false
## Ok value -> if value then Err Missing else Ok Bool.true
##
## expect Dict.update (Dict.empty {}) "a" alterValue == Dict.single "a" Bool.false
## expect Dict.update (Dict.single "a" Bool.false) "a" alterValue == Dict.single "a" Bool.true
## expect Dict.update (Dict.single "a" Bool.true) "a" alterValue == Dict.empty {}
## ```
update : Dict k v, k, ([Present v, Missing] -> [Present v, Missing]) -> Dict k v
update : Dict k v, k, (Result v [Missing] -> Result v [Missing]) -> Dict k v
update = \@Dict { buckets, data, maxBucketCapacity, maxLoadFactor, shifts }, key, alter ->
{ bucketIndex, result } = find (@Dict { buckets, data, maxBucketCapacity, maxLoadFactor, shifts }) key
when result is
Ok value ->
when alter (Present value) is
Present newValue ->
when alter (Ok value) is
Ok newValue ->
bucket = listGetUnsafe buckets bucketIndex
newData = List.set data (Num.toU64 bucket.dataIndex) (key, newValue)
@Dict { buckets, data: newData, maxBucketCapacity, maxLoadFactor, shifts }
Missing ->
Err Missing ->
removeBucket (@Dict { buckets, data, maxBucketCapacity, maxLoadFactor, shifts }) bucketIndex
Err KeyNotFound ->
when alter Missing is
Present newValue ->
when alter (Err Missing) is
Ok newValue ->
if List.len data >= maxBucketCapacity then
# Need to reallocate let regular insert handle that.
insert (@Dict { buckets, data, maxBucketCapacity, maxLoadFactor, shifts }) key newValue
@ -556,7 +556,7 @@ update = \@Dict { buckets, data, maxBucketCapacity, maxLoadFactor, shifts }, key
distAndFingerprint = incrementDistN baseDistAndFingerprint (Num.toU32 dist)
insertHelper buckets data bucketIndex distAndFingerprint key newValue maxBucketCapacity maxLoadFactor shifts
Missing ->
Err Missing ->
@Dict { buckets, data, maxBucketCapacity, maxLoadFactor, shifts }
circularDist = \start, end, size ->
@ -894,9 +894,9 @@ calcNumBuckets = \shifts ->
maxBucketCount
fillBucketsFromData = \buckets0, data, shifts ->
buckets1, (key, _), dataIndex <- List.walkWithIndex data buckets0
(bucketIndex, distAndFingerprint) = nextWhileLess buckets1 key shifts
placeAndShiftUp buckets1 { distAndFingerprint, dataIndex: Num.toU32 dataIndex } bucketIndex
List.walkWithIndex data buckets0 \buckets1, (key, _), dataIndex ->
(bucketIndex, distAndFingerprint) = nextWhileLess buckets1 key shifts
placeAndShiftUp buckets1 { distAndFingerprint, dataIndex: Num.toU32 dataIndex } bucketIndex
nextWhileLess : List Bucket, k, U8 -> (U64, U32) where k implements Hash & Eq
nextWhileLess = \buckets, key, shifts ->
@ -1213,15 +1213,15 @@ expect
]
dict =
acc, k <- List.walk badKeys (Dict.empty {})
Dict.update acc k \val ->
when val is
Present p -> Present (p |> Num.addWrap 1)
Missing -> Present 0
List.walk badKeys (Dict.empty {}) \acc, k ->
Dict.update acc k \val ->
when val is
Ok p -> Ok (p |> Num.addWrap 1)
Err Missing -> Ok 0
allInsertedCorrectly =
acc, k <- List.walk badKeys Bool.true
acc && Dict.contains dict k
List.walk badKeys Bool.true \acc, k ->
acc && Dict.contains dict k
allInsertedCorrectly

View file

@ -138,203 +138,203 @@ dbgInit = \{} -> @DbgFormatter { data: "" }
dbgList : list, ElemWalker (DbgFormatter, Bool) list elem, (elem -> Inspector DbgFormatter) -> Inspector DbgFormatter
dbgList = \content, walkFn, toDbgInspector ->
f0 <- custom
dbgWrite f0 "["
|> \f1 ->
(f2, prependSep), elem <- walkFn content (f1, Bool.false)
f3 =
if prependSep then
dbgWrite f2 ", "
else
f2
custom \f0 ->
dbgWrite f0 "["
|> \f1 ->
walkFn content (f1, Bool.false) \(f2, prependSep), elem ->
f3 =
if prependSep then
dbgWrite f2 ", "
else
f2
elem
|> toDbgInspector
|> apply f3
|> \f4 -> (f4, Bool.true)
|> .0
|> dbgWrite "]"
elem
|> toDbgInspector
|> apply f3
|> \f4 -> (f4, Bool.true)
|> .0
|> dbgWrite "]"
dbgSet : set, ElemWalker (DbgFormatter, Bool) set elem, (elem -> Inspector DbgFormatter) -> Inspector DbgFormatter
dbgSet = \content, walkFn, toDbgInspector ->
f0 <- custom
dbgWrite f0 "{"
|> \f1 ->
(f2, prependSep), elem <- walkFn content (f1, Bool.false)
f3 =
if prependSep then
dbgWrite f2 ", "
else
f2
custom \f0 ->
dbgWrite f0 "{"
|> \f1 ->
walkFn content (f1, Bool.false) \(f2, prependSep), elem ->
f3 =
if prependSep then
dbgWrite f2 ", "
else
f2
elem
|> toDbgInspector
|> apply f3
|> \f4 -> (f4, Bool.true)
|> .0
|> dbgWrite "}"
elem
|> toDbgInspector
|> apply f3
|> \f4 -> (f4, Bool.true)
|> .0
|> dbgWrite "}"
dbgDict : dict, KeyValWalker (DbgFormatter, Bool) dict key value, (key -> Inspector DbgFormatter), (value -> Inspector DbgFormatter) -> Inspector DbgFormatter
dbgDict = \d, walkFn, keyToInspector, valueToInspector ->
f0 <- custom
dbgWrite f0 "{"
|> \f1 ->
(f2, prependSep), key, value <- walkFn d (f1, Bool.false)
f3 =
if prependSep then
dbgWrite f2 ", "
else
f2
custom \f0 ->
dbgWrite f0 "{"
|> \f1 ->
walkFn d (f1, Bool.false) \(f2, prependSep), key, value ->
f3 =
if prependSep then
dbgWrite f2 ", "
else
f2
apply (keyToInspector key) f3
|> dbgWrite ": "
|> \x -> apply (valueToInspector value) x
|> \f4 -> (f4, Bool.true)
|> .0
|> dbgWrite "}"
apply (keyToInspector key) f3
|> dbgWrite ": "
|> \x -> apply (valueToInspector value) x
|> \f4 -> (f4, Bool.true)
|> .0
|> dbgWrite "}"
dbgTag : Str, List (Inspector DbgFormatter) -> Inspector DbgFormatter
dbgTag = \name, fields ->
if List.isEmpty fields then
f0 <- custom
dbgWrite f0 name
custom \f0 ->
dbgWrite f0 name
else
f0 <- custom
dbgWrite f0 "("
|> dbgWrite name
|> \f1 ->
f2, inspector <- List.walk fields f1
dbgWrite f2 " "
|> \x -> apply inspector x
|> dbgWrite ")"
custom \f0 ->
dbgWrite f0 "("
|> dbgWrite name
|> \f1 ->
List.walk fields f1 \f2, inspector ->
dbgWrite f2 " "
|> \x -> apply inspector x
|> dbgWrite ")"
dbgTuple : List (Inspector DbgFormatter) -> Inspector DbgFormatter
dbgTuple = \fields ->
f0 <- custom
dbgWrite f0 "("
|> \f1 ->
(f2, prependSep), inspector <- List.walk fields (f1, Bool.false)
f3 =
if prependSep then
dbgWrite f2 ", "
else
f2
custom \f0 ->
dbgWrite f0 "("
|> \f1 ->
List.walk fields (f1, Bool.false) \(f2, prependSep), inspector ->
f3 =
if prependSep then
dbgWrite f2 ", "
else
f2
apply inspector f3
|> \f4 -> (f4, Bool.true)
|> .0
|> dbgWrite ")"
apply inspector f3
|> \f4 -> (f4, Bool.true)
|> .0
|> dbgWrite ")"
dbgRecord : List { key : Str, value : Inspector DbgFormatter } -> Inspector DbgFormatter
dbgRecord = \fields ->
f0 <- custom
dbgWrite f0 "{"
|> \f1 ->
(f2, prependSep), { key, value } <- List.walk fields (f1, Bool.false)
f3 =
if prependSep then
dbgWrite f2 ", "
else
f2
custom \f0 ->
dbgWrite f0 "{"
|> \f1 ->
List.walk fields (f1, Bool.false) \(f2, prependSep), { key, value } ->
f3 =
if prependSep then
dbgWrite f2 ", "
else
f2
dbgWrite f3 key
|> dbgWrite ": "
|> \x -> apply value x
|> \f4 -> (f4, Bool.true)
|> .0
|> dbgWrite "}"
dbgWrite f3 key
|> dbgWrite ": "
|> \x -> apply value x
|> \f4 -> (f4, Bool.true)
|> .0
|> dbgWrite "}"
dbgBool : Bool -> Inspector DbgFormatter
dbgBool = \b ->
if b then
f0 <- custom
dbgWrite f0 "Bool.true"
custom \f0 ->
dbgWrite f0 "Bool.true"
else
f0 <- custom
dbgWrite f0 "Bool.false"
custom \f0 ->
dbgWrite f0 "Bool.false"
dbgStr : Str -> Inspector DbgFormatter
dbgStr = \s ->
f0 <- custom
f0
|> dbgWrite "\""
|> dbgWrite s # TODO: Should we be escaping strings for dbg/logging?
|> dbgWrite "\""
custom \f0 ->
f0
|> dbgWrite "\""
|> dbgWrite s # TODO: Should we be escaping strings for dbg/logging?
|> dbgWrite "\""
dbgOpaque : * -> Inspector DbgFormatter
dbgOpaque = \_ ->
f0 <- custom
dbgWrite f0 "<opaque>"
custom \f0 ->
dbgWrite f0 "<opaque>"
dbgFunction : * -> Inspector DbgFormatter
dbgFunction = \_ ->
f0 <- custom
dbgWrite f0 "<function>"
custom \f0 ->
dbgWrite f0 "<function>"
dbgU8 : U8 -> Inspector DbgFormatter
dbgU8 = \num ->
f0 <- custom
dbgWrite f0 (num |> Num.toStr)
custom \f0 ->
dbgWrite f0 (num |> Num.toStr)
dbgI8 : I8 -> Inspector DbgFormatter
dbgI8 = \num ->
f0 <- custom
dbgWrite f0 (num |> Num.toStr)
custom \f0 ->
dbgWrite f0 (num |> Num.toStr)
dbgU16 : U16 -> Inspector DbgFormatter
dbgU16 = \num ->
f0 <- custom
dbgWrite f0 (num |> Num.toStr)
custom \f0 ->
dbgWrite f0 (num |> Num.toStr)
dbgI16 : I16 -> Inspector DbgFormatter
dbgI16 = \num ->
f0 <- custom
dbgWrite f0 (num |> Num.toStr)
custom \f0 ->
dbgWrite f0 (num |> Num.toStr)
dbgU32 : U32 -> Inspector DbgFormatter
dbgU32 = \num ->
f0 <- custom
dbgWrite f0 (num |> Num.toStr)
custom \f0 ->
dbgWrite f0 (num |> Num.toStr)
dbgI32 : I32 -> Inspector DbgFormatter
dbgI32 = \num ->
f0 <- custom
dbgWrite f0 (num |> Num.toStr)
custom \f0 ->
dbgWrite f0 (num |> Num.toStr)
dbgU64 : U64 -> Inspector DbgFormatter
dbgU64 = \num ->
f0 <- custom
dbgWrite f0 (num |> Num.toStr)
custom \f0 ->
dbgWrite f0 (num |> Num.toStr)
dbgI64 : I64 -> Inspector DbgFormatter
dbgI64 = \num ->
f0 <- custom
dbgWrite f0 (num |> Num.toStr)
custom \f0 ->
dbgWrite f0 (num |> Num.toStr)
dbgU128 : U128 -> Inspector DbgFormatter
dbgU128 = \num ->
f0 <- custom
dbgWrite f0 (num |> Num.toStr)
custom \f0 ->
dbgWrite f0 (num |> Num.toStr)
dbgI128 : I128 -> Inspector DbgFormatter
dbgI128 = \num ->
f0 <- custom
dbgWrite f0 (num |> Num.toStr)
custom \f0 ->
dbgWrite f0 (num |> Num.toStr)
dbgF32 : F32 -> Inspector DbgFormatter
dbgF32 = \num ->
f0 <- custom
dbgWrite f0 (num |> Num.toStr)
custom \f0 ->
dbgWrite f0 (num |> Num.toStr)
dbgF64 : F64 -> Inspector DbgFormatter
dbgF64 = \num ->
f0 <- custom
dbgWrite f0 (num |> Num.toStr)
custom \f0 ->
dbgWrite f0 (num |> Num.toStr)
dbgDec : Dec -> Inspector DbgFormatter
dbgDec = \num ->
f0 <- custom
dbgWrite f0 (num |> Num.toStr)
custom \f0 ->
dbgWrite f0 (num |> Num.toStr)
dbgWrite : DbgFormatter, Str -> DbgFormatter
dbgWrite = \@DbgFormatter { data }, added ->

View file

@ -62,8 +62,8 @@ hashSet = \hasher, @Set inner -> Hash.hash hasher inner
toInspectorSet : Set k -> Inspector f where k implements Inspect & Hash & Eq, f implements InspectFormatter
toInspectorSet = \set ->
fmt <- Inspect.custom
Inspect.apply (Inspect.set set walk Inspect.toInspector) fmt
Inspect.custom \fmt ->
Inspect.apply (Inspect.set set walk Inspect.toInspector) fmt
## Creates a new empty `Set`.
## ```roc

View file

@ -0,0 +1,265 @@
module [
Task,
ok,
err,
await,
map,
mapErr,
onErr,
attempt,
forever,
loop,
fromResult,
batch,
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. This can be used with
## [ok] to build a [Task] that returns a record.
##
## The following example returns a Record with two fields, `apples` and
## `oranges`, each of which is a `List Str`. If it fails it returns the tag
## `NoFruitAvailable`.
##
## ```
## getFruitBasket : Task { apples : List Str, oranges : List Str } [NoFruitAvailable]
## getFruitBasket = Task.ok {
## apples: <- getFruit Apples |> Task.batch,
## oranges: <- getFruit Oranges |> Task.batch,
## }
## ```
batch : Task a c -> (Task (a -> b) c -> Task b c)
batch = \current ->
\next ->
await next \f ->
map current f
## 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] ->
value = task!
Task.ok (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,4 +11,5 @@ package [
Hash,
Box,
Inspect,
Task,
] {}

View file

@ -16,6 +16,7 @@ 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
@ -35,3 +36,4 @@ 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");