aboutsummaryrefslogtreecommitdiff
path: root/src/std
diff options
context:
space:
mode:
Diffstat (limited to 'src/std')
-rw-r--r--src/std/any.gleam57
-rw-r--r--src/std/atom.gleam17
-rw-r--r--src/std/bool.gleam38
-rw-r--r--src/std/expect.gleam20
-rw-r--r--src/std/float.gleam21
-rw-r--r--src/std/http.gleam13
-rw-r--r--src/std/int.gleam21
-rw-r--r--src/std/iodata.gleam58
-rw-r--r--src/std/list.gleam306
-rw-r--r--src/std/map_dict.gleam100
-rw-r--r--src/std/order.gleam48
-rw-r--r--src/std/result.gleam51
-rw-r--r--src/std/string.gleam36
-rw-r--r--src/std/tuple.gleam29
14 files changed, 815 insertions, 0 deletions
diff --git a/src/std/any.gleam b/src/std/any.gleam
new file mode 100644
index 0000000..c5c4a62
--- /dev/null
+++ b/src/std/any.gleam
@@ -0,0 +1,57 @@
+import std/list
+import std/atom
+import std/result
+
+fn list_module() {
+ list
+}
+
+// `Any` data is data that we don"t know the type of yet.
+// We likely get data like this from interop with Erlang, or from
+// IO with the outside world.
+//
+pub external type Any;
+
+// Convert any Gleam data into `Any` data.
+//
+pub external fn from(a) -> Any = "gleam__stdlib" "identity";
+
+// Unsafely cast any type into any other type.
+//
+// This is an escape hatch for the type system that may be useful when wrapping
+// native Erlang APIs. It is to be used as a last measure only.
+//
+pub external fn unsafe_coerce(a) -> b = "gleam__stdlib" "identity";
+
+pub external fn string(Any) -> Result(String, String)
+ = "gleam__stdlib" "decode_string"
+
+pub external fn int(Any) -> Result(Int, String)
+ = "gleam__stdlib" "decode_int"
+
+pub external fn float(Any) -> Result(Float, String)
+ = "gleam__stdlib" "decode_float"
+
+pub external fn atom(Any) -> Result(atom:Atom, String)
+ = "gleam__stdlib" "decode_atom"
+
+pub external fn bool(Any) -> Result(Bool, String)
+ = "gleam__stdlib" "decode_bool"
+
+pub external fn thunk(Any) -> Result(fn() -> Any, String)
+ = "gleam__stdlib" "decode_thunk"
+
+external fn list_any(Any) -> Result(List(Any), String) =
+ "gleam__stdlib" "decode_list"
+
+pub fn list(any, decode) {
+ any
+ |> list_any
+ |> result:then(_, list_module():traverse(_, decode))
+}
+
+pub external fn tuple(Any) -> Result({Any, Any}, String)
+ = "gleam__stdlib" "decode_tuple"
+
+pub external fn field(Any, a) -> Result(Any, String)
+ = "gleam__stdlib" "decode_field"
diff --git a/src/std/atom.gleam b/src/std/atom.gleam
new file mode 100644
index 0000000..67d040d
--- /dev/null
+++ b/src/std/atom.gleam
@@ -0,0 +1,17 @@
+pub external type Atom;
+
+pub enum AtomNotLoaded =
+ | AtomNotLoaded
+
+pub external fn from_string(String) -> Result(Atom, AtomNotLoaded) =
+ "gleam__stdlib" "atom_from_string";
+
+// This function can create a new atom if one does not already exist for
+// the given string. Atoms are not garbage collected so this can result
+// in a memory leak if called over time on new values
+//
+pub external fn create_from_string(String) -> Atom =
+ "gleam__stdlib" "atom_create_from_string";
+
+pub external fn to_string(Atom) -> String =
+ "gleam__stdlib" "atom_to_string";
diff --git a/src/std/bool.gleam b/src/std/bool.gleam
new file mode 100644
index 0000000..985f0d6
--- /dev/null
+++ b/src/std/bool.gleam
@@ -0,0 +1,38 @@
+import std/order
+
+pub fn negate(bool) {
+ case bool {
+ | True -> False
+ | False -> True
+ }
+}
+
+pub fn compare(a, b) {
+ case {a, b} {
+ | {True, True} -> order:Eq
+ | {True, False} -> order:Gt
+ | {False, False} -> order:Eq
+ | {False, True} -> order:Lt
+ }
+}
+
+pub fn max(a, b) {
+ case a {
+ | True -> True
+ | False -> b
+ }
+}
+
+pub fn min(a, b) {
+ case a {
+ | False -> False
+ | True -> b
+ }
+}
+
+pub fn to_int(bool) {
+ case bool {
+ | False -> 0
+ | True -> 1
+ }
+}
diff --git a/src/std/expect.gleam b/src/std/expect.gleam
new file mode 100644
index 0000000..5ea6a93
--- /dev/null
+++ b/src/std/expect.gleam
@@ -0,0 +1,20 @@
+// TODO: Move this module into another package so it can be used as a
+// dep only in test.
+
+pub external type Expectation;
+
+pub external fn equal(a, a) -> Expectation = "gleam__stdlib" "expect_equal";
+
+pub external fn not_equal(a, a) -> Expectation = "gleam__stdlib" "expect_not_equal";
+
+pub external fn true(Bool) -> Expectation = "gleam__stdlib" "expect_true";
+
+pub external fn false(Bool) -> Expectation = "gleam__stdlib" "expect_false";
+
+pub external fn is_ok(Result(a, b)) -> Expectation = "gleam__stdlib" "expect_is_ok";
+
+pub external fn is_error(Result(a, b)) -> Expectation = "gleam__stdlib" "expect_is_error";
+
+pub fn fail() {
+ true(False)
+}
diff --git a/src/std/float.gleam b/src/std/float.gleam
new file mode 100644
index 0000000..8e1904e
--- /dev/null
+++ b/src/std/float.gleam
@@ -0,0 +1,21 @@
+import std/iodata
+
+pub enum NotAFloat =
+ | NotAFloat
+
+pub external fn parse(String) -> Result(Float, NotAFloat) =
+ "gleam__stdlib" "parse_float";
+
+pub fn to_string(f) {
+ f
+ |> iodata:from_float
+ |> iodata:to_string
+}
+
+pub external fn ceiling(Float) -> Float = "math" "ceil";
+
+pub external fn floor(Float) -> Float = "math" "floor";
+
+pub external fn round(Float) -> Int = "erlang" "round";
+
+pub external fn truncate(Float) -> Int = "erlang" "trunc";
diff --git a/src/std/http.gleam b/src/std/http.gleam
new file mode 100644
index 0000000..d9942a8
--- /dev/null
+++ b/src/std/http.gleam
@@ -0,0 +1,13 @@
+// HTTP standard method as defined by RFC 2616, and PATCH which is defined by
+// RFC 5789.
+//
+pub enum Method =
+ | Get
+ | Post
+ | Head
+ | Put
+ | Delete
+ | Trace
+ | Connect
+ | Options
+ | Patch
diff --git a/src/std/int.gleam b/src/std/int.gleam
new file mode 100644
index 0000000..b1f8256
--- /dev/null
+++ b/src/std/int.gleam
@@ -0,0 +1,21 @@
+import std/order
+
+pub enum NotAnInt =
+ | NotAnInt
+
+pub external fn parse(String) -> Result(Int, NotAnInt) = "gleam__stdlib" "parse_int";
+
+pub external fn to_string(Int) -> String = "erlang" "integer_to_binary"
+
+pub external fn to_base_string(Int, Int) -> String = "erlang" "integer_to_binary"
+
+pub fn compare(a, b) {
+ case a == b {
+ | True -> order:Eq
+ | False ->
+ case a < b {
+ | True -> order:Lt
+ | False -> order:Gt
+ }
+ }
+}
diff --git a/src/std/iodata.gleam b/src/std/iodata.gleam
new file mode 100644
index 0000000..56efc65
--- /dev/null
+++ b/src/std/iodata.gleam
@@ -0,0 +1,58 @@
+pub external type Iodata;
+
+pub external fn prepend(Iodata, String) -> Iodata =
+ "gleam__stdlib" "iodata_prepend";
+
+pub external fn append(Iodata, String) -> Iodata =
+ "gleam__stdlib" "iodata_append";
+
+pub external fn prepend_iodata(Iodata, Iodata) -> Iodata =
+ "gleam__stdlib" "iodata_prepend";
+
+pub external fn append_iodata(Iodata, Iodata) -> Iodata =
+ "gleam__stdlib" "iodata_append";
+
+pub external fn from_strings(List(String)) -> Iodata =
+ "gleam__stdlib" "identity";
+
+pub external fn concat(List(Iodata)) -> Iodata =
+ "gleam__stdlib" "identity";
+
+pub external fn new(String) -> Iodata =
+ "gleam__stdlib" "identity";
+
+pub external fn to_string(Iodata) -> String =
+ "erlang" "iolist_to_binary";
+
+pub external fn byte_size(Iodata) -> Int =
+ "erlang" "iolist_size";
+
+pub external fn from_float(Float) -> Iodata =
+ "io_lib_format" "fwrite_g";
+
+pub external fn lowercase(Iodata) -> Iodata = "string" "lowercase"
+
+pub external fn uppercase(Iodata) -> Iodata = "string" "uppercase"
+
+pub external fn reverse(Iodata) -> Iodata = "string" "reverse"
+
+enum Direction =
+ | All
+
+external fn erl_split(Iodata, String, Direction) -> List(Iodata) =
+ "string" "split"
+
+pub fn split(iodata, on) {
+ erl_split(iodata, on, All)
+}
+
+external fn erl_replace(Iodata, String, String, Direction) -> Iodata =
+ "string" "replace"
+
+pub fn replace(iodata, pattern, replacement) {
+ erl_replace(iodata, pattern, replacement, All)
+}
+
+pub external fn is_equal(Iodata, Iodata) -> Bool = "string" "equal"
+
+pub external fn is_empty(Iodata) -> Bool = "string" "is_empty"
diff --git a/src/std/list.gleam b/src/std/list.gleam
new file mode 100644
index 0000000..68dcb4a
--- /dev/null
+++ b/src/std/list.gleam
@@ -0,0 +1,306 @@
+import std/int
+import std/order
+
+pub enum Empty =
+ | Empty
+
+pub enum NotFound =
+ | NotFound
+
+pub enum LengthMismatch =
+ | LengthMismatch
+
+// Using the Erlang C BIF implementation.
+//
+pub external fn length(List(a)) -> Int = "erlang" "length"
+
+// Using the Erlang C BIF implementation.
+//
+pub external fn reverse(List(a)) -> List(a) = "lists" "reverse"
+
+pub fn is_empty(list) {
+ list == []
+}
+
+pub fn contains(list, elem) {
+ case list {
+ | [] -> False
+ | [head | rest] -> head == elem || contains(rest, elem)
+ }
+}
+
+pub fn head(list) {
+ case list {
+ | [] -> Error(Empty)
+ | [x | _] -> Ok(x)
+ }
+}
+
+pub fn tail(list) {
+ case list {
+ | [] -> Error(Empty)
+ | [_ | xs] -> Ok(xs)
+ }
+}
+
+fn do_filter(list, fun, acc) {
+ case list {
+ | [] -> reverse(acc)
+ | [x | xs] ->
+ let new_acc =
+ case fun(x) {
+ | True -> [x | acc]
+ | False -> acc
+ }
+ do_filter(xs, fun, new_acc)
+ }
+}
+
+pub fn filter(list, fun) {
+ do_filter(list, fun, [])
+}
+
+fn do_map(list, fun, acc) {
+ case list {
+ | [] -> reverse(acc)
+ | [x | xs] -> do_map(xs, fun, [fun(x) | acc])
+ }
+}
+
+pub fn map(list, fun) {
+ do_map(list, fun, [])
+}
+
+fn do_index_map(list, fun, index, acc) {
+ case list {
+ | [] -> reverse(acc)
+ | [x | xs] -> do_index_map(xs, fun, index + 1, [fun(index, x) | acc])
+ }
+}
+
+pub fn index_map(list, fun) {
+ do_index_map(list, fun, 0, [])
+}
+
+fn do_traverse(list, fun, acc) {
+ case list {
+ | [] -> Ok(reverse(acc))
+ | [x | xs] ->
+ case fun(x) {
+ | Ok(y) -> do_traverse(xs, fun, [y | acc])
+ | Error(error) -> Error(error)
+ }
+ }
+}
+
+pub fn traverse(list, fun) {
+ do_traverse(list, fun, [])
+}
+
+pub fn drop(list, n) {
+ case n <= 0 {
+ | True -> list
+ | False ->
+ case list {
+ | [] -> []
+ | [_ | xs] -> drop(xs, n - 1)
+ }
+ }
+}
+
+fn do_take(list, n, acc) {
+ case n <= 0 {
+ | True -> reverse(acc)
+ | False ->
+ case list {
+ | [] -> reverse(acc)
+ | [x | xs] -> do_take(xs, n - 1, [x | acc])
+ }
+ }
+}
+
+pub fn take(list, n) {
+ do_take(list, n, [])
+}
+
+pub fn new() {
+ []
+}
+
+pub external fn append(List(a), List(a)) -> List(a) = "lists" "append";
+
+fn do_flatten(lists, acc) {
+ case lists {
+ | [] -> acc
+ | [l | rest] -> do_flatten(rest, append(acc, l))
+ }
+}
+
+pub fn flatten(lists) {
+ do_flatten(lists, [])
+}
+
+pub fn fold(list, acc, fun) {
+ case list {
+ | [] -> acc
+ | [x | rest] -> fold(rest, fun(x, acc), fun)
+ }
+}
+
+pub fn fold_right(list, acc, fun) {
+ case list {
+ | [] -> acc
+ | [x | rest] -> fun(x, fold_right(rest, acc, fun))
+ }
+}
+
+pub fn find(haystack, f) {
+ case haystack {
+ | [] -> Error(NotFound)
+ | [x | rest] ->
+ case f(x) {
+ | Ok(x) -> Ok(x)
+ | _ -> find(rest, f)
+ }
+ }
+}
+
+pub fn all(list, f) {
+ case list {
+ | [] -> True
+ | [x | rest] ->
+ case f(x) {
+ | True -> all(rest, f)
+ | _ -> False
+ }
+ }
+}
+
+pub fn any(list, f) {
+ case list {
+ | [] -> False
+ | [ x | rest] ->
+ case f(x) {
+ | False -> any(rest, f)
+ | _ -> True
+ }
+ }
+}
+
+pub fn zip(l1, l2) {
+ case {l1, l2} {
+ | {[], _} -> []
+ | {_, []} -> []
+ | {[x1 | rest1], [x2 | rest2] } -> [ {x1, x2} | zip(rest1, rest2) ]
+ }
+}
+
+pub fn strict_zip(l1, l2) {
+ case length(l1) == length(l2) {
+ | True -> Ok(zip(l1, l2))
+ | False -> Error(LengthMismatch)
+ }
+}
+
+pub fn intersperse(list, elem) {
+ case list {
+ | [] -> []
+ | [x | []] -> [x]
+ | [x | rest] -> [x | [elem | intersperse(rest, elem)]]
+ }
+}
+
+pub fn at(list, i) {
+ case i < 0 {
+ | True -> Error(NotFound)
+ | False ->
+ case list {
+ | [] -> Error(NotFound)
+ | [x | rest] ->
+ case i == 0 {
+ | True -> Ok(x)
+ | False -> at(rest, i - 1)
+ }
+ }
+ }
+}
+
+pub fn unique(list) {
+ case list {
+ | [] -> []
+ | [x | rest] -> [x | unique(filter(rest, fn(y) { y != x }))]
+ }
+}
+
+fn merge_sort(a, b) {
+ case {a, b} {
+ | {[], _} -> b
+ | {_, []} -> a
+ | {[ax | ar], [bx | br]} ->
+ case ax < bx {
+ | True -> [ax | merge_sort(ar, b)]
+ | False -> [bx | merge_sort(a, br)]
+ }
+ }
+}
+
+pub fn sort(list) {
+ let list_length = length(list)
+ case list_length < 2 {
+ | True -> list
+ | False ->
+ let split_length = list_length / 2
+ let a_list = take(list, split_length)
+ let b_list = drop(list, split_length)
+ merge_sort(sort(a_list), sort(b_list))
+ }
+}
+
+pub fn range(start, stop) {
+ case int:compare(start, stop) {
+ | order:Eq -> []
+ | order:Gt -> [start | range(start - 1, stop)]
+ | order:Lt -> [start | range(start + 1, stop)]
+ }
+}
+
+fn do_repeat(a, times, acc) {
+ case times <= 0 {
+ | True -> acc
+ | False -> do_repeat(a, times - 1, [a | acc])
+ }
+}
+
+pub fn repeat(a, times) {
+ do_repeat(a, times, [])
+}
+
+fn do_split(list, n, taken) {
+ case n <= 0 {
+ | True -> {reverse(taken), list}
+ | False ->
+ case list {
+ | [] -> {reverse(taken), []}
+ | [x | xs] -> do_split(xs, n - 1, [x | taken])
+ }
+ }
+}
+
+pub fn split(list, n) {
+ do_split(list, n, [])
+}
+
+fn do_split_while(list, f, acc) {
+ case list {
+ | [] -> {reverse(acc), []}
+ | [x | xs] ->
+ case f(x) {
+ | False -> {reverse(acc), list}
+ | _ -> do_split_while(xs, f, [x | acc])
+ }
+ }
+}
+
+pub fn split_while(list, f) {
+ do_split_while(list, f, [])
+}
diff --git a/src/std/map_dict.gleam b/src/std/map_dict.gleam
new file mode 100644
index 0000000..32065f5
--- /dev/null
+++ b/src/std/map_dict.gleam
@@ -0,0 +1,100 @@
+import std/any
+import std/result
+import std/list
+
+pub external type MapDict(key, value);
+
+pub enum NotFound =
+ | NotFound
+
+pub external fn size(MapDict(k, v)) -> Int
+ = "maps" "size"
+
+pub external fn to_list(MapDict(key, value)) -> List({key, value})
+ = "maps" "to_list"
+
+pub external fn from_list(List({key, value})) -> MapDict(key, value)
+ = "maps" "from_list"
+
+external fn is_key(key, MapDict(key, v)) -> Bool
+ = "maps" "is_key"
+
+pub fn has_key(map, key) {
+ is_key(key, map)
+}
+
+pub external fn new() -> MapDict(key, value)
+ = "maps" "new"
+
+pub external fn fetch(MapDict(key, value), key) -> Result(value, NotFound)
+ = "gleam__stdlib" "map_fetch";
+
+external fn erl_put(key, value, MapDict(key, value)) -> MapDict(key, value)
+ = "maps" "put";
+
+pub fn put(map, key, value) {
+ erl_put(key, value, map)
+}
+
+external fn erl_map_values(fn(key, value) -> value, MapDict(key, value))
+ -> MapDict(key, value)
+ = "maps" "map";
+
+pub fn map_values(map, fun) {
+ erl_map_values(fun, map)
+}
+
+pub external fn keys(MapDict(keys, v)) -> List(keys)
+ = "maps" "keys"
+
+pub external fn values(MapDict(k, values)) -> List(values)
+ = "maps" "values"
+
+external fn erl_filter(fn(key, value) -> Bool, MapDict(key, value))
+ -> MapDict(key, value)
+ = "maps" "filter";
+
+pub fn filter(map, fun) {
+ erl_filter(fun, map)
+}
+
+external fn erl_take(List(k), MapDict(k, v)) -> MapDict(k, v) = "maps" "with"
+
+pub fn take(map, keys) {
+ erl_take(keys, map)
+}
+
+pub external fn merge(MapDict(k, v), MapDict(k, v)) -> MapDict(k, v) = "maps" "merge"
+
+external fn erl_delete(k, MapDict(k, v)) -> MapDict(k, v) = "maps" "remove"
+
+pub fn delete(map, key) {
+ erl_delete(key, map)
+}
+
+pub fn drop(map, keys) {
+ list:fold(keys, map, fn(key, acc) {
+ delete(acc, key)
+ })
+}
+
+pub external type NotFound;
+
+pub fn update(dict, key, f) {
+ case fetch(dict, key) {
+ | Ok(value) -> put(dict, key, f(Ok(value)))
+ | Error(_) -> put(dict, key, f(Error(NotFound)))
+ }
+}
+
+fn do_fold(list, acc, f) {
+ case list {
+ | [] -> acc
+ | [{k, v} | tail] -> do_fold(tail, f(k, v, acc), f)
+ }
+}
+
+pub fn fold(dict, acc, f) {
+ let kvs = to_list(dict)
+ do_fold(kvs, acc, f)
+}
diff --git a/src/std/order.gleam b/src/std/order.gleam
new file mode 100644
index 0000000..4d39705
--- /dev/null
+++ b/src/std/order.gleam
@@ -0,0 +1,48 @@
+pub enum Order =
+ | Lt
+ | Eq
+ | Gt
+;
+
+pub fn reverse(order) {
+ case order {
+ | Lt -> Gt
+ | Eq -> Eq
+ | Gt -> Lt
+ }
+}
+
+pub fn to_int(order) {
+ case order {
+ | Lt -> -1
+ | Eq -> 0
+ | Gt -> 1
+ }
+}
+
+pub fn compare(a, b) {
+ case {a, b} {
+ | {Lt, Lt} -> Eq
+ | {Lt, _} -> Lt
+ | {Eq, Eq} -> Eq
+ | {Gt, Gt} -> Eq
+ | {Eq, Gt} -> Lt
+ | _ -> Gt
+ }
+}
+
+pub fn max(a, b) {
+ case {a, b} {
+ | {Gt, _} -> Gt
+ | {Eq, Lt} -> Eq
+ | _ -> b
+ }
+}
+
+pub fn min(a, b) {
+ case {a, b} {
+ | {Lt, _} -> Lt
+ | {Eq, Gt} -> Eq
+ | _ -> b
+ }
+}
diff --git a/src/std/result.gleam b/src/std/result.gleam
new file mode 100644
index 0000000..133d9d5
--- /dev/null
+++ b/src/std/result.gleam
@@ -0,0 +1,51 @@
+// Result represents the result of something that may succeed or fail.
+// `Ok` means it was successful, `Error` means it failed.
+
+pub fn is_ok(result) {
+ case result {
+ | Error(_) -> False
+ | Ok(_) -> True
+ }
+}
+
+pub fn is_error(result) {
+ case result {
+ | Ok(_) -> False
+ | Error(_) -> True
+ }
+}
+
+pub fn map(result, fun) {
+ case result {
+ | Ok(x) -> Ok(fun(x))
+ | Error(e) -> Error(e)
+ }
+}
+
+pub fn map_error(result, fun) {
+ case result {
+ | Ok(_) -> result
+ | Error(error) -> Error(fun(error))
+ }
+}
+
+pub fn flatten(result) {
+ case result {
+ | Ok(x) -> x
+ | Error(error) -> Error(error)
+ }
+}
+
+pub fn then(result, fun) {
+ case result {
+ | Ok(x) -> fun(x)
+ | Error(e) -> Error(e)
+ }
+}
+
+pub fn unwrap(result, default) {
+ case result {
+ | Ok(v) -> v
+ | Error(_) -> default
+ }
+}
diff --git a/src/std/string.gleam b/src/std/string.gleam
new file mode 100644
index 0000000..9eacb37
--- /dev/null
+++ b/src/std/string.gleam
@@ -0,0 +1,36 @@
+import std/iodata
+import std/list
+
+pub external fn length(String) -> Int = "string" "length"
+
+pub enum ParseError =
+ | ParseError
+
+pub external fn lowercase(String) -> String = "string" "lowercase"
+
+pub external fn uppercase(String) -> String = "string" "uppercase"
+
+pub fn reverse(string) {
+ string
+ |> iodata:new
+ |> iodata:reverse
+ |> iodata:to_string
+}
+
+pub fn split(string, on) {
+ string
+ |> iodata:new
+ |> iodata:split(_, on)
+ |> list:map(_, iodata:to_string)
+}
+
+pub fn replace(string, pattern, with) {
+ string
+ |> iodata:new
+ |> iodata:replace(_, pattern, with)
+ |> iodata:to_string
+}
+
+pub fn append(s1, s2) {
+ iodata:new(s1) |> iodata:append(_, s2) |> iodata:to_string(_)
+}
diff --git a/src/std/tuple.gleam b/src/std/tuple.gleam
new file mode 100644
index 0000000..ab38a74
--- /dev/null
+++ b/src/std/tuple.gleam
@@ -0,0 +1,29 @@
+import std/list
+
+pub fn new(a, b) {
+ {a, b}
+}
+
+pub fn first(tup) {
+ let {a, _} = tup
+ a
+}
+
+pub fn second(tup) {
+ let {_, a} = tup
+ a
+}
+
+pub fn swap(tup) {
+ let {a, b} = tup
+ {b, a}
+}
+
+pub fn fetch(haystack, needle) {
+ list:find(haystack, fn(tuple) {
+ case first(tuple) == needle {
+ | True -> tuple |> second |> Ok
+ | False -> Error([])
+ }
+ })
+}