├── .vscode ├── extensions.json └── settings.json ├── test ├── gleam_stdlib_test.gleam ├── gleam │ ├── function_test.gleam │ ├── dynamic_test.gleam │ ├── pair_test.gleam │ ├── order_test.gleam │ ├── bool_test.gleam │ ├── option_test.gleam │ ├── bytes_tree_test.gleam │ ├── string_tree_test.gleam │ ├── set_test.gleam │ ├── result_test.gleam │ ├── int_test.gleam │ ├── bit_array_test.gleam │ ├── float_test.gleam │ └── dict_test.gleam ├── gleeunit_ffi.erl ├── gleam_stdlib_test_ffi.erl ├── gleam_stdlib_test_ffi.mjs ├── gleeunit │ ├── internal │ │ ├── gleam_panic.gleam │ │ ├── gleeunit_gleam_panic_ffi.erl │ │ ├── gleeunit_gleam_panic_ffi.mjs │ │ └── reporting.gleam │ └── should.gleam ├── gleeunit.gleam ├── gleeunit_progress.erl └── gleeunit_ffi.mjs ├── src └── gleam │ ├── function.gleam │ ├── io.gleam │ ├── pair.gleam │ ├── order.gleam │ ├── dynamic.gleam │ ├── bytes_tree.gleam │ ├── string_tree.gleam │ ├── bool.gleam │ ├── option.gleam │ ├── bit_array.gleam │ ├── set.gleam │ ├── result.gleam │ ├── float.gleam │ └── dict.gleam ├── .github ├── dependabot.yml └── workflows │ └── ci.yml ├── .gitignore ├── gleam.toml ├── .editorconfig ├── README.md └── LICENCE /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["gleam.gleam"] 3 | } -------------------------------------------------------------------------------- /test/gleam_stdlib_test.gleam: -------------------------------------------------------------------------------- 1 | import gleeunit 2 | 3 | pub fn main() -> Nil { 4 | gleeunit.main() 5 | } 6 | -------------------------------------------------------------------------------- /src/gleam/function.gleam: -------------------------------------------------------------------------------- 1 | /// Takes a single argument and always returns its input value. 2 | /// 3 | pub fn identity(x: a) -> a { 4 | x 5 | } 6 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "[gleam]": { 4 | "editor.defaultFormatter": "gleam.gleam" 5 | }, 6 | "deno.enable": true 7 | } 8 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | - package-ecosystem: github-actions 2 | directory: "/" 3 | schedule: 4 | interval: monthly 5 | open-pull-requests-limit: 10 6 | labels: [] 7 | -------------------------------------------------------------------------------- /test/gleam/function_test.gleam: -------------------------------------------------------------------------------- 1 | import gleam/function 2 | 3 | pub fn identity_test() { 4 | assert function.identity(1) == 1 5 | assert function.identity("") == "" 6 | assert function.identity([]) == [] 7 | assert function.identity(#(1, 2.0)) == #(1, 2.0) 8 | } 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .rebar3 2 | _* 3 | .eunit 4 | *.o 5 | *.beam 6 | *.plt 7 | *.swp 8 | *.swo 9 | .erlang.cookie 10 | ebin 11 | log 12 | erl_crash.dump 13 | .rebar 14 | gen 15 | logs 16 | _build 17 | .idea 18 | *.iml 19 | rebar3.crashdump 20 | doc 21 | .tool-versions 22 | build 23 | manifest.toml 24 | -------------------------------------------------------------------------------- /test/gleam/dynamic_test.gleam: -------------------------------------------------------------------------------- 1 | import gleam/dynamic 2 | 3 | pub fn classify_true_test() { 4 | assert dynamic.classify(dynamic.bool(True)) == "Bool" 5 | } 6 | 7 | pub fn classify_false_test() { 8 | assert dynamic.classify(dynamic.bool(False)) == "Bool" 9 | } 10 | 11 | pub fn null_test() { 12 | assert dynamic.classify(dynamic.nil()) == "Nil" 13 | } 14 | -------------------------------------------------------------------------------- /test/gleeunit_ffi.erl: -------------------------------------------------------------------------------- 1 | -module(gleeunit_ffi). 2 | 3 | -export([find_files/2, run_eunit/2]). 4 | 5 | find_files(Pattern, In) -> 6 | Results = filelib:wildcard(binary_to_list(Pattern), binary_to_list(In)), 7 | lists:map(fun list_to_binary/1, Results). 8 | 9 | run_eunit(Tests, Options) -> 10 | case eunit:test(Tests, Options) of 11 | ok -> {ok, nil}; 12 | error -> {error, nil}; 13 | {error, Term} -> {error, Term} 14 | end. 15 | -------------------------------------------------------------------------------- /gleam.toml: -------------------------------------------------------------------------------- 1 | name = "gleam_stdlib" 2 | version = "0.67.1" 3 | gleam = ">= 1.13.0" 4 | licences = ["Apache-2.0"] 5 | description = "A standard library for the Gleam programming language" 6 | 7 | repository = { type = "github", user = "gleam-lang", repo = "stdlib" } 8 | links = [ 9 | { title = "Website", href = "https://gleam.run" }, 10 | { title = "Sponsor", href = "https://github.com/sponsors/lpil" }, 11 | ] 12 | 13 | [javascript.deno] 14 | allow_read = ["./"] 15 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | # top-most EditorConfig file 6 | root = true 7 | 8 | # Matches multiple files with brace expansion notation 9 | # Set default charset 10 | [*.{gleam, erl, mjs, js, ts, toml, yaml, yml, json, jsonc, md}] 11 | end_of_line = lf 12 | charset = utf-8 13 | trim_trailing_whitespace = true 14 | insert_final_newline = true 15 | indent_style = space 16 | indent_size = 2 17 | 18 | [*.erl] 19 | indent_size = 4 20 | 21 | [*.{erl, mjs, js, ts}] 22 | max_line_length = 80 23 | -------------------------------------------------------------------------------- /test/gleam_stdlib_test_ffi.erl: -------------------------------------------------------------------------------- 1 | -module(gleam_stdlib_test_ffi). 2 | 3 | -export([main/0, improper_list_append/3]). 4 | 5 | -include_lib("eunit/include/eunit.hrl"). 6 | 7 | main() -> 8 | Options = [ 9 | no_tty, {report, {eunit_progress, [colored]}} 10 | ], 11 | Files = filelib:wildcard("test/**/*.{erl,gleam}"), 12 | Modules = lists:map(fun filepath_to_module/1, Files), 13 | case eunit:test(Modules, Options) of 14 | ok -> erlang:halt(0); 15 | _ -> erlang:halt(1) 16 | end. 17 | 18 | filepath_to_module(Path0) -> 19 | Path1 = string:replace(Path0, "test/", ""), 20 | Path2 = string:replace(Path1, ".erl", ""), 21 | Path3 = string:replace(Path2, ".gleam", ""), 22 | Path4 = string:replace(Path3, "/", "@", all), 23 | Path5 = list_to_binary(Path4), 24 | binary_to_atom(Path5). 25 | 26 | improper_list_append(ItemA, ItemB, ImproperTail) -> 27 | [ItemA, ItemB | ImproperTail]. 28 | -------------------------------------------------------------------------------- /test/gleam_stdlib_test_ffi.mjs: -------------------------------------------------------------------------------- 1 | export function uint8array(list) { 2 | const ints = list.toArray(); 3 | const array = new Uint8Array(ints.length); 4 | for (let i = 0; i < ints.length; i++) { 5 | array[i] = ints[i]; 6 | } 7 | return array; 8 | } 9 | 10 | export function get_null() { 11 | return null; 12 | } 13 | 14 | export function object(items) { 15 | const object = {}; 16 | for (const [k, v] of items) { 17 | object[k] = v; 18 | } 19 | return object; 20 | } 21 | 22 | export function map(items) { 23 | const object = new Map(); 24 | for (const [k, v] of items) { 25 | object.set(k, v); 26 | } 27 | return object; 28 | } 29 | 30 | const singleton = { a: 1 }; 31 | 32 | export function singleton_object() { 33 | return singleton; 34 | } 35 | 36 | export function circular_reference() { 37 | const x = [1, 2, 3]; 38 | x.push(x); 39 | return x; 40 | } 41 | 42 | export function js_error() { 43 | const error = new Error("Oh no!"); 44 | error.name = "SomeError"; 45 | return error; 46 | } 47 | -------------------------------------------------------------------------------- /test/gleam/pair_test.gleam: -------------------------------------------------------------------------------- 1 | import gleam/pair 2 | 3 | pub fn first_test() { 4 | assert pair.first(#(1, 2)) == 1 5 | 6 | assert pair.first(#("abc", [])) == "abc" 7 | } 8 | 9 | pub fn second_test() { 10 | assert pair.second(#(1, 2)) == 2 11 | 12 | assert pair.second(#("abc", [])) == [] 13 | } 14 | 15 | pub fn swap_test() { 16 | assert pair.swap(#(1, "2")) == #("2", 1) 17 | } 18 | 19 | pub fn map_first_test() { 20 | let inc = fn(a) { a + 1 } 21 | assert pair.map_first(#(1, 2), inc) == #(2, 2) 22 | 23 | assert pair.map_first(#(8, 2), inc) == #(9, 2) 24 | 25 | assert pair.map_first(#(0, -2), inc) == #(1, -2) 26 | 27 | assert pair.map_first(#(-10, 20), inc) == #(-9, 20) 28 | } 29 | 30 | pub fn map_second_test() { 31 | let dec = fn(a) { a - 1 } 32 | assert pair.map_second(#(1, 2), dec) == #(1, 1) 33 | 34 | assert pair.map_second(#(8, 2), dec) == #(8, 1) 35 | 36 | assert pair.map_second(#(0, -2), dec) == #(0, -3) 37 | 38 | assert pair.map_second(#(-10, 20), dec) == #(-10, 19) 39 | } 40 | 41 | pub fn new_test() { 42 | assert pair.new(1, 2) == #(1, 2) 43 | } 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # stdlib 2 | 3 | [![Package Version](https://img.shields.io/hexpm/v/gleam_stdlib)](https://hex.pm/packages/gleam_stdlib) 4 | [![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/gleam_stdlib/) 5 | [![Discord chat](https://img.shields.io/discord/768594524158427167?color=blue)](https://discord.gg/Fm8Pwmy) 6 | 7 | Gleam's standard library! 8 | Documentation available on [HexDocs](https://hexdocs.pm/gleam_stdlib/). 9 | 10 | ## Installation 11 | 12 | Add `gleam_stdlib` to your Gleam project. 13 | 14 | ```sh 15 | gleam add gleam_stdlib 16 | ``` 17 | ```gleam 18 | import gleam/io 19 | 20 | pub fn greet(name: String) -> Nil { 21 | io.println("Hello " <> name <> "!") 22 | } 23 | ``` 24 | 25 | ## Targets 26 | 27 | Gleam's standard library supports both targets: Erlang and JavaScript. 28 | 29 | ### Compatibility 30 | 31 | This library is compatible with all versions of Erlang/OTP 26 and higher, 32 | as well as all NodeJS, Deno, Bun, and major browsers that are currently 33 | supported by their maintainers. If you have a compatibility issue with 34 | any platform open an issue and we'll see what we can do to help. 35 | -------------------------------------------------------------------------------- /test/gleeunit/internal/gleam_panic.gleam: -------------------------------------------------------------------------------- 1 | import gleam/dynamic 2 | 3 | pub type GleamPanic { 4 | GleamPanic( 5 | message: String, 6 | file: String, 7 | module: String, 8 | function: String, 9 | line: Int, 10 | kind: PanicKind, 11 | ) 12 | } 13 | 14 | pub type PanicKind { 15 | Todo 16 | Panic 17 | LetAssert( 18 | start: Int, 19 | end: Int, 20 | pattern_start: Int, 21 | pattern_end: Int, 22 | value: dynamic.Dynamic, 23 | ) 24 | Assert(start: Int, end: Int, expression_start: Int, kind: AssertKind) 25 | } 26 | 27 | pub type AssertKind { 28 | BinaryOperator( 29 | operator: String, 30 | left: AssertedExpression, 31 | right: AssertedExpression, 32 | ) 33 | FunctionCall(arguments: List(AssertedExpression)) 34 | OtherExpression(expression: AssertedExpression) 35 | } 36 | 37 | pub type AssertedExpression { 38 | AssertedExpression(start: Int, end: Int, kind: ExpressionKind) 39 | } 40 | 41 | pub type ExpressionKind { 42 | Literal(value: dynamic.Dynamic) 43 | Expression(value: dynamic.Dynamic) 44 | Unevaluated 45 | } 46 | 47 | @external(erlang, "gleeunit_gleam_panic_ffi", "from_dynamic") 48 | @external(javascript, "./gleeunit_gleam_panic_ffi.mjs", "from_dynamic") 49 | pub fn from_dynamic(data: dynamic.Dynamic) -> Result(GleamPanic, Nil) 50 | -------------------------------------------------------------------------------- /src/gleam/io.gleam: -------------------------------------------------------------------------------- 1 | /// Writes a string to standard output (stdout). 2 | /// 3 | /// If you want your output to be printed on its own line see `println`. 4 | /// 5 | /// ## Example 6 | /// 7 | /// ```gleam 8 | /// io.print("Hi mum") 9 | /// // -> Nil 10 | /// // Hi mum 11 | /// ``` 12 | /// 13 | @external(erlang, "gleam_stdlib", "print") 14 | @external(javascript, "../gleam_stdlib.mjs", "print") 15 | pub fn print(string: String) -> Nil 16 | 17 | /// Writes a string to standard error (stderr). 18 | /// 19 | /// If you want your output to be printed on its own line see `println_error`. 20 | /// 21 | /// ## Example 22 | /// 23 | /// ``` 24 | /// io.print_error("Hi pop") 25 | /// // -> Nil 26 | /// // Hi pop 27 | /// ``` 28 | /// 29 | @external(erlang, "gleam_stdlib", "print_error") 30 | @external(javascript, "../gleam_stdlib.mjs", "print_error") 31 | pub fn print_error(string: String) -> Nil 32 | 33 | /// Writes a string to standard output (stdout), appending a newline to the end. 34 | /// 35 | /// ## Example 36 | /// 37 | /// ```gleam 38 | /// io.println("Hi mum") 39 | /// // -> Nil 40 | /// // Hi mum 41 | /// ``` 42 | /// 43 | @external(erlang, "gleam_stdlib", "println") 44 | @external(javascript, "../gleam_stdlib.mjs", "console_log") 45 | pub fn println(string: String) -> Nil 46 | 47 | /// Writes a string to standard error (stderr), appending a newline to the end. 48 | /// 49 | /// ## Example 50 | /// 51 | /// ```gleam 52 | /// io.println_error("Hi pop") 53 | /// // -> Nil 54 | /// // Hi pop 55 | /// ``` 56 | /// 57 | @external(erlang, "gleam_stdlib", "println_error") 58 | @external(javascript, "../gleam_stdlib.mjs", "console_error") 59 | pub fn println_error(string: String) -> Nil 60 | -------------------------------------------------------------------------------- /test/gleam/order_test.gleam: -------------------------------------------------------------------------------- 1 | import gleam/int 2 | import gleam/list 3 | import gleam/order.{Eq, Gt, Lt} 4 | 5 | pub fn negate_test() { 6 | assert order.negate(Lt) == Gt 7 | 8 | assert order.negate(Eq) == Eq 9 | 10 | assert order.negate(Gt) == Lt 11 | } 12 | 13 | pub fn to_int_test() { 14 | assert order.to_int(Lt) == -1 15 | 16 | assert order.to_int(Eq) == 0 17 | 18 | assert order.to_int(Gt) == 1 19 | } 20 | 21 | pub fn compare_test() { 22 | assert order.compare(Lt, Lt) == Eq 23 | 24 | assert order.compare(Lt, Eq) == Lt 25 | 26 | assert order.compare(Lt, Gt) == Lt 27 | 28 | assert order.compare(Eq, Lt) == Gt 29 | 30 | assert order.compare(Eq, Eq) == Eq 31 | 32 | assert order.compare(Eq, Gt) == Lt 33 | 34 | assert order.compare(Gt, Lt) == Gt 35 | 36 | assert order.compare(Gt, Eq) == Gt 37 | 38 | assert order.compare(Gt, Gt) == Eq 39 | } 40 | 41 | pub fn reverse_test() { 42 | assert list.sort([4, 5, 1], by: order.reverse(int.compare)) == [5, 4, 1] 43 | } 44 | 45 | pub fn break_tie_test() { 46 | assert order.break_tie(in: Eq, with: Lt) == Lt 47 | 48 | assert order.break_tie(in: Eq, with: Gt) == Gt 49 | 50 | assert order.break_tie(in: Eq, with: Eq) == Eq 51 | 52 | assert order.break_tie(in: Gt, with: Lt) == Gt 53 | 54 | assert order.break_tie(in: Lt, with: Gt) == Lt 55 | } 56 | 57 | pub fn lazy_break_tie_test() { 58 | assert order.lazy_break_tie(in: Eq, with: fn() { Lt }) == Lt 59 | 60 | assert order.lazy_break_tie(in: Eq, with: fn() { Gt }) == Gt 61 | 62 | assert order.lazy_break_tie(in: Eq, with: fn() { Eq }) == Eq 63 | 64 | assert order.lazy_break_tie(in: Gt, with: fn() { panic }) == Gt 65 | 66 | assert order.lazy_break_tie(in: Lt, with: fn() { panic }) == Lt 67 | } 68 | -------------------------------------------------------------------------------- /test/gleeunit/should.gleam: -------------------------------------------------------------------------------- 1 | //// Use the `assert` keyword instead of this module. 2 | 3 | import gleam/option.{type Option, None, Some} 4 | import gleam/string 5 | 6 | pub fn equal(a: t, b: t) -> Nil { 7 | case a == b { 8 | True -> Nil 9 | _ -> 10 | panic as string.concat([ 11 | "\n", 12 | string.inspect(a), 13 | "\nshould equal\n", 14 | string.inspect(b), 15 | ]) 16 | } 17 | } 18 | 19 | pub fn not_equal(a: t, b: t) -> Nil { 20 | case a != b { 21 | True -> Nil 22 | _ -> 23 | panic as string.concat([ 24 | "\n", 25 | string.inspect(a), 26 | "\nshould not equal\n", 27 | string.inspect(b), 28 | ]) 29 | } 30 | } 31 | 32 | pub fn be_ok(a: Result(a, e)) -> a { 33 | case a { 34 | Ok(value) -> value 35 | _ -> panic as string.concat(["\n", string.inspect(a), "\nshould be ok"]) 36 | } 37 | } 38 | 39 | pub fn be_error(a: Result(a, e)) -> e { 40 | case a { 41 | Error(error) -> error 42 | _ -> panic as string.concat(["\n", string.inspect(a), "\nshould be error"]) 43 | } 44 | } 45 | 46 | pub fn be_some(a: Option(a)) -> a { 47 | case a { 48 | Some(value) -> value 49 | _ -> panic as string.concat(["\n", string.inspect(a), "\nshould be some"]) 50 | } 51 | } 52 | 53 | pub fn be_none(a: Option(a)) -> Nil { 54 | case a { 55 | None -> Nil 56 | _ -> panic as string.concat(["\n", string.inspect(a), "\nshould be none"]) 57 | } 58 | } 59 | 60 | pub fn be_true(actual: Bool) -> Nil { 61 | actual 62 | |> equal(True) 63 | } 64 | 65 | pub fn be_false(actual: Bool) -> Nil { 66 | actual 67 | |> equal(False) 68 | } 69 | 70 | pub fn fail() -> Nil { 71 | be_true(False) 72 | } 73 | -------------------------------------------------------------------------------- /test/gleeunit/internal/gleeunit_gleam_panic_ffi.erl: -------------------------------------------------------------------------------- 1 | -module(gleeunit_gleam_panic_ffi). 2 | -export([from_dynamic/1]). 3 | 4 | from_dynamic(#{ 5 | gleam_error := assert, 6 | start := Start, 7 | 'end' := End, 8 | expression_start := EStart 9 | } = E) -> 10 | wrap(E, {assert, Start, End, EStart, assert_kind(E)}); 11 | from_dynamic(#{ 12 | gleam_error := let_assert, 13 | start := Start, 14 | 'end' := End, 15 | pattern_start := PStart, 16 | pattern_end := PEnd, 17 | value := Value 18 | } = E) -> 19 | wrap(E, {let_assert, Start, End, PStart, PEnd, Value}); 20 | from_dynamic(#{gleam_error := panic} = E) -> 21 | wrap(E, panic); 22 | from_dynamic(#{gleam_error := todo} = E) -> 23 | wrap(E, todo); 24 | from_dynamic(_) -> 25 | {error, nil}. 26 | 27 | assert_kind(#{kind := binary_operator, left := L, right := R, operator := O}) -> 28 | {binary_operator, atom_to_binary(O), expression(L), expression(R)}; 29 | assert_kind(#{kind := function_call, arguments := Arguments}) -> 30 | {function_call, lists:map(fun expression/1, Arguments)}; 31 | assert_kind(#{kind := expression, expression := Expression}) -> 32 | {other_expression, expression(Expression)}. 33 | 34 | expression(#{start := S, 'end' := E, kind := literal, value := Value}) -> 35 | {asserted_expression, S, E, {literal, Value}}; 36 | expression(#{start := S, 'end' := E, kind := expression, value := Value}) -> 37 | {asserted_expression, S, E, {expression, Value}}; 38 | expression(#{start := S, 'end' := E, kind := unevaluated}) -> 39 | {asserted_expression, S, E, unevaluated}. 40 | 41 | wrap(#{ 42 | gleam_error := _, 43 | file := File, 44 | message := Message, 45 | module := Module, 46 | function := Function, 47 | line := Line 48 | }, Kind) -> 49 | {ok, {gleam_panic, Message, File, Module, Function, Line, Kind}}. 50 | -------------------------------------------------------------------------------- /src/gleam/pair.gleam: -------------------------------------------------------------------------------- 1 | /// Returns the first element in a pair. 2 | /// 3 | /// ## Examples 4 | /// 5 | /// ```gleam 6 | /// first(#(1, 2)) 7 | /// // -> 1 8 | /// ``` 9 | /// 10 | pub fn first(pair: #(a, b)) -> a { 11 | let #(a, _) = pair 12 | a 13 | } 14 | 15 | /// Returns the second element in a pair. 16 | /// 17 | /// ## Examples 18 | /// 19 | /// ```gleam 20 | /// second(#(1, 2)) 21 | /// // -> 2 22 | /// ``` 23 | /// 24 | pub fn second(pair: #(a, b)) -> b { 25 | let #(_, a) = pair 26 | a 27 | } 28 | 29 | /// Returns a new pair with the elements swapped. 30 | /// 31 | /// ## Examples 32 | /// 33 | /// ```gleam 34 | /// swap(#(1, 2)) 35 | /// // -> #(2, 1) 36 | /// ``` 37 | /// 38 | pub fn swap(pair: #(a, b)) -> #(b, a) { 39 | let #(a, b) = pair 40 | #(b, a) 41 | } 42 | 43 | /// Returns a new pair with the first element having had `with` applied to 44 | /// it. 45 | /// 46 | /// ## Examples 47 | /// 48 | /// ```gleam 49 | /// #(1, 2) |> map_first(fn(n) { n * 2 }) 50 | /// // -> #(2, 2) 51 | /// ``` 52 | /// 53 | pub fn map_first(of pair: #(a, b), with fun: fn(a) -> c) -> #(c, b) { 54 | let #(a, b) = pair 55 | #(fun(a), b) 56 | } 57 | 58 | /// Returns a new pair with the second element having had `with` applied to 59 | /// it. 60 | /// 61 | /// ## Examples 62 | /// 63 | /// ```gleam 64 | /// #(1, 2) |> map_second(fn(n) { n * 2 }) 65 | /// // -> #(1, 4) 66 | /// ``` 67 | /// 68 | pub fn map_second(of pair: #(a, b), with fun: fn(b) -> c) -> #(a, c) { 69 | let #(a, b) = pair 70 | #(a, fun(b)) 71 | } 72 | 73 | /// Returns a new pair with the given elements. This can also be done using the dedicated 74 | /// syntax instead: `new(1, 2) == #(1, 2)`. 75 | /// 76 | /// ## Examples 77 | /// 78 | /// ```gleam 79 | /// new(1, 2) 80 | /// // -> #(1, 2) 81 | /// ``` 82 | /// 83 | pub fn new(first: a, second: b) -> #(a, b) { 84 | #(first, second) 85 | } 86 | -------------------------------------------------------------------------------- /test/gleam/bool_test.gleam: -------------------------------------------------------------------------------- 1 | import gleam/bool 2 | 3 | pub fn and_test() { 4 | assert bool.and(True, True) 5 | 6 | assert !bool.and(False, True) 7 | 8 | assert bool.and(True, True) 9 | 10 | assert !bool.and(True, False) 11 | } 12 | 13 | pub fn or_test() { 14 | assert bool.or(True, True) 15 | 16 | assert bool.or(False, True) 17 | 18 | assert bool.or(True, False) 19 | 20 | assert bool.or(True, False) 21 | } 22 | 23 | pub fn negate_test() { 24 | assert !bool.negate(True) 25 | 26 | assert bool.negate(False) 27 | } 28 | 29 | pub fn nor_test() { 30 | assert bool.nor(False, False) 31 | 32 | assert !bool.nor(False, True) 33 | 34 | assert !bool.nor(True, False) 35 | 36 | assert !bool.nor(True, True) 37 | } 38 | 39 | pub fn nand_test() { 40 | assert bool.nand(False, False) 41 | 42 | assert bool.nand(False, True) 43 | 44 | assert bool.nand(True, False) 45 | 46 | assert !bool.nand(True, True) 47 | } 48 | 49 | pub fn exclusive_or_test() { 50 | assert !bool.exclusive_or(True, True) 51 | 52 | assert !bool.exclusive_or(False, False) 53 | 54 | assert bool.exclusive_or(True, False) 55 | 56 | assert bool.exclusive_or(False, True) 57 | } 58 | 59 | pub fn exclusive_nor_test() { 60 | assert bool.exclusive_nor(False, False) 61 | 62 | assert !bool.exclusive_nor(False, True) 63 | 64 | assert !bool.exclusive_nor(True, False) 65 | 66 | assert bool.exclusive_nor(True, True) 67 | } 68 | 69 | pub fn to_string_test() { 70 | assert bool.to_string(True) == "True" 71 | 72 | assert bool.to_string(False) == "False" 73 | } 74 | 75 | pub fn guard_test() { 76 | let assert 2 = { 77 | use <- bool.guard(when: True, return: 2) 78 | 1 79 | } 80 | 81 | let assert 1 = { 82 | use <- bool.guard(when: False, return: 2) 83 | 1 84 | } 85 | } 86 | 87 | pub fn lazy_guard_test() { 88 | let oops = fn() { panic as "this shouldn't run!" } 89 | 90 | let assert 2 = { 91 | use <- bool.lazy_guard(when: True, otherwise: oops) 92 | 2 93 | } 94 | 95 | let assert 1 = { 96 | use <- bool.lazy_guard(when: False, return: oops) 97 | 1 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /test/gleeunit.gleam: -------------------------------------------------------------------------------- 1 | import gleam/list 2 | import gleam/result 3 | import gleam/string 4 | 5 | /// Find and run all test functions for the current project using Erlang's EUnit 6 | /// test framework, or a custom JavaScript test runner. 7 | /// 8 | /// Any Erlang or Gleam function in the `test` directory with a name ending in 9 | /// `_test` is considered a test function and will be run. 10 | /// 11 | /// A test that panics is considered a failure. 12 | /// 13 | pub fn main() -> Nil { 14 | do_main() 15 | } 16 | 17 | @external(javascript, "./gleeunit_ffi.mjs", "main") 18 | fn do_main() -> Nil { 19 | let options = [Verbose, NoTty, Report(#(GleeunitProgress, [Colored(True)]))] 20 | 21 | let result = 22 | find_files(matching: "**/*.{erl,gleam}", in: "test") 23 | |> list.map(gleam_to_erlang_module_name) 24 | |> list.map(dangerously_convert_string_to_atom(_, Utf8)) 25 | |> run_eunit(options) 26 | 27 | let code = case result { 28 | Ok(_) -> 0 29 | Error(_) -> 1 30 | } 31 | halt(code) 32 | } 33 | 34 | @external(erlang, "erlang", "halt") 35 | fn halt(a: Int) -> Nil 36 | 37 | fn gleam_to_erlang_module_name(path: String) -> String { 38 | case string.ends_with(path, ".gleam") { 39 | True -> 40 | path 41 | |> string.replace(".gleam", "") 42 | |> string.replace("/", "@") 43 | 44 | False -> 45 | path 46 | |> string.split("/") 47 | |> list.last 48 | |> result.unwrap(path) 49 | |> string.replace(".erl", "") 50 | } 51 | } 52 | 53 | @external(erlang, "gleeunit_ffi", "find_files") 54 | fn find_files(matching matching: String, in in: String) -> List(String) 55 | 56 | type Atom 57 | 58 | type Encoding { 59 | Utf8 60 | } 61 | 62 | @external(erlang, "erlang", "binary_to_atom") 63 | fn dangerously_convert_string_to_atom(a: String, b: Encoding) -> Atom 64 | 65 | type ReportModuleName { 66 | GleeunitProgress 67 | } 68 | 69 | type GleeunitProgressOption { 70 | Colored(Bool) 71 | } 72 | 73 | type EunitOption { 74 | Verbose 75 | NoTty 76 | Report(#(ReportModuleName, List(GleeunitProgressOption))) 77 | } 78 | 79 | @external(erlang, "gleeunit_ffi", "run_eunit") 80 | fn run_eunit(a: List(Atom), b: List(EunitOption)) -> Result(Nil, a) 81 | -------------------------------------------------------------------------------- /test/gleeunit_progress.erl: -------------------------------------------------------------------------------- 1 | %% A formatter adapted from Sean Cribb's https://github.com/seancribbs/eunit_formatters 2 | 3 | -module(gleeunit_progress). 4 | -behaviour(eunit_listener). 5 | -define(NOTEST, true). 6 | -include_lib("eunit/include/eunit.hrl"). 7 | 8 | %% eunit_listener callbacks 9 | -export([ 10 | init/1, handle_begin/3, handle_end/3, handle_cancel/3, terminate/2, 11 | start/0, start/1 12 | ]). 13 | 14 | -define(reporting, gleeunit@internal@reporting). 15 | 16 | start() -> 17 | start([]). 18 | 19 | start(Options) -> 20 | eunit_listener:start(?MODULE, Options). 21 | 22 | init(_Options) -> 23 | ?reporting:new_state(). 24 | 25 | handle_begin(_test_or_group, _data, State) -> 26 | State. 27 | 28 | handle_end(group, _data, State) -> 29 | State; 30 | handle_end(test, Data, State) -> 31 | {AtomModule, AtomFunction, _Arity} = proplists:get_value(source, Data), 32 | Module = erlang:atom_to_binary(AtomModule), 33 | Function = erlang:atom_to_binary(AtomFunction), 34 | 35 | % EUnit swallows stdout, so print it to make debugging easier. 36 | case proplists:get_value(output, Data) of 37 | undefined -> ok; 38 | <<>> -> ok; 39 | Out -> gleam@io:print(Out) 40 | end, 41 | 42 | case proplists:get_value(status, Data) of 43 | ok -> 44 | ?reporting:test_passed(State); 45 | {skipped, _Reason} -> 46 | ?reporting:test_skipped(State, Module, Function); 47 | {error, {_, Exception, _Stack}} -> 48 | ?reporting:test_failed(State, Module, Function, Exception) 49 | end. 50 | 51 | handle_cancel(_test_or_group, Data, State) -> 52 | ?reporting:test_failed(State, <<"gleeunit">>, <<"main">>, Data). 53 | 54 | terminate({ok, _Data}, State) -> 55 | ?reporting:finished(State), 56 | ok; 57 | terminate({error, Reason}, State) -> 58 | ?reporting:finished(State), 59 | io:fwrite(" 60 | Eunit failed: 61 | 62 | ~80p 63 | 64 | This is probably a bug in gleeunit. Please report it. 65 | ", [Reason]), 66 | sync_end(error). 67 | 68 | sync_end(Result) -> 69 | receive 70 | {stop, Reference, ReplyTo} -> 71 | ReplyTo ! {result, Reference, Result}, 72 | ok 73 | end. 74 | -------------------------------------------------------------------------------- /test/gleeunit/internal/gleeunit_gleam_panic_ffi.mjs: -------------------------------------------------------------------------------- 1 | import { Ok, Error, Empty, NonEmpty } from "../../gleam.mjs"; 2 | import { 3 | GleamPanic, 4 | Todo, 5 | Panic, 6 | LetAssert, 7 | Assert, 8 | BinaryOperator, 9 | FunctionCall, 10 | OtherExpression, 11 | AssertedExpression, 12 | Literal, 13 | Expression, 14 | Unevaluated, 15 | } from "./gleam_panic.mjs"; 16 | 17 | export function from_dynamic(error) { 18 | if (!(error instanceof globalThis.Error) || !error.gleam_error) { 19 | return new Error(undefined); 20 | } 21 | 22 | if (error.gleam_error === "todo") { 23 | return wrap(error, new Todo()); 24 | } 25 | 26 | if (error.gleam_error === "panic") { 27 | return wrap(error, new Panic()); 28 | } 29 | 30 | if (error.gleam_error === "let_assert") { 31 | let kind = new LetAssert( 32 | error.start, 33 | error.end, 34 | error.pattern_start, 35 | error.pattern_end, 36 | error.value, 37 | ); 38 | return wrap(error, kind); 39 | } 40 | 41 | if (error.gleam_error === "assert") { 42 | let kind = new Assert( 43 | error.start, 44 | error.end, 45 | error.expression_start, 46 | assert_kind(error), 47 | ); 48 | return wrap(error, kind); 49 | } 50 | 51 | return new Error(undefined); 52 | } 53 | 54 | function assert_kind(error) { 55 | if (error.kind == "binary_operator") { 56 | return new BinaryOperator( 57 | error.operator, 58 | expression(error.left), 59 | expression(error.right), 60 | ); 61 | } 62 | 63 | if (error.kind == "function_call") { 64 | let list = new Empty(); 65 | let i = error.arguments.length; 66 | while (i--) { 67 | list = new NonEmpty(expression(error.arguments[i]), list); 68 | } 69 | return new FunctionCall(list); 70 | } 71 | 72 | return new OtherExpression(expression(error.expression)); 73 | } 74 | 75 | function expression(data) { 76 | const expression = new AssertedExpression(data.start, data.end, undefined); 77 | if (data.kind == "literal") { 78 | expression.kind = new Literal(data.value); 79 | } else if (data.kind == "expression") { 80 | expression.kind = new Expression(data.value); 81 | } else { 82 | expression.kind = new Unevaluated(); 83 | } 84 | return expression; 85 | } 86 | 87 | function wrap(e, kind) { 88 | return new Ok( 89 | new GleamPanic(e.message, e.file, e.module, e.function, e.line, kind), 90 | ); 91 | } 92 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - "main" 7 | - "v*.*.*" 8 | pull_request: 9 | workflow_dispatch: 10 | 11 | permissions: 12 | contents: read 13 | 14 | concurrency: 15 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 16 | cancel-in-progress: true 17 | 18 | jobs: 19 | test_erlang: 20 | runs-on: ubuntu-24.04 21 | timeout-minutes: 15 22 | strategy: 23 | fail-fast: false 24 | matrix: 25 | erlang_version: ["27", "28"] 26 | steps: 27 | - uses: actions/checkout@v4 28 | - uses: erlef/setup-beam@v1 29 | with: 30 | otp-version: ${{ matrix.erlang_version }} 31 | gleam-version: "1.13.0" 32 | - run: gleam test --target erlang 33 | - run: gleam format --check src test 34 | 35 | test_javascript_node: 36 | runs-on: ubuntu-24.04 37 | timeout-minutes: 15 38 | strategy: 39 | fail-fast: false 40 | matrix: 41 | node_version: ["18.20", "20.16", "22.5"] 42 | steps: 43 | - uses: actions/checkout@v4 44 | - uses: erlef/setup-beam@v1 45 | with: 46 | otp-version: "28" 47 | gleam-version: "1.13.0" 48 | - uses: actions/setup-node@v4 49 | with: 50 | node-version: ${{ matrix.node_version }} 51 | - run: gleam test --target javascript --runtime node 52 | 53 | test_javascript_bun: 54 | runs-on: ubuntu-24.04 55 | timeout-minutes: 15 56 | strategy: 57 | fail-fast: false 58 | matrix: 59 | bun_version: ["1.1"] 60 | steps: 61 | - uses: actions/checkout@v4 62 | - uses: erlef/setup-beam@v1 63 | with: 64 | otp-version: "28" 65 | gleam-version: "1.13.0" 66 | - uses: oven-sh/setup-bun@v2 67 | with: 68 | bun-version: ${{ matrix.bun_version }} 69 | - run: gleam test --target javascript --runtime bun 70 | 71 | test_javascript_deno: 72 | runs-on: ubuntu-24.04 73 | timeout-minutes: 15 74 | strategy: 75 | fail-fast: false 76 | matrix: 77 | deno_version: ["1.45"] 78 | steps: 79 | - uses: actions/checkout@v4 80 | - uses: erlef/setup-beam@v1 81 | with: 82 | otp-version: "28" 83 | gleam-version: "1.13.0" 84 | - uses: denoland/setup-deno@v1 85 | with: 86 | deno-version: ${{ matrix.deno_version }} 87 | - run: gleam test --target javascript --runtime deno 88 | -------------------------------------------------------------------------------- /test/gleam/option_test.gleam: -------------------------------------------------------------------------------- 1 | import gleam/option.{None, Some} 2 | 3 | pub fn all_test() { 4 | assert option.all([Some(1), Some(2), Some(3)]) == Some([1, 2, 3]) 5 | 6 | assert option.all([]) == Some([]) 7 | 8 | assert option.all([Some(1), None, Some(3)]) == None 9 | } 10 | 11 | pub fn is_some_test() { 12 | assert option.is_some(Some(1)) 13 | 14 | assert !option.is_some(None) 15 | } 16 | 17 | pub fn is_none_test() { 18 | assert !option.is_none(Some(1)) 19 | 20 | assert option.is_none(None) 21 | } 22 | 23 | pub fn to_result_test() { 24 | assert option.to_result(Some(1), "possible_error") == Ok(1) 25 | 26 | assert option.to_result(None, "possible_error") == Error("possible_error") 27 | } 28 | 29 | pub fn from_result_test() { 30 | assert option.from_result(Ok(1)) == Some(1) 31 | 32 | assert option.from_result(Error("some_error")) == None 33 | } 34 | 35 | pub fn unwrap_option_test() { 36 | assert option.unwrap(Some(1), 0) == 1 37 | 38 | assert option.unwrap(None, 0) == 0 39 | } 40 | 41 | pub fn lazy_unwrap_option_test() { 42 | assert option.lazy_unwrap(Some(1), fn() { 0 }) == 1 43 | 44 | assert option.lazy_unwrap(None, fn() { 0 }) == 0 45 | } 46 | 47 | pub fn map_option_test() { 48 | assert option.map(Some(1), fn(x) { x + 1 }) == Some(2) 49 | 50 | assert option.map(Some(1), fn(_) { "2" }) == Some("2") 51 | 52 | assert option.map(None, fn(x) { x + 1 }) == None 53 | } 54 | 55 | pub fn flatten_option_test() { 56 | assert option.flatten(Some(Some(1))) == Some(1) 57 | 58 | assert option.flatten(Some(None)) == None 59 | 60 | assert option.flatten(None) == None 61 | } 62 | 63 | pub fn then_option_test() { 64 | assert option.then(Some(1), fn(x) { Some(x + 1) }) == Some(2) 65 | 66 | assert option.then(Some(1), fn(_) { Some("2") }) == Some("2") 67 | 68 | assert option.then(None, fn(x) { Some(x + 1) }) == None 69 | } 70 | 71 | pub fn or_option_test() { 72 | assert option.or(Some(1), Some(2)) == Some(1) 73 | 74 | assert option.or(Some(1), None) == Some(1) 75 | 76 | assert option.or(None, Some(2)) == Some(2) 77 | 78 | assert option.or(None, None) == None 79 | } 80 | 81 | pub fn lazy_or_option_test() { 82 | assert option.lazy_or(Some(1), fn() { Some(2) }) == Some(1) 83 | 84 | assert option.lazy_or(Some(1), fn() { None }) == Some(1) 85 | 86 | assert option.lazy_or(None, fn() { Some(2) }) == Some(2) 87 | 88 | assert option.lazy_or(None, fn() { None }) == None 89 | } 90 | 91 | pub fn values_test() { 92 | assert option.values([Some(1), None, Some(3)]) == [1, 3] 93 | } 94 | -------------------------------------------------------------------------------- /test/gleam/bytes_tree_test.gleam: -------------------------------------------------------------------------------- 1 | import gleam/bytes_tree 2 | import gleam/string_tree 3 | 4 | pub fn tree_test() { 5 | let data = 6 | bytes_tree.from_bit_array(<<1>>) 7 | |> bytes_tree.append(<<2>>) 8 | |> bytes_tree.append(<<3>>) 9 | |> bytes_tree.prepend(<<0>>) 10 | 11 | assert bytes_tree.to_bit_array(data) == <<0, 1, 2, 3>> 12 | 13 | assert bytes_tree.byte_size(data) == 4 14 | } 15 | 16 | pub fn tree_unaligned_bit_arrays_test() { 17 | let data = 18 | bytes_tree.from_bit_array(<<-1:5>>) 19 | |> bytes_tree.append(<<-1:3>>) 20 | |> bytes_tree.append(<<-2:2>>) 21 | |> bytes_tree.prepend(<<-1:4>>) 22 | 23 | assert bytes_tree.to_bit_array(data) 24 | == <<-1:4, 0:4, -1:5, 0:3, -1:3, 0:5, -2:2, 0:6>> 25 | 26 | assert bytes_tree.byte_size(data) == 4 27 | } 28 | 29 | pub fn tree_with_strings_test() { 30 | let data = 31 | bytes_tree.from_bit_array(<<1>>) 32 | |> bytes_tree.append_string("2") 33 | |> bytes_tree.append_string("3") 34 | |> bytes_tree.prepend_string("0") 35 | 36 | assert bytes_tree.to_bit_array(data) == <<"0":utf8, 1, "2":utf8, "3":utf8>> 37 | 38 | assert bytes_tree.byte_size(data) == 4 39 | } 40 | 41 | pub fn tree_with_trees_test() { 42 | let data = 43 | bytes_tree.from_bit_array(<<1>>) 44 | |> bytes_tree.append_tree(bytes_tree.from_bit_array(<<2>>)) 45 | |> bytes_tree.append_tree(bytes_tree.from_bit_array(<<3>>)) 46 | |> bytes_tree.prepend_tree(bytes_tree.from_bit_array(<<0>>)) 47 | 48 | assert bytes_tree.to_bit_array(data) == <<0, 1, 2, 3>> 49 | 50 | assert bytes_tree.byte_size(data) == 4 51 | } 52 | 53 | pub fn concat_test() { 54 | assert [ 55 | bytes_tree.from_bit_array(<<1, 2>>), 56 | bytes_tree.from_bit_array(<<3, 4>>), 57 | bytes_tree.from_bit_array(<<5, 6>>), 58 | ] 59 | |> bytes_tree.concat 60 | |> bytes_tree.to_bit_array 61 | == <<1, 2, 3, 4, 5, 6>> 62 | } 63 | 64 | pub fn concat_bit_arrays_test() { 65 | assert bytes_tree.to_bit_array( 66 | bytes_tree.concat_bit_arrays([<<"h":utf8>>, <<"e":utf8>>, <<"y":utf8>>]), 67 | ) 68 | == <<"hey":utf8>> 69 | } 70 | 71 | pub fn concat_unaligned_bit_arrays_test() { 72 | assert bytes_tree.to_bit_array( 73 | bytes_tree.concat_bit_arrays([<<-1:4>>, <<-1:5>>, <<-1:3>>, <<-2:2>>]), 74 | ) 75 | == <<-1:4, 0:4, -1:5, 0:3, -1:3, 0:5, -2:2, 0:6>> 76 | } 77 | 78 | pub fn from_bit_array() { 79 | // Regression test: no additional modification of the tree 80 | assert bytes_tree.to_bit_array(bytes_tree.from_bit_array(<<>>)) == <<>> 81 | } 82 | 83 | pub fn from_string_test() { 84 | // Regression test: no additional modification of the tree 85 | assert bytes_tree.to_bit_array(bytes_tree.from_string("")) == <<>> 86 | } 87 | 88 | pub fn new_test() { 89 | assert bytes_tree.to_bit_array(bytes_tree.new()) == <<>> 90 | } 91 | 92 | pub fn from_string_tree_test() { 93 | assert bytes_tree.from_string_tree(string_tree.from_string("hello")) 94 | == bytes_tree.from_string("hello") 95 | } 96 | -------------------------------------------------------------------------------- /test/gleeunit_ffi.mjs: -------------------------------------------------------------------------------- 1 | import { readFileSync } from "node:fs"; 2 | import { Ok, Error as GleamError } from "./gleam.mjs"; 3 | import * as reporting from "./gleeunit/internal/reporting.mjs"; 4 | 5 | export function read_file(path) { 6 | try { 7 | return new Ok(readFileSync(path)); 8 | } catch { 9 | return new GleamError(undefined); 10 | } 11 | } 12 | 13 | async function* gleamFiles(directory) { 14 | for (let entry of await read_dir(directory)) { 15 | let path = join_path(directory, entry); 16 | if (path.endsWith(".gleam")) { 17 | yield path; 18 | } else { 19 | try { 20 | yield* gleamFiles(path); 21 | } catch (error) { 22 | // Could not read directory, assume it's a file 23 | } 24 | } 25 | } 26 | } 27 | 28 | async function readRootPackageName() { 29 | let toml = await async_read_file("gleam.toml", "utf-8"); 30 | for (let line of toml.split("\n")) { 31 | let matches = line.match(/\s*name\s*=\s*"([a-z][a-z0-9_]*)"/); // Match regexp in compiler-cli/src/new.rs in validate_name() 32 | if (matches) return matches[1]; 33 | } 34 | throw new Error("Could not determine package name from gleam.toml"); 35 | } 36 | 37 | export async function main() { 38 | let state = reporting.new_state(); 39 | 40 | let packageName = await readRootPackageName(); 41 | let dist = `../${packageName}/`; 42 | 43 | for await (let path of await gleamFiles("test")) { 44 | let js_path = path.slice("test/".length).replace(".gleam", ".mjs"); 45 | let module = await import(join_path(dist, js_path)); 46 | for (let fnName of Object.keys(module)) { 47 | if (!fnName.endsWith("_test")) continue; 48 | try { 49 | await module[fnName](); 50 | state = reporting.test_passed(state); 51 | } catch (error) { 52 | let moduleName = js_path.slice(0, -4); 53 | state = reporting.test_failed(state, moduleName, fnName, error); 54 | } 55 | } 56 | } 57 | 58 | const status = reporting.finished(state); 59 | exit(status); 60 | } 61 | 62 | export function crash(message) { 63 | throw new Error(message); 64 | } 65 | 66 | function exit(code) { 67 | if (globalThis.Deno) { 68 | Deno.exit(code); 69 | } else { 70 | process.exit(code); 71 | } 72 | } 73 | 74 | async function read_dir(path) { 75 | if (globalThis.Deno) { 76 | let items = []; 77 | for await (let item of Deno.readDir(path, { withFileTypes: true })) { 78 | items.push(item.name); 79 | } 80 | return items; 81 | } else { 82 | let { readdir } = await import("node:fs/promises"); 83 | return readdir(path); 84 | } 85 | } 86 | 87 | function join_path(a, b) { 88 | if (a.endsWith("/")) return a + b; 89 | return a + "/" + b; 90 | } 91 | 92 | async function async_read_file(path) { 93 | if (globalThis.Deno) { 94 | return Deno.readTextFile(path); 95 | } else { 96 | let { readFile } = await import("node:fs/promises"); 97 | let contents = await readFile(path); 98 | return contents.toString(); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/gleam/order.gleam: -------------------------------------------------------------------------------- 1 | /// Represents the result of a single comparison to determine the precise 2 | /// ordering of two values. 3 | /// 4 | pub type Order { 5 | /// Less-than 6 | Lt 7 | 8 | /// Equal 9 | Eq 10 | 11 | /// Greater than 12 | Gt 13 | } 14 | 15 | /// Inverts an order, so less-than becomes greater-than and greater-than 16 | /// becomes less-than. 17 | /// 18 | /// ## Examples 19 | /// 20 | /// ```gleam 21 | /// negate(Lt) 22 | /// // -> Gt 23 | /// ``` 24 | /// 25 | /// ```gleam 26 | /// negate(Eq) 27 | /// // -> Eq 28 | /// ``` 29 | /// 30 | /// ```gleam 31 | /// negate(Gt) 32 | /// // -> Lt 33 | /// ``` 34 | /// 35 | pub fn negate(order: Order) -> Order { 36 | case order { 37 | Lt -> Gt 38 | Eq -> Eq 39 | Gt -> Lt 40 | } 41 | } 42 | 43 | /// Produces a numeric representation of the order. 44 | /// 45 | /// ## Examples 46 | /// 47 | /// ```gleam 48 | /// to_int(Lt) 49 | /// // -> -1 50 | /// ``` 51 | /// 52 | /// ```gleam 53 | /// to_int(Eq) 54 | /// // -> 0 55 | /// ``` 56 | /// 57 | /// ```gleam 58 | /// to_int(Gt) 59 | /// // -> 1 60 | /// ``` 61 | /// 62 | pub fn to_int(order: Order) -> Int { 63 | case order { 64 | Lt -> -1 65 | Eq -> 0 66 | Gt -> 1 67 | } 68 | } 69 | 70 | /// Compares two `Order` values to one another, producing a new `Order`. 71 | /// 72 | /// ## Examples 73 | /// 74 | /// ```gleam 75 | /// compare(Eq, with: Lt) 76 | /// // -> Gt 77 | /// ``` 78 | /// 79 | pub fn compare(a: Order, with b: Order) -> Order { 80 | case a, b { 81 | x, y if x == y -> Eq 82 | Lt, _ | Eq, Gt -> Lt 83 | _, _ -> Gt 84 | } 85 | } 86 | 87 | /// Inverts an ordering function, so less-than becomes greater-than and greater-than 88 | /// becomes less-than. 89 | /// 90 | /// ## Examples 91 | /// 92 | /// ```gleam 93 | /// import gleam/int 94 | /// import gleam/list 95 | /// 96 | /// list.sort([1, 5, 4], by: reverse(int.compare)) 97 | /// // -> [5, 4, 1] 98 | /// ``` 99 | /// 100 | pub fn reverse(orderer: fn(a, a) -> Order) -> fn(a, a) -> Order { 101 | fn(a, b) { orderer(b, a) } 102 | } 103 | 104 | /// Return a fallback `Order` in case the first argument is `Eq`. 105 | /// 106 | /// ## Examples 107 | /// 108 | /// ```gleam 109 | /// import gleam/int 110 | /// 111 | /// break_tie(in: int.compare(1, 1), with: Lt) 112 | /// // -> Lt 113 | /// ``` 114 | /// 115 | /// ```gleam 116 | /// import gleam/int 117 | /// 118 | /// break_tie(in: int.compare(1, 0), with: Eq) 119 | /// // -> Gt 120 | /// ``` 121 | /// 122 | pub fn break_tie(in order: Order, with other: Order) -> Order { 123 | case order { 124 | Lt | Gt -> order 125 | Eq -> other 126 | } 127 | } 128 | 129 | /// Invokes a fallback function returning an `Order` in case the first argument 130 | /// is `Eq`. 131 | /// 132 | /// This can be useful when the fallback comparison might be expensive and it 133 | /// needs to be delayed until strictly necessary. 134 | /// 135 | /// ## Examples 136 | /// 137 | /// ```gleam 138 | /// import gleam/int 139 | /// 140 | /// lazy_break_tie(in: int.compare(1, 1), with: fn() { Lt }) 141 | /// // -> Lt 142 | /// ``` 143 | /// 144 | /// ```gleam 145 | /// import gleam/int 146 | /// 147 | /// lazy_break_tie(in: int.compare(1, 0), with: fn() { Eq }) 148 | /// // -> Gt 149 | /// ``` 150 | /// 151 | pub fn lazy_break_tie(in order: Order, with comparison: fn() -> Order) -> Order { 152 | case order { 153 | Lt | Gt -> order 154 | Eq -> comparison() 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/gleam/dynamic.gleam: -------------------------------------------------------------------------------- 1 | import gleam/dict 2 | 3 | /// `Dynamic` data is data that we don't know the type of yet. 4 | /// We likely get data like this from interop with Erlang, or from 5 | /// IO with the outside world. 6 | /// 7 | /// This module contains code for forming dynamic data, and the 8 | /// `gleam/dynamic/decode` module contains code for turning dynamic data back 9 | /// into Gleam data with known types. You will likely mostly use the other 10 | /// module in your projects. 11 | /// 12 | /// The exact runtime representation of dynamic values will depend on the 13 | /// compilation target used. 14 | /// 15 | pub type Dynamic 16 | 17 | /// Return a string indicating the type of the dynamic value. 18 | /// 19 | /// This function may be useful for constructing error messages or logs. If you 20 | /// want to turn dynamic data into well typed data then you want the 21 | /// `gleam/dynamic/decode` module. 22 | /// 23 | /// ```gleam 24 | /// classify(string("Hello")) 25 | /// // -> "String" 26 | /// ``` 27 | /// 28 | @external(erlang, "gleam_stdlib", "classify_dynamic") 29 | @external(javascript, "../gleam_stdlib.mjs", "classify_dynamic") 30 | pub fn classify(data: Dynamic) -> String 31 | 32 | /// Create a dynamic value from a bool. 33 | /// 34 | @external(erlang, "gleam_stdlib", "identity") 35 | @external(javascript, "../gleam_stdlib.mjs", "identity") 36 | pub fn bool(a: Bool) -> Dynamic 37 | 38 | /// Create a dynamic value from a string. 39 | /// 40 | /// On Erlang this will be a binary string rather than a character list. 41 | /// 42 | @external(erlang, "gleam_stdlib", "identity") 43 | @external(javascript, "../gleam_stdlib.mjs", "identity") 44 | pub fn string(a: String) -> Dynamic 45 | 46 | /// Create a dynamic value from a float. 47 | /// 48 | @external(erlang, "gleam_stdlib", "identity") 49 | @external(javascript, "../gleam_stdlib.mjs", "identity") 50 | pub fn float(a: Float) -> Dynamic 51 | 52 | /// Create a dynamic value from an int. 53 | /// 54 | @external(erlang, "gleam_stdlib", "identity") 55 | @external(javascript, "../gleam_stdlib.mjs", "identity") 56 | pub fn int(a: Int) -> Dynamic 57 | 58 | /// Create a dynamic value from a bit array. 59 | /// 60 | @external(erlang, "gleam_stdlib", "identity") 61 | @external(javascript, "../gleam_stdlib.mjs", "identity") 62 | pub fn bit_array(a: BitArray) -> Dynamic 63 | 64 | /// Create a dynamic value from a list. 65 | /// 66 | @external(erlang, "gleam_stdlib", "identity") 67 | @external(javascript, "../gleam_stdlib.mjs", "identity") 68 | pub fn list(a: List(Dynamic)) -> Dynamic 69 | 70 | /// Create a dynamic value from a list, converting it to a sequential runtime 71 | /// format rather than the regular list format. 72 | /// 73 | /// On Erlang this will be a tuple, on JavaScript this will be an array. 74 | /// 75 | @external(erlang, "erlang", "list_to_tuple") 76 | @external(javascript, "../gleam_stdlib.mjs", "list_to_array") 77 | pub fn array(a: List(Dynamic)) -> Dynamic 78 | 79 | /// Create a dynamic value made an unordered series of keys and values, where 80 | /// the keys are unique. 81 | /// 82 | /// On Erlang this will be a map, on JavaScript this will be a Gleam dict 83 | /// object. 84 | /// 85 | pub fn properties(entries: List(#(Dynamic, Dynamic))) -> Dynamic { 86 | cast(dict.from_list(entries)) 87 | } 88 | 89 | /// A dynamic value representing nothing. 90 | /// 91 | /// On Erlang this will be the atom `nil`, on JavaScript this will be 92 | /// `undefined`. 93 | /// 94 | pub fn nil() -> Dynamic { 95 | cast(Nil) 96 | } 97 | 98 | @external(erlang, "gleam_stdlib", "identity") 99 | @external(javascript, "../gleam_stdlib.mjs", "identity") 100 | fn cast(a: anything) -> Dynamic 101 | -------------------------------------------------------------------------------- /test/gleam/string_tree_test.gleam: -------------------------------------------------------------------------------- 1 | import gleam/string_tree 2 | 3 | pub fn string_tree_test() { 4 | let data = 5 | string_tree.from_string("ello") 6 | |> string_tree.append(",") 7 | |> string_tree.append(" world!") 8 | |> string_tree.prepend("H") 9 | 10 | assert string_tree.to_string(data) == "Hello, world!" 11 | 12 | assert string_tree.byte_size(data) == 13 13 | 14 | let data = 15 | string_tree.from_string("ello") 16 | |> string_tree.append_tree(string_tree.from_string(",")) 17 | |> string_tree.append_tree( 18 | string_tree.concat([ 19 | string_tree.from_string(" wo"), 20 | string_tree.from_string("rld!"), 21 | ]), 22 | ) 23 | |> string_tree.prepend_tree(string_tree.from_string("H")) 24 | 25 | assert string_tree.to_string(data) == "Hello, world!" 26 | 27 | assert string_tree.byte_size(data) == 13 28 | } 29 | 30 | pub fn reverse_test() { 31 | assert "Ĺo͂řȩm̅" 32 | |> string_tree.from_string 33 | |> string_tree.reverse 34 | |> string_tree.reverse 35 | |> string_tree.to_string 36 | == "Ĺo͂řȩm̅" 37 | 38 | assert "Ĺo͂řȩm̅" 39 | |> string_tree.from_string 40 | |> string_tree.reverse 41 | |> string_tree.to_string 42 | == "m̅ȩřo͂Ĺ" 43 | 44 | assert "👶🏿" 45 | |> string_tree.from_string 46 | |> string_tree.reverse 47 | |> string_tree.reverse 48 | |> string_tree.to_string 49 | == "👶🏿" 50 | 51 | assert "👶🏿" 52 | |> string_tree.from_string 53 | |> string_tree.reverse 54 | |> string_tree.to_string 55 | == "👶🏿" 56 | } 57 | 58 | pub fn lowercase_test() { 59 | assert ["Gleam", "Gleam"] 60 | |> string_tree.from_strings 61 | |> string_tree.lowercase 62 | |> string_tree.to_string 63 | == "gleamgleam" 64 | } 65 | 66 | pub fn uppercase_test() { 67 | assert ["Gleam", "Gleam"] 68 | |> string_tree.from_strings 69 | |> string_tree.uppercase 70 | |> string_tree.to_string 71 | == "GLEAMGLEAM" 72 | } 73 | 74 | pub fn split_test() { 75 | assert "Gleam,Erlang,Elixir" 76 | |> string_tree.from_string 77 | |> string_tree.split(",") 78 | == [ 79 | string_tree.from_string("Gleam"), 80 | string_tree.from_string("Erlang"), 81 | string_tree.from_string("Elixir"), 82 | ] 83 | 84 | assert ["Gleam, Erl", "ang,Elixir"] 85 | |> string_tree.from_strings 86 | |> string_tree.split(", ") 87 | == [ 88 | string_tree.from_string("Gleam"), 89 | string_tree.from_strings(["Erl", "ang,Elixir"]), 90 | ] 91 | } 92 | 93 | pub fn is_equal_test() { 94 | assert string_tree.is_equal( 95 | string_tree.from_string("12"), 96 | string_tree.from_strings(["1", "2"]), 97 | ) 98 | 99 | assert string_tree.is_equal( 100 | string_tree.from_string("12"), 101 | string_tree.from_string("12"), 102 | ) 103 | 104 | assert !string_tree.is_equal( 105 | string_tree.from_string("12"), 106 | string_tree.from_string("2"), 107 | ) 108 | } 109 | 110 | pub fn is_empty_test() { 111 | assert string_tree.is_empty(string_tree.from_string("")) 112 | 113 | assert !string_tree.is_empty(string_tree.from_string("12")) 114 | 115 | assert string_tree.is_empty(string_tree.from_strings([])) 116 | 117 | assert string_tree.is_empty(string_tree.from_strings(["", ""])) 118 | } 119 | 120 | pub fn new_test() { 121 | assert string_tree.to_string(string_tree.new()) == "" 122 | } 123 | 124 | pub fn join_test() { 125 | assert [ 126 | string_tree.from_string("Gleam"), 127 | string_tree.from_string("Elixir"), 128 | string_tree.from_string("Erlang"), 129 | ] 130 | |> string_tree.join(", ") 131 | |> string_tree.to_string 132 | == "Gleam, Elixir, Erlang" 133 | } 134 | -------------------------------------------------------------------------------- /test/gleam/set_test.gleam: -------------------------------------------------------------------------------- 1 | import gleam/int 2 | import gleam/list 3 | import gleam/set 4 | 5 | pub fn size_test() { 6 | assert set.size(set.new()) == 0 7 | 8 | assert set.new() 9 | |> set.insert(1) 10 | |> set.insert(2) 11 | |> set.size 12 | == 2 13 | 14 | assert set.new() 15 | |> set.insert(1) 16 | |> set.insert(1) 17 | |> set.insert(2) 18 | |> set.size 19 | == 2 20 | } 21 | 22 | pub fn is_empty_test() { 23 | assert set.is_empty(set.new()) 24 | 25 | assert !{ 26 | set.new() 27 | |> set.insert(1) 28 | |> set.is_empty() 29 | } 30 | 31 | assert set.new() 32 | |> set.insert(1) 33 | |> set.delete(1) 34 | |> set.is_empty() 35 | } 36 | 37 | pub fn contains_test() { 38 | assert set.new() 39 | |> set.insert(1) 40 | |> set.contains(this: 1) 41 | 42 | assert !set.contains(set.new(), this: 1) 43 | } 44 | 45 | pub fn delete_test() { 46 | assert !{ 47 | set.new() 48 | |> set.insert(1) 49 | |> set.delete(1) 50 | |> set.contains(1) 51 | } 52 | } 53 | 54 | pub fn to_list_test() { 55 | assert set.new() 56 | |> set.insert(2) 57 | |> set.insert(3) 58 | |> set.insert(4) 59 | |> set.to_list 60 | |> list.sort(by: int.compare) 61 | == [2, 3, 4] 62 | } 63 | 64 | pub fn from_list_test() { 65 | assert [1, 1, 2, 4, 3, 2] 66 | |> set.from_list 67 | |> set.to_list 68 | |> list.sort(by: int.compare) 69 | == [1, 2, 3, 4] 70 | } 71 | 72 | pub fn fold_test() { 73 | assert [1, 3, 9] 74 | |> set.from_list 75 | |> set.fold(from: 0, with: fn(m, a) { m + a }) 76 | == 13 77 | } 78 | 79 | pub fn map_test() { 80 | assert [1, 2, 3, 4] 81 | |> set.from_list 82 | |> set.map(with: int.to_string) 83 | == set.from_list(["1", "2", "3", "4"]) 84 | } 85 | 86 | pub fn filter_test() { 87 | assert [1, 4, 6, 3, 675, 44, 67] 88 | |> set.from_list() 89 | |> set.filter(keeping: int.is_even) 90 | |> set.to_list 91 | |> list.sort(int.compare) 92 | == [4, 6, 44] 93 | } 94 | 95 | pub fn take_test() { 96 | assert [1, 2, 3] 97 | |> set.from_list 98 | |> set.take([1, 3, 5]) 99 | == set.from_list([1, 3]) 100 | } 101 | 102 | pub fn drop_test() { 103 | assert ["a", "b", "c"] 104 | |> set.from_list 105 | |> set.drop(["a", "b", "d"]) 106 | == set.from_list(["c"]) 107 | } 108 | 109 | pub fn union_test() { 110 | assert set.union(set.from_list([1, 2]), set.from_list([2, 3])) 111 | |> set.to_list 112 | |> list.sort(int.compare) 113 | == [1, 2, 3] 114 | } 115 | 116 | pub fn intersection_test() { 117 | assert set.to_list(set.intersection( 118 | set.from_list([1, 2]), 119 | set.from_list([2, 3]), 120 | )) 121 | == [2] 122 | } 123 | 124 | pub fn difference_test() { 125 | assert set.to_list(set.difference( 126 | set.from_list([1, 2]), 127 | set.from_list([2, 3, 4]), 128 | )) 129 | == [1] 130 | } 131 | 132 | pub fn is_subset_test() { 133 | assert !set.is_subset(set.from_list([1, 2, 3, 4]), set.from_list([2, 4])) 134 | 135 | assert set.is_subset(set.from_list([2, 4]), set.from_list([1, 2, 3, 4])) 136 | 137 | assert !set.is_subset(set.from_list([1, 2, 3]), set.from_list([4, 5, 6])) 138 | 139 | assert !set.is_subset(set.from_list([1, 2]), set.from_list([2, 3, 4])) 140 | } 141 | 142 | pub fn is_disjoint_test() { 143 | assert set.is_disjoint(set.from_list([1, 2, 3]), set.from_list([4, 5, 6])) 144 | 145 | assert !set.is_disjoint(set.from_list([1, 2]), set.from_list([2, 3, 4])) 146 | } 147 | 148 | pub fn symmetric_difference_test() { 149 | assert set.symmetric_difference( 150 | set.from_list([1, 2, 3]), 151 | set.from_list([3, 4]), 152 | ) 153 | == set.from_list([1, 2, 4]) 154 | } 155 | 156 | pub fn each_test() { 157 | assert [1, 2, 3] 158 | |> set.from_list 159 | |> set.each(fn(member) { 160 | case member { 161 | 1 | 2 | 3 -> Nil 162 | _ -> panic as "unexpected value" 163 | } 164 | }) 165 | == Nil 166 | } 167 | -------------------------------------------------------------------------------- /test/gleam/result_test.gleam: -------------------------------------------------------------------------------- 1 | import gleam/list 2 | import gleam/result 3 | 4 | pub fn is_ok_test() { 5 | assert result.is_ok(Ok(1)) 6 | 7 | assert !result.is_ok(Error(1)) 8 | } 9 | 10 | pub fn is_error_test() { 11 | assert !result.is_error(Ok(1)) 12 | 13 | assert result.is_error(Error(1)) 14 | } 15 | 16 | pub fn map_test() { 17 | assert result.map(Ok(1), fn(x) { x + 1 }) == Ok(2) 18 | 19 | assert result.map(Ok(1), fn(_) { "2" }) == Ok("2") 20 | 21 | assert result.map(Error(1), fn(x) { x + 1 }) == Error(1) 22 | } 23 | 24 | pub fn map_error_test() { 25 | assert result.map_error(Ok(1), fn(x) { x + 1 }) == Ok(1) 26 | 27 | assert result.map_error(Error(1), fn(x) { #("ok", x + 1) }) 28 | == Error(#("ok", 2)) 29 | } 30 | 31 | pub fn flatten_test() { 32 | assert result.flatten(Ok(Ok(1))) == Ok(1) 33 | 34 | assert result.flatten(Ok(Error(1))) == Error(1) 35 | 36 | assert result.flatten(Error(1)) == Error(1) 37 | 38 | assert result.flatten(Error(Error(1))) == Error(Error(1)) 39 | } 40 | 41 | pub fn try_test() { 42 | assert result.try(Error(1), fn(x) { Ok(x + 1) }) == Error(1) 43 | 44 | assert result.try(Ok(1), fn(x) { Ok(x + 1) }) == Ok(2) 45 | 46 | assert result.try(Ok(1), fn(_) { Ok("type change") }) == Ok("type change") 47 | 48 | assert result.try(Ok(1), fn(_) { Error(1) }) == Error(1) 49 | } 50 | 51 | pub fn unwrap_test() { 52 | assert result.unwrap(Ok(1), 50) == 1 53 | 54 | assert result.unwrap(Error("nope"), 50) == 50 55 | } 56 | 57 | pub fn unwrap_error_test() { 58 | assert result.unwrap_error(Error(1), 50) == 1 59 | 60 | assert result.unwrap_error(Ok("nope"), 50) == 50 61 | } 62 | 63 | pub fn lazy_unwrap_test() { 64 | assert result.lazy_unwrap(Ok(1), fn() { 50 }) == 1 65 | 66 | assert result.lazy_unwrap(Error("nope"), fn() { 50 }) == 50 67 | } 68 | 69 | pub fn or_test() { 70 | assert result.or(Ok(1), Ok(2)) == Ok(1) 71 | 72 | assert result.or(Ok(1), Error("Error 2")) == Ok(1) 73 | 74 | assert result.or(Error("Error 1"), Ok(2)) == Ok(2) 75 | 76 | assert result.or(Error("Error 1"), Error("Error 2")) == Error("Error 2") 77 | } 78 | 79 | pub fn lazy_or_test() { 80 | assert result.lazy_or(Ok(1), fn() { Ok(2) }) == Ok(1) 81 | 82 | assert result.lazy_or(Ok(1), fn() { Error("Error 2") }) == Ok(1) 83 | 84 | assert result.lazy_or(Error("Error 1"), fn() { Ok(2) }) == Ok(2) 85 | 86 | assert result.lazy_or(Error("Error 1"), fn() { Error("Error 2") }) 87 | == Error("Error 2") 88 | } 89 | 90 | pub fn all_test() { 91 | assert result.all([Ok(1), Ok(2), Ok(3)]) == Ok([1, 2, 3]) 92 | 93 | assert result.all([Ok(1), Error("a"), Error("b"), Ok(3)]) == Error("a") 94 | } 95 | 96 | pub fn partition_test() { 97 | assert result.partition([]) == #([], []) 98 | 99 | assert result.partition([Ok(1), Ok(2), Ok(3)]) == #([3, 2, 1], []) 100 | 101 | assert result.partition([Error("a"), Error("b"), Error("c")]) 102 | == #([], ["c", "b", "a"]) 103 | 104 | assert result.partition([Ok(1), Error("a"), Ok(2), Error("b"), Error("c")]) 105 | == #([2, 1], ["c", "b", "a"]) 106 | 107 | // TCO test 108 | let _ = 109 | list.repeat(Ok(1), 1_000_000) 110 | |> result.partition 111 | 112 | list.repeat(Error("a"), 1_000_000) 113 | |> result.partition 114 | } 115 | 116 | pub fn replace_error_test() { 117 | assert result.replace_error(Error(Nil), "Invalid") == Error("Invalid") 118 | } 119 | 120 | pub fn replace_error_with_ok_test() { 121 | assert result.replace_error(Ok(Nil), "Invalid") == Ok(Nil) 122 | } 123 | 124 | pub fn replace_test() { 125 | assert result.replace(Ok(Nil), "OK") == Ok("OK") 126 | } 127 | 128 | pub fn replace_with_ok_test() { 129 | assert result.replace(Error(Nil), "Invalid") == Error(Nil) 130 | } 131 | 132 | pub fn values_test() { 133 | assert result.values([Ok(1), Error(""), Ok(3)]) == [1, 3] 134 | } 135 | 136 | pub fn try_recover_test() { 137 | assert result.try_recover(Ok(1), fn(_) { panic }) == Ok(1) 138 | 139 | assert result.try_recover(Error(1), fn(n) { Ok(n + 1) }) == Ok(2) 140 | 141 | assert result.try_recover(Error(1), fn(_) { Error("failed to recover") }) 142 | == Error("failed to recover") 143 | } 144 | -------------------------------------------------------------------------------- /src/gleam/bytes_tree.gleam: -------------------------------------------------------------------------------- 1 | //// `BytesTree` is a type used for efficiently building binary content to be 2 | //// written to a file or a socket. Internally it is represented as tree so to 3 | //// append or prepend to a bytes tree is a constant time operation that 4 | //// allocates a new node in the tree without copying any of the content. When 5 | //// writing to an output stream the tree is traversed and the content is sent 6 | //// directly rather than copying it into a single buffer beforehand. 7 | //// 8 | //// If we append one bit array to another the bit arrays must be copied to a 9 | //// new location in memory so that they can sit together. This behaviour 10 | //// enables efficient reading of the data but copying can be expensive, 11 | //// especially if we want to join many bit arrays together. 12 | //// 13 | //// BytesTree is different in that it can be joined together in constant 14 | //// time using minimal memory, and then can be efficiently converted to a 15 | //// bit array using the `to_bit_array` function. 16 | //// 17 | //// Byte trees are always byte aligned, so that a number of bits that is not 18 | //// divisible by 8 will be padded with 0s. 19 | //// 20 | //// On Erlang this type is compatible with Erlang's iolists. 21 | 22 | import gleam/bit_array 23 | import gleam/list 24 | import gleam/string_tree.{type StringTree} 25 | 26 | pub opaque type BytesTree { 27 | Bytes(BitArray) 28 | Text(StringTree) 29 | Many(List(BytesTree)) 30 | } 31 | 32 | /// Create an empty `BytesTree`. Useful as the start of a pipe chaining many 33 | /// trees together. 34 | /// 35 | pub fn new() -> BytesTree { 36 | concat([]) 37 | } 38 | 39 | /// Prepends a bit array to the start of a bytes tree. 40 | /// 41 | /// Runs in constant time. 42 | /// 43 | pub fn prepend(to second: BytesTree, prefix first: BitArray) -> BytesTree { 44 | append_tree(from_bit_array(first), second) 45 | } 46 | 47 | /// Appends a bit array to the end of a bytes tree. 48 | /// 49 | /// Runs in constant time. 50 | /// 51 | pub fn append(to first: BytesTree, suffix second: BitArray) -> BytesTree { 52 | append_tree(first, from_bit_array(second)) 53 | } 54 | 55 | /// Prepends a bytes tree onto the start of another. 56 | /// 57 | /// Runs in constant time. 58 | /// 59 | pub fn prepend_tree(to second: BytesTree, prefix first: BytesTree) -> BytesTree { 60 | append_tree(first, second) 61 | } 62 | 63 | /// Appends a bytes tree onto the end of another. 64 | /// 65 | /// Runs in constant time. 66 | /// 67 | @external(erlang, "gleam_stdlib", "iodata_append") 68 | pub fn append_tree(to first: BytesTree, suffix second: BytesTree) -> BytesTree { 69 | case second { 70 | Many(trees) -> Many([first, ..trees]) 71 | Text(_) | Bytes(_) -> Many([first, second]) 72 | } 73 | } 74 | 75 | /// Prepends a string onto the start of a bytes tree. 76 | /// 77 | /// Runs in constant time when running on Erlang. 78 | /// Runs in linear time with the length of the string otherwise. 79 | /// 80 | pub fn prepend_string(to second: BytesTree, prefix first: String) -> BytesTree { 81 | append_tree(from_string(first), second) 82 | } 83 | 84 | /// Appends a string onto the end of a bytes tree. 85 | /// 86 | /// Runs in constant time when running on Erlang. 87 | /// Runs in linear time with the length of the string otherwise. 88 | /// 89 | pub fn append_string(to first: BytesTree, suffix second: String) -> BytesTree { 90 | append_tree(first, from_string(second)) 91 | } 92 | 93 | /// Joins a list of bytes trees into a single one. 94 | /// 95 | /// Runs in constant time. 96 | /// 97 | @external(erlang, "gleam_stdlib", "identity") 98 | pub fn concat(trees: List(BytesTree)) -> BytesTree { 99 | Many(trees) 100 | } 101 | 102 | /// Joins a list of bit arrays into a single bytes tree. 103 | /// 104 | /// Runs in constant time. 105 | /// 106 | pub fn concat_bit_arrays(bits: List(BitArray)) -> BytesTree { 107 | bits 108 | |> list.map(from_bit_array) 109 | |> concat() 110 | } 111 | 112 | /// Creates a new bytes tree from a string. 113 | /// 114 | /// Runs in constant time when running on Erlang. 115 | /// Runs in linear time otherwise. 116 | /// 117 | @external(erlang, "gleam_stdlib", "wrap_list") 118 | pub fn from_string(string: String) -> BytesTree { 119 | Text(string_tree.from_string(string)) 120 | } 121 | 122 | /// Creates a new bytes tree from a string tree. 123 | /// 124 | /// Runs in constant time when running on Erlang. 125 | /// Runs in linear time otherwise. 126 | /// 127 | @external(erlang, "gleam_stdlib", "wrap_list") 128 | pub fn from_string_tree(tree: string_tree.StringTree) -> BytesTree { 129 | Text(tree) 130 | } 131 | 132 | /// Creates a new bytes tree from a bit array. 133 | /// 134 | /// Runs in constant time. 135 | /// 136 | pub fn from_bit_array(bits: BitArray) -> BytesTree { 137 | bits 138 | |> bit_array.pad_to_bytes 139 | |> wrap_list 140 | } 141 | 142 | @external(erlang, "gleam_stdlib", "wrap_list") 143 | fn wrap_list(bits: BitArray) -> BytesTree { 144 | Bytes(bits) 145 | } 146 | 147 | /// Turns a bytes tree into a bit array. 148 | /// 149 | /// Runs in linear time. 150 | /// 151 | /// When running on Erlang this function is implemented natively by the 152 | /// virtual machine and is highly optimised. 153 | /// 154 | @external(erlang, "erlang", "list_to_bitstring") 155 | pub fn to_bit_array(tree: BytesTree) -> BitArray { 156 | [[tree]] 157 | |> to_list([]) 158 | |> list.reverse 159 | |> bit_array.concat 160 | } 161 | 162 | fn to_list(stack: List(List(BytesTree)), acc: List(BitArray)) -> List(BitArray) { 163 | case stack { 164 | [] -> acc 165 | 166 | [[], ..remaining_stack] -> to_list(remaining_stack, acc) 167 | 168 | [[Bytes(bits), ..rest], ..remaining_stack] -> 169 | to_list([rest, ..remaining_stack], [bits, ..acc]) 170 | 171 | [[Text(tree), ..rest], ..remaining_stack] -> { 172 | let bits = bit_array.from_string(string_tree.to_string(tree)) 173 | to_list([rest, ..remaining_stack], [bits, ..acc]) 174 | } 175 | 176 | [[Many(trees), ..rest], ..remaining_stack] -> 177 | to_list([trees, rest, ..remaining_stack], acc) 178 | } 179 | } 180 | 181 | /// Returns the size of the bytes tree's content in bytes. 182 | /// 183 | /// Runs in linear time. 184 | /// 185 | @external(erlang, "erlang", "iolist_size") 186 | pub fn byte_size(tree: BytesTree) -> Int { 187 | [[tree]] 188 | |> to_list([]) 189 | |> list.fold(0, fn(acc, bits) { bit_array.byte_size(bits) + acc }) 190 | } 191 | -------------------------------------------------------------------------------- /src/gleam/string_tree.gleam: -------------------------------------------------------------------------------- 1 | import gleam/list 2 | 3 | /// `StringTree` is a type used for efficiently building text content to be 4 | /// written to a file or a socket. Internally it is represented as tree so to 5 | /// append or prepend to a string tree is a constant time operation that 6 | /// allocates a new node in the tree without copying any of the content. When 7 | /// writing to an output stream the tree is traversed and the content is sent 8 | /// directly rather than copying it into a single buffer beforehand. 9 | /// 10 | /// On Erlang this type is compatible with Erlang's iodata. On JavaScript this 11 | /// type is compatible with normal strings. 12 | /// 13 | /// The BEAM virtual machine has an optimisation for appending strings, where it 14 | /// will mutate the string buffer when safe to do so, so if you are looking to 15 | /// build a string through appending many small strings then you may get better 16 | /// performance by not using a string tree. Always benchmark your performance 17 | /// sensitive code. 18 | /// 19 | pub type StringTree 20 | 21 | /// Create an empty `StringTree`. Useful as the start of a pipe chaining many 22 | /// trees together. 23 | /// 24 | pub fn new() -> StringTree { 25 | from_strings([]) 26 | } 27 | 28 | /// Prepends a `String` onto the start of some `StringTree`. 29 | /// 30 | /// Runs in constant time. 31 | /// 32 | pub fn prepend(to tree: StringTree, prefix prefix: String) -> StringTree { 33 | append_tree(from_string(prefix), tree) 34 | } 35 | 36 | /// Appends a `String` onto the end of some `StringTree`. 37 | /// 38 | /// Runs in constant time. 39 | /// 40 | pub fn append(to tree: StringTree, suffix second: String) -> StringTree { 41 | append_tree(tree, from_string(second)) 42 | } 43 | 44 | /// Prepends some `StringTree` onto the start of another. 45 | /// 46 | /// Runs in constant time. 47 | /// 48 | pub fn prepend_tree( 49 | to tree: StringTree, 50 | prefix prefix: StringTree, 51 | ) -> StringTree { 52 | append_tree(prefix, tree) 53 | } 54 | 55 | /// Appends some `StringTree` onto the end of another. 56 | /// 57 | /// Runs in constant time. 58 | /// 59 | @external(erlang, "gleam_stdlib", "iodata_append") 60 | @external(javascript, "../gleam_stdlib.mjs", "add") 61 | pub fn append_tree(to tree: StringTree, suffix suffix: StringTree) -> StringTree 62 | 63 | /// Converts a list of strings into a `StringTree`. 64 | /// 65 | /// Runs in constant time. 66 | /// 67 | @external(erlang, "gleam_stdlib", "identity") 68 | @external(javascript, "../gleam_stdlib.mjs", "concat") 69 | pub fn from_strings(strings: List(String)) -> StringTree 70 | 71 | /// Joins a list of trees into a single tree. 72 | /// 73 | /// Runs in constant time. 74 | /// 75 | @external(erlang, "gleam_stdlib", "identity") 76 | @external(javascript, "../gleam_stdlib.mjs", "concat") 77 | pub fn concat(trees: List(StringTree)) -> StringTree 78 | 79 | /// Converts a string into a `StringTree`. 80 | /// 81 | /// Runs in constant time. 82 | /// 83 | @external(erlang, "gleam_stdlib", "identity") 84 | @external(javascript, "../gleam_stdlib.mjs", "identity") 85 | pub fn from_string(string: String) -> StringTree 86 | 87 | /// Turns a `StringTree` into a `String` 88 | /// 89 | /// This function is implemented natively by the virtual machine and is highly 90 | /// optimised. 91 | /// 92 | @external(erlang, "unicode", "characters_to_binary") 93 | @external(javascript, "../gleam_stdlib.mjs", "identity") 94 | pub fn to_string(tree: StringTree) -> String 95 | 96 | /// Returns the size of the `StringTree` in bytes. 97 | /// 98 | @external(erlang, "erlang", "iolist_size") 99 | @external(javascript, "../gleam_stdlib.mjs", "length") 100 | pub fn byte_size(tree: StringTree) -> Int 101 | 102 | /// Joins the given trees into a new tree separated with the given string. 103 | /// 104 | pub fn join(trees: List(StringTree), with sep: String) -> StringTree { 105 | trees 106 | |> list.intersperse(from_string(sep)) 107 | |> concat 108 | } 109 | 110 | /// Converts a `StringTree` to a new one where the contents have been 111 | /// lowercased. 112 | /// 113 | @external(erlang, "string", "lowercase") 114 | @external(javascript, "../gleam_stdlib.mjs", "lowercase") 115 | pub fn lowercase(tree: StringTree) -> StringTree 116 | 117 | /// Converts a `StringTree` to a new one where the contents have been 118 | /// uppercased. 119 | /// 120 | @external(erlang, "string", "uppercase") 121 | @external(javascript, "../gleam_stdlib.mjs", "uppercase") 122 | pub fn uppercase(tree: StringTree) -> StringTree 123 | 124 | /// Converts a `StringTree` to a new one with the contents reversed. 125 | /// 126 | @external(erlang, "string", "reverse") 127 | pub fn reverse(tree: StringTree) -> StringTree { 128 | tree 129 | |> to_string 130 | |> do_to_graphemes 131 | |> list.reverse 132 | |> from_strings 133 | } 134 | 135 | @external(javascript, "../gleam_stdlib.mjs", "graphemes") 136 | fn do_to_graphemes(string: String) -> List(String) 137 | 138 | type Direction { 139 | All 140 | } 141 | 142 | /// Splits a `StringTree` on a given pattern into a list of trees. 143 | /// 144 | @external(javascript, "../gleam_stdlib.mjs", "split") 145 | pub fn split(tree: StringTree, on pattern: String) -> List(StringTree) { 146 | erl_split(tree, pattern, All) 147 | } 148 | 149 | @external(erlang, "string", "split") 150 | fn erl_split(a: StringTree, b: String, c: Direction) -> List(StringTree) 151 | 152 | /// Replaces all instances of a pattern with a given string substitute. 153 | /// 154 | @external(erlang, "gleam_stdlib", "string_replace") 155 | @external(javascript, "../gleam_stdlib.mjs", "string_replace") 156 | pub fn replace( 157 | in tree: StringTree, 158 | each pattern: String, 159 | with substitute: String, 160 | ) -> StringTree 161 | 162 | /// Compares two string trees to determine if they have the same textual 163 | /// content. 164 | /// 165 | /// Comparing two string trees using the `==` operator may return `False` even 166 | /// if they have the same content as they may have been build in different ways, 167 | /// so using this function is often preferred. 168 | /// 169 | /// ## Examples 170 | /// 171 | /// ```gleam 172 | /// from_strings(["a", "b"]) == from_string("ab") 173 | /// // -> False 174 | /// ``` 175 | /// 176 | /// ```gleam 177 | /// is_equal(from_strings(["a", "b"]), from_string("ab")) 178 | /// // -> True 179 | /// ``` 180 | /// 181 | @external(erlang, "string", "equal") 182 | pub fn is_equal(a: StringTree, b: StringTree) -> Bool { 183 | a == b 184 | } 185 | 186 | /// Inspects a `StringTree` to determine if it is equivalent to an empty string. 187 | /// 188 | /// ## Examples 189 | /// 190 | /// ```gleam 191 | /// from_string("ok") |> is_empty 192 | /// // -> False 193 | /// ``` 194 | /// 195 | /// ```gleam 196 | /// from_string("") |> is_empty 197 | /// // -> True 198 | /// ``` 199 | /// 200 | /// ```gleam 201 | /// from_strings([]) |> is_empty 202 | /// // -> True 203 | /// ``` 204 | /// 205 | @external(erlang, "string", "is_empty") 206 | pub fn is_empty(tree: StringTree) -> Bool { 207 | from_string("") == tree 208 | } 209 | -------------------------------------------------------------------------------- /test/gleeunit/internal/reporting.gleam: -------------------------------------------------------------------------------- 1 | import gleam/bit_array 2 | import gleam/dynamic 3 | import gleam/int 4 | import gleam/io 5 | import gleam/list 6 | import gleam/option.{type Option} 7 | import gleam/result 8 | import gleam/string 9 | import gleeunit/internal/gleam_panic.{type GleamPanic} 10 | 11 | pub type State { 12 | State(passed: Int, failed: Int, skipped: Int) 13 | } 14 | 15 | pub fn new_state() -> State { 16 | State(passed: 0, failed: 0, skipped: 0) 17 | } 18 | 19 | pub fn finished(state: State) -> Int { 20 | case state { 21 | State(passed: 0, failed: 0, skipped: 0) -> { 22 | io.println("\nNo tests found!") 23 | 1 24 | } 25 | State(failed: 0, skipped: 0, ..) -> { 26 | let message = "\n" <> int.to_string(state.passed) <> " tests, no failures" 27 | io.println(green(message)) 28 | 0 29 | } 30 | State(skipped: 0, ..) -> { 31 | let message = 32 | "\n" 33 | <> int.to_string(state.passed) 34 | <> " tests, " 35 | <> int.to_string(state.failed) 36 | <> " failures" 37 | io.println(red(message)) 38 | 0 39 | } 40 | State(failed: 0, ..) -> { 41 | let message = 42 | "\n" 43 | <> int.to_string(state.passed) 44 | <> " tests, 0 failures, " 45 | <> int.to_string(state.skipped) 46 | <> " skipped" 47 | io.println(yellow(message)) 48 | 1 49 | } 50 | State(..) -> { 51 | let message = 52 | "\n" 53 | <> int.to_string(state.passed) 54 | <> " tests, " 55 | <> int.to_string(state.failed) 56 | <> " failures, " 57 | <> " skipped" 58 | io.println(red(message)) 59 | 1 60 | } 61 | } 62 | } 63 | 64 | pub fn test_passed(state: State) -> State { 65 | io.print(green(".")) 66 | State(..state, passed: state.passed + 1) 67 | } 68 | 69 | pub fn test_failed( 70 | state: State, 71 | module: String, 72 | function: String, 73 | error: dynamic.Dynamic, 74 | ) -> State { 75 | let message = case gleam_panic.from_dynamic(error) { 76 | Ok(error) -> { 77 | let src = option.from_result(read_file(error.file)) 78 | format_gleam_error(error, module, function, src) 79 | } 80 | Error(_) -> format_unknown(module, function, error) 81 | } 82 | 83 | io.print("\n" <> message) 84 | State(..state, failed: state.failed + 1) 85 | } 86 | 87 | fn format_unknown( 88 | module: String, 89 | function: String, 90 | error: dynamic.Dynamic, 91 | ) -> String { 92 | string.concat([ 93 | grey(module <> "." <> function) <> "\n", 94 | "An unexpected error occurred:\n", 95 | "\n", 96 | " " <> string.inspect(error) <> "\n", 97 | ]) 98 | } 99 | 100 | fn format_gleam_error( 101 | error: GleamPanic, 102 | module: String, 103 | function: String, 104 | src: Option(BitArray), 105 | ) -> String { 106 | let location = grey(error.file <> ":" <> int.to_string(error.line)) 107 | 108 | case error.kind { 109 | gleam_panic.Panic -> { 110 | string.concat([ 111 | bold(red("panic")) <> " " <> location <> "\n", 112 | cyan(" test") <> ": " <> module <> "." <> function <> "\n", 113 | cyan(" info") <> ": " <> error.message <> "\n", 114 | ]) 115 | } 116 | 117 | gleam_panic.Todo -> { 118 | string.concat([ 119 | bold(yellow("todo")) <> " " <> location <> "\n", 120 | cyan(" test") <> ": " <> module <> "." <> function <> "\n", 121 | cyan(" info") <> ": " <> error.message <> "\n", 122 | ]) 123 | } 124 | 125 | gleam_panic.Assert(start:, end:, kind:, ..) -> { 126 | string.concat([ 127 | bold(red("assert")) <> " " <> location <> "\n", 128 | cyan(" test") <> ": " <> module <> "." <> function <> "\n", 129 | code_snippet(src, start, end), 130 | assert_info(kind), 131 | cyan(" info") <> ": " <> error.message <> "\n", 132 | ]) 133 | } 134 | 135 | gleam_panic.LetAssert(start:, end:, value:, ..) -> { 136 | string.concat([ 137 | bold(red("let assert")) <> " " <> location <> "\n", 138 | cyan(" test") <> ": " <> module <> "." <> function <> "\n", 139 | code_snippet(src, start, end), 140 | cyan("value") <> ": " <> string.inspect(value) <> "\n", 141 | cyan(" info") <> ": " <> error.message <> "\n", 142 | ]) 143 | } 144 | } 145 | } 146 | 147 | fn assert_info(kind: gleam_panic.AssertKind) -> String { 148 | case kind { 149 | gleam_panic.BinaryOperator(left:, right:, ..) -> { 150 | string.concat([assert_value(" left", left), assert_value("right", right)]) 151 | } 152 | 153 | gleam_panic.FunctionCall(arguments:) -> { 154 | arguments 155 | |> list.index_map(fn(e, i) { 156 | let number = string.pad_start(int.to_string(i), 5, " ") 157 | assert_value(number, e) 158 | }) 159 | |> string.concat 160 | } 161 | 162 | gleam_panic.OtherExpression(..) -> "" 163 | } 164 | } 165 | 166 | fn assert_value(name: String, value: gleam_panic.AssertedExpression) -> String { 167 | cyan(name) <> ": " <> inspect_value(value) <> "\n" 168 | } 169 | 170 | fn inspect_value(value: gleam_panic.AssertedExpression) -> String { 171 | case value.kind { 172 | gleam_panic.Unevaluated -> grey("unevaluated") 173 | gleam_panic.Literal(..) -> grey("literal") 174 | gleam_panic.Expression(value:) -> string.inspect(value) 175 | } 176 | } 177 | 178 | fn code_snippet(src: Option(BitArray), start: Int, end: Int) -> String { 179 | { 180 | use src <- result.try(option.to_result(src, Nil)) 181 | use snippet <- result.try(bit_array.slice(src, start, end - start)) 182 | use snippet <- result.try(bit_array.to_string(snippet)) 183 | let snippet = cyan(" code") <> ": " <> snippet <> "\n" 184 | Ok(snippet) 185 | } 186 | |> result.unwrap("") 187 | } 188 | 189 | pub fn test_skipped(state: State, module: String, function: String) -> State { 190 | io.print("\n" <> module <> "." <> function <> yellow(" skipped")) 191 | State(..state, skipped: state.skipped + 1) 192 | } 193 | 194 | fn bold(text: String) -> String { 195 | "\u{001b}[1m" <> text <> "\u{001b}[22m" 196 | } 197 | 198 | fn cyan(text: String) -> String { 199 | "\u{001b}[36m" <> text <> "\u{001b}[39m" 200 | } 201 | 202 | fn yellow(text: String) -> String { 203 | "\u{001b}[33m" <> text <> "\u{001b}[39m" 204 | } 205 | 206 | fn green(text: String) -> String { 207 | "\u{001b}[32m" <> text <> "\u{001b}[39m" 208 | } 209 | 210 | fn red(text: String) -> String { 211 | "\u{001b}[31m" <> text <> "\u{001b}[39m" 212 | } 213 | 214 | fn grey(text: String) -> String { 215 | "\u{001b}[90m" <> text <> "\u{001b}[39m" 216 | } 217 | 218 | @external(erlang, "file", "read_file") 219 | fn read_file(path: String) -> Result(BitArray, dynamic.Dynamic) { 220 | case read_file_text(path) { 221 | Ok(text) -> Ok(bit_array.from_string(text)) 222 | Error(e) -> Error(e) 223 | } 224 | } 225 | 226 | @external(javascript, "../../gleeunit_ffi.mjs", "read_file") 227 | fn read_file_text(path: String) -> Result(String, dynamic.Dynamic) 228 | -------------------------------------------------------------------------------- /src/gleam/bool.gleam: -------------------------------------------------------------------------------- 1 | //// A type with two possible values, `True` and `False`. Used to indicate whether 2 | //// things are... true or false! 3 | //// 4 | //// Often is it clearer and offers more type safety to define a custom type 5 | //// than to use `Bool`. For example, rather than having a `is_teacher: Bool` 6 | //// field consider having a `role: SchoolRole` field where `SchoolRole` is a custom 7 | //// type that can be either `Student` or `Teacher`. 8 | 9 | /// Returns the and of two bools, but it evaluates both arguments. 10 | /// 11 | /// It's the function equivalent of the `&&` operator. 12 | /// This function is useful in higher order functions or pipes. 13 | /// 14 | /// ## Examples 15 | /// 16 | /// ```gleam 17 | /// and(True, True) 18 | /// // -> True 19 | /// ``` 20 | /// 21 | /// ```gleam 22 | /// and(False, True) 23 | /// // -> False 24 | /// ``` 25 | /// 26 | /// ```gleam 27 | /// False |> and(True) 28 | /// // -> False 29 | /// ``` 30 | /// 31 | pub fn and(a: Bool, b: Bool) -> Bool { 32 | a && b 33 | } 34 | 35 | /// Returns the or of two bools, but it evaluates both arguments. 36 | /// 37 | /// It's the function equivalent of the `||` operator. 38 | /// This function is useful in higher order functions or pipes. 39 | /// 40 | /// ## Examples 41 | /// 42 | /// ```gleam 43 | /// or(True, True) 44 | /// // -> True 45 | /// ``` 46 | /// 47 | /// ```gleam 48 | /// or(False, True) 49 | /// // -> True 50 | /// ``` 51 | /// 52 | /// ```gleam 53 | /// False |> or(True) 54 | /// // -> True 55 | /// ``` 56 | /// 57 | pub fn or(a: Bool, b: Bool) -> Bool { 58 | a || b 59 | } 60 | 61 | /// Returns the opposite bool value. 62 | /// 63 | /// This is the same as the `!` or `not` operators in some other languages. 64 | /// 65 | /// ## Examples 66 | /// 67 | /// ```gleam 68 | /// negate(True) 69 | /// // -> False 70 | /// ``` 71 | /// 72 | /// ```gleam 73 | /// negate(False) 74 | /// // -> True 75 | /// ``` 76 | /// 77 | pub fn negate(bool: Bool) -> Bool { 78 | !bool 79 | } 80 | 81 | /// Returns the nor of two bools. 82 | /// 83 | /// ## Examples 84 | /// 85 | /// ```gleam 86 | /// nor(False, False) 87 | /// // -> True 88 | /// ``` 89 | /// 90 | /// ```gleam 91 | /// nor(False, True) 92 | /// // -> False 93 | /// ``` 94 | /// 95 | /// ```gleam 96 | /// nor(True, False) 97 | /// // -> False 98 | /// ``` 99 | /// 100 | /// ```gleam 101 | /// nor(True, True) 102 | /// // -> False 103 | /// ``` 104 | /// 105 | pub fn nor(a: Bool, b: Bool) -> Bool { 106 | !{ a || b } 107 | } 108 | 109 | /// Returns the nand of two bools. 110 | /// 111 | /// ## Examples 112 | /// 113 | /// ```gleam 114 | /// nand(False, False) 115 | /// // -> True 116 | /// ``` 117 | /// 118 | /// ```gleam 119 | /// nand(False, True) 120 | /// // -> True 121 | /// ``` 122 | /// 123 | /// ```gleam 124 | /// nand(True, False) 125 | /// // -> True 126 | /// ``` 127 | /// 128 | /// ```gleam 129 | /// nand(True, True) 130 | /// // -> False 131 | /// ``` 132 | /// 133 | pub fn nand(a: Bool, b: Bool) -> Bool { 134 | !{ a && b } 135 | } 136 | 137 | /// Returns the exclusive or of two bools. 138 | /// 139 | /// ## Examples 140 | /// 141 | /// ```gleam 142 | /// exclusive_or(False, False) 143 | /// // -> False 144 | /// ``` 145 | /// 146 | /// ```gleam 147 | /// exclusive_or(False, True) 148 | /// // -> True 149 | /// ``` 150 | /// 151 | /// ```gleam 152 | /// exclusive_or(True, False) 153 | /// // -> True 154 | /// ``` 155 | /// 156 | /// ```gleam 157 | /// exclusive_or(True, True) 158 | /// // -> False 159 | /// ``` 160 | /// 161 | pub fn exclusive_or(a: Bool, b: Bool) -> Bool { 162 | a != b 163 | } 164 | 165 | /// Returns the exclusive nor of two bools. 166 | /// 167 | /// ## Examples 168 | /// 169 | /// ```gleam 170 | /// exclusive_nor(False, False) 171 | /// // -> True 172 | /// ``` 173 | /// 174 | /// ```gleam 175 | /// exclusive_nor(False, True) 176 | /// // -> False 177 | /// ``` 178 | /// 179 | /// ```gleam 180 | /// exclusive_nor(True, False) 181 | /// // -> False 182 | /// ``` 183 | /// 184 | /// ```gleam 185 | /// exclusive_nor(True, True) 186 | /// // -> True 187 | /// ``` 188 | /// 189 | pub fn exclusive_nor(a: Bool, b: Bool) -> Bool { 190 | a == b 191 | } 192 | 193 | /// Returns a string representation of the given bool. 194 | /// 195 | /// ## Examples 196 | /// 197 | /// ```gleam 198 | /// to_string(True) 199 | /// // -> "True" 200 | /// ``` 201 | /// 202 | /// ```gleam 203 | /// to_string(False) 204 | /// // -> "False" 205 | /// ``` 206 | /// 207 | pub fn to_string(bool: Bool) -> String { 208 | case bool { 209 | False -> "False" 210 | True -> "True" 211 | } 212 | } 213 | 214 | /// Run a callback function if the given bool is `False`, otherwise return a 215 | /// default value. 216 | /// 217 | /// With a `use` expression this function can simulate the early-return pattern 218 | /// found in some other programming languages. 219 | /// 220 | /// In a procedural language: 221 | /// 222 | /// ```js 223 | /// if (predicate) return value; 224 | /// // ... 225 | /// ``` 226 | /// 227 | /// In Gleam with a `use` expression: 228 | /// 229 | /// ```gleam 230 | /// use <- guard(when: predicate, return: value) 231 | /// // ... 232 | /// ``` 233 | /// 234 | /// Like everything in Gleam `use` is an expression, so it short circuits the 235 | /// current block, not the entire function. As a result you can assign the value 236 | /// to a variable: 237 | /// 238 | /// ```gleam 239 | /// let x = { 240 | /// use <- guard(when: predicate, return: value) 241 | /// // ... 242 | /// } 243 | /// ``` 244 | /// 245 | /// Note that unlike in procedural languages the `return` value is evaluated 246 | /// even when the predicate is `False`, so it is advisable not to perform 247 | /// expensive computation nor side-effects there. 248 | /// 249 | /// 250 | /// ## Examples 251 | /// 252 | /// ```gleam 253 | /// let name = "" 254 | /// use <- guard(when: name == "", return: "Welcome!") 255 | /// "Hello, " <> name 256 | /// // -> "Welcome!" 257 | /// ``` 258 | /// 259 | /// ```gleam 260 | /// let name = "Kamaka" 261 | /// use <- guard(when: name == "", return: "Welcome!") 262 | /// "Hello, " <> name 263 | /// // -> "Hello, Kamaka" 264 | /// ``` 265 | /// 266 | pub fn guard( 267 | when requirement: Bool, 268 | return consequence: a, 269 | otherwise alternative: fn() -> a, 270 | ) -> a { 271 | case requirement { 272 | True -> consequence 273 | False -> alternative() 274 | } 275 | } 276 | 277 | /// Runs a callback function if the given bool is `True`, otherwise runs an 278 | /// alternative callback function. 279 | /// 280 | /// Useful when further computation should be delayed regardless of the given 281 | /// bool's value. 282 | /// 283 | /// See [`guard`](#guard) for more info. 284 | /// 285 | /// ## Examples 286 | /// 287 | /// ```gleam 288 | /// let name = "Kamaka" 289 | /// let inquiry = fn() { "How may we address you?" } 290 | /// use <- lazy_guard(when: name == "", return: inquiry) 291 | /// "Hello, " <> name 292 | /// // -> "Hello, Kamaka" 293 | /// ``` 294 | /// 295 | /// ```gleam 296 | /// import gleam/int 297 | /// 298 | /// let name = "" 299 | /// let greeting = fn() { "Hello, " <> name } 300 | /// use <- lazy_guard(when: name == "", otherwise: greeting) 301 | /// let number = int.random(99) 302 | /// let name = "User " <> int.to_string(number) 303 | /// "Welcome, " <> name 304 | /// // -> "Welcome, User 54" 305 | /// ``` 306 | /// 307 | pub fn lazy_guard( 308 | when requirement: Bool, 309 | return consequence: fn() -> a, 310 | otherwise alternative: fn() -> a, 311 | ) -> a { 312 | case requirement { 313 | True -> consequence() 314 | False -> alternative() 315 | } 316 | } 317 | -------------------------------------------------------------------------------- /src/gleam/option.gleam: -------------------------------------------------------------------------------- 1 | /// `Option` represents a value that may be present or not. `Some` means the value is 2 | /// present, `None` means the value is not. 3 | /// 4 | /// This is Gleam's alternative to having a value that could be Null, as is 5 | /// possible in some other languages. 6 | /// 7 | /// ## `Option` and `Result` 8 | /// 9 | /// In other languages fallible functions may return either `Result` or 10 | /// `Option` depending on whether there is more information to be given about the 11 | /// failure. In Gleam all fallible functions return `Result`, and `Nil` is used 12 | /// as the error if there is no extra detail to give. This consistency removes 13 | /// the boilerplate that would otherwise be needed to convert between `Option` 14 | /// and `Result` types, and makes APIs more predictable. 15 | /// 16 | /// The `Option` type should only be used for taking optional values as 17 | /// function arguments, or for storing them in other data structures. 18 | /// 19 | pub type Option(a) { 20 | Some(a) 21 | None 22 | } 23 | 24 | /// Combines a list of `Option`s into a single `Option`. 25 | /// If all elements in the list are `Some` then returns a `Some` holding the list of values. 26 | /// If any element is `None` then returns`None`. 27 | /// 28 | /// ## Examples 29 | /// 30 | /// ```gleam 31 | /// all([Some(1), Some(2)]) 32 | /// // -> Some([1, 2]) 33 | /// ``` 34 | /// 35 | /// ```gleam 36 | /// all([Some(1), None]) 37 | /// // -> None 38 | /// ``` 39 | /// 40 | pub fn all(list: List(Option(a))) -> Option(List(a)) { 41 | all_loop(list, []) 42 | } 43 | 44 | fn all_loop(list: List(Option(a)), acc: List(a)) -> Option(List(a)) { 45 | case list { 46 | [] -> Some(reverse(acc)) 47 | [None, ..] -> None 48 | [Some(first), ..rest] -> all_loop(rest, [first, ..acc]) 49 | } 50 | } 51 | 52 | // This is copied from the list module and not imported as importing it would 53 | // result in a circular dependency! 54 | @external(erlang, "lists", "reverse") 55 | fn reverse(list: List(a)) -> List(a) { 56 | reverse_and_prepend(list, []) 57 | } 58 | 59 | fn reverse_and_prepend(list prefix: List(a), to suffix: List(a)) -> List(a) { 60 | case prefix { 61 | [] -> suffix 62 | [first, ..rest] -> reverse_and_prepend(list: rest, to: [first, ..suffix]) 63 | } 64 | } 65 | 66 | /// Checks whether the `Option` is a `Some` value. 67 | /// 68 | /// ## Examples 69 | /// 70 | /// ```gleam 71 | /// is_some(Some(1)) 72 | /// // -> True 73 | /// ``` 74 | /// 75 | /// ```gleam 76 | /// is_some(None) 77 | /// // -> False 78 | /// ``` 79 | /// 80 | pub fn is_some(option: Option(a)) -> Bool { 81 | option != None 82 | } 83 | 84 | /// Checks whether the `Option` is a `None` value. 85 | /// 86 | /// ## Examples 87 | /// 88 | /// ```gleam 89 | /// is_none(Some(1)) 90 | /// // -> False 91 | /// ``` 92 | /// 93 | /// ```gleam 94 | /// is_none(None) 95 | /// // -> True 96 | /// ``` 97 | /// 98 | pub fn is_none(option: Option(a)) -> Bool { 99 | option == None 100 | } 101 | 102 | /// Converts an `Option` type to a `Result` type. 103 | /// 104 | /// ## Examples 105 | /// 106 | /// ```gleam 107 | /// to_result(Some(1), "some_error") 108 | /// // -> Ok(1) 109 | /// ``` 110 | /// 111 | /// ```gleam 112 | /// to_result(None, "some_error") 113 | /// // -> Error("some_error") 114 | /// ``` 115 | /// 116 | pub fn to_result(option: Option(a), e) -> Result(a, e) { 117 | case option { 118 | Some(a) -> Ok(a) 119 | None -> Error(e) 120 | } 121 | } 122 | 123 | /// Converts a `Result` type to an `Option` type. 124 | /// 125 | /// ## Examples 126 | /// 127 | /// ```gleam 128 | /// from_result(Ok(1)) 129 | /// // -> Some(1) 130 | /// ``` 131 | /// 132 | /// ```gleam 133 | /// from_result(Error("some_error")) 134 | /// // -> None 135 | /// ``` 136 | /// 137 | pub fn from_result(result: Result(a, e)) -> Option(a) { 138 | case result { 139 | Ok(a) -> Some(a) 140 | Error(_) -> None 141 | } 142 | } 143 | 144 | /// Extracts the value from an `Option`, returning a default value if there is none. 145 | /// 146 | /// ## Examples 147 | /// 148 | /// ```gleam 149 | /// unwrap(Some(1), 0) 150 | /// // -> 1 151 | /// ``` 152 | /// 153 | /// ```gleam 154 | /// unwrap(None, 0) 155 | /// // -> 0 156 | /// ``` 157 | /// 158 | pub fn unwrap(option: Option(a), or default: a) -> a { 159 | case option { 160 | Some(x) -> x 161 | None -> default 162 | } 163 | } 164 | 165 | /// Extracts the value from an `Option`, evaluating the default function if the option is `None`. 166 | /// 167 | /// ## Examples 168 | /// 169 | /// ```gleam 170 | /// lazy_unwrap(Some(1), fn() { 0 }) 171 | /// // -> 1 172 | /// ``` 173 | /// 174 | /// ```gleam 175 | /// lazy_unwrap(None, fn() { 0 }) 176 | /// // -> 0 177 | /// ``` 178 | /// 179 | pub fn lazy_unwrap(option: Option(a), or default: fn() -> a) -> a { 180 | case option { 181 | Some(x) -> x 182 | None -> default() 183 | } 184 | } 185 | 186 | /// Updates a value held within the `Some` of an `Option` by calling a given function 187 | /// on it. 188 | /// 189 | /// If the `Option` is a `None` rather than `Some`, the function is not called and the 190 | /// `Option` stays the same. 191 | /// 192 | /// ## Examples 193 | /// 194 | /// ```gleam 195 | /// map(over: Some(1), with: fn(x) { x + 1 }) 196 | /// // -> Some(2) 197 | /// ``` 198 | /// 199 | /// ```gleam 200 | /// map(over: None, with: fn(x) { x + 1 }) 201 | /// // -> None 202 | /// ``` 203 | /// 204 | pub fn map(over option: Option(a), with fun: fn(a) -> b) -> Option(b) { 205 | case option { 206 | Some(x) -> Some(fun(x)) 207 | None -> None 208 | } 209 | } 210 | 211 | /// Merges a nested `Option` into a single layer. 212 | /// 213 | /// ## Examples 214 | /// 215 | /// ```gleam 216 | /// flatten(Some(Some(1))) 217 | /// // -> Some(1) 218 | /// ``` 219 | /// 220 | /// ```gleam 221 | /// flatten(Some(None)) 222 | /// // -> None 223 | /// ``` 224 | /// 225 | /// ```gleam 226 | /// flatten(None) 227 | /// // -> None 228 | /// ``` 229 | /// 230 | pub fn flatten(option: Option(Option(a))) -> Option(a) { 231 | case option { 232 | Some(x) -> x 233 | None -> None 234 | } 235 | } 236 | 237 | /// Updates a value held within the `Some` of an `Option` by calling a given function 238 | /// on it, where the given function also returns an `Option`. The two options are 239 | /// then merged together into one `Option`. 240 | /// 241 | /// If the `Option` is a `None` rather than `Some` the function is not called and the 242 | /// option stays the same. 243 | /// 244 | /// This function is the equivalent of calling `map` followed by `flatten`, and 245 | /// it is useful for chaining together multiple functions that return `Option`. 246 | /// 247 | /// ## Examples 248 | /// 249 | /// ```gleam 250 | /// then(Some(1), fn(x) { Some(x + 1) }) 251 | /// // -> Some(2) 252 | /// ``` 253 | /// 254 | /// ```gleam 255 | /// then(Some(1), fn(x) { Some(#("a", x)) }) 256 | /// // -> Some(#("a", 1)) 257 | /// ``` 258 | /// 259 | /// ```gleam 260 | /// then(Some(1), fn(_) { None }) 261 | /// // -> None 262 | /// ``` 263 | /// 264 | /// ```gleam 265 | /// then(None, fn(x) { Some(x + 1) }) 266 | /// // -> None 267 | /// ``` 268 | /// 269 | pub fn then(option: Option(a), apply fun: fn(a) -> Option(b)) -> Option(b) { 270 | case option { 271 | Some(x) -> fun(x) 272 | None -> None 273 | } 274 | } 275 | 276 | /// Returns the first value if it is `Some`, otherwise returns the second value. 277 | /// 278 | /// ## Examples 279 | /// 280 | /// ```gleam 281 | /// or(Some(1), Some(2)) 282 | /// // -> Some(1) 283 | /// ``` 284 | /// 285 | /// ```gleam 286 | /// or(Some(1), None) 287 | /// // -> Some(1) 288 | /// ``` 289 | /// 290 | /// ```gleam 291 | /// or(None, Some(2)) 292 | /// // -> Some(2) 293 | /// ``` 294 | /// 295 | /// ```gleam 296 | /// or(None, None) 297 | /// // -> None 298 | /// ``` 299 | /// 300 | pub fn or(first: Option(a), second: Option(a)) -> Option(a) { 301 | case first { 302 | Some(_) -> first 303 | None -> second 304 | } 305 | } 306 | 307 | /// Returns the first value if it is `Some`, otherwise evaluates the given function for a fallback value. 308 | /// 309 | /// ## Examples 310 | /// 311 | /// ```gleam 312 | /// lazy_or(Some(1), fn() { Some(2) }) 313 | /// // -> Some(1) 314 | /// ``` 315 | /// 316 | /// ```gleam 317 | /// lazy_or(Some(1), fn() { None }) 318 | /// // -> Some(1) 319 | /// ``` 320 | /// 321 | /// ```gleam 322 | /// lazy_or(None, fn() { Some(2) }) 323 | /// // -> Some(2) 324 | /// ``` 325 | /// 326 | /// ```gleam 327 | /// lazy_or(None, fn() { None }) 328 | /// // -> None 329 | /// ``` 330 | /// 331 | pub fn lazy_or(first: Option(a), second: fn() -> Option(a)) -> Option(a) { 332 | case first { 333 | Some(_) -> first 334 | None -> second() 335 | } 336 | } 337 | 338 | /// Given a list of `Option`s, 339 | /// returns only the values inside `Some`. 340 | /// 341 | /// ## Examples 342 | /// 343 | /// ```gleam 344 | /// values([Some(1), None, Some(3)]) 345 | /// // -> [1, 3] 346 | /// ``` 347 | /// 348 | pub fn values(options: List(Option(a))) -> List(a) { 349 | values_loop(options, []) 350 | } 351 | 352 | fn values_loop(list: List(Option(a)), acc: List(a)) -> List(a) { 353 | case list { 354 | [] -> reverse(acc) 355 | [None, ..rest] -> values_loop(rest, acc) 356 | [Some(first), ..rest] -> values_loop(rest, [first, ..acc]) 357 | } 358 | } 359 | -------------------------------------------------------------------------------- /src/gleam/bit_array.gleam: -------------------------------------------------------------------------------- 1 | //// BitArrays are a sequence of binary data of any length. 2 | 3 | import gleam/int 4 | import gleam/order 5 | import gleam/string 6 | 7 | /// Converts a UTF-8 `String` type into a `BitArray`. 8 | /// 9 | @external(erlang, "gleam_stdlib", "identity") 10 | @external(javascript, "../gleam_stdlib.mjs", "bit_array_from_string") 11 | pub fn from_string(x: String) -> BitArray 12 | 13 | /// Returns an integer which is the number of bits in the bit array. 14 | /// 15 | @external(erlang, "erlang", "bit_size") 16 | @external(javascript, "../gleam_stdlib.mjs", "bit_array_bit_size") 17 | pub fn bit_size(x: BitArray) -> Int 18 | 19 | /// Returns an integer which is the number of bytes in the bit array. 20 | /// 21 | @external(erlang, "erlang", "byte_size") 22 | @external(javascript, "../gleam_stdlib.mjs", "bit_array_byte_size") 23 | pub fn byte_size(x: BitArray) -> Int 24 | 25 | /// Pads a bit array with zeros so that it is a whole number of bytes. 26 | /// 27 | @external(erlang, "gleam_stdlib", "bit_array_pad_to_bytes") 28 | @external(javascript, "../gleam_stdlib.mjs", "bit_array_pad_to_bytes") 29 | pub fn pad_to_bytes(x: BitArray) -> BitArray 30 | 31 | /// Creates a new bit array by joining two bit arrays. 32 | /// 33 | /// ## Examples 34 | /// 35 | /// ```gleam 36 | /// append(to: from_string("butter"), suffix: from_string("fly")) 37 | /// // -> from_string("butterfly") 38 | /// ``` 39 | /// 40 | pub fn append(to first: BitArray, suffix second: BitArray) -> BitArray { 41 | concat([first, second]) 42 | } 43 | 44 | /// Extracts a sub-section of a bit array. 45 | /// 46 | /// The slice will start at given position and continue up to specified 47 | /// length. 48 | /// A negative length can be used to extract bytes at the end of a bit array. 49 | /// 50 | /// This function runs in constant time. 51 | /// 52 | @external(erlang, "gleam_stdlib", "bit_array_slice") 53 | @external(javascript, "../gleam_stdlib.mjs", "bit_array_slice") 54 | pub fn slice( 55 | from string: BitArray, 56 | at position: Int, 57 | take length: Int, 58 | ) -> Result(BitArray, Nil) 59 | 60 | /// Tests to see whether a bit array is valid UTF-8. 61 | /// 62 | pub fn is_utf8(bits: BitArray) -> Bool { 63 | is_utf8_loop(bits) 64 | } 65 | 66 | @target(erlang) 67 | fn is_utf8_loop(bits: BitArray) -> Bool { 68 | case bits { 69 | <<>> -> True 70 | <<_:utf8, rest:bytes>> -> is_utf8_loop(rest) 71 | _ -> False 72 | } 73 | } 74 | 75 | @target(javascript) 76 | fn is_utf8_loop(bits: BitArray) -> Bool { 77 | case to_string(bits) { 78 | Ok(_) -> True 79 | Error(_) -> False 80 | } 81 | } 82 | 83 | /// Converts a bit array to a string. 84 | /// 85 | /// Returns an error if the bit array is invalid UTF-8 data. 86 | /// 87 | @external(javascript, "../gleam_stdlib.mjs", "bit_array_to_string") 88 | pub fn to_string(bits: BitArray) -> Result(String, Nil) { 89 | case is_utf8(bits) { 90 | True -> Ok(unsafe_to_string(bits)) 91 | False -> Error(Nil) 92 | } 93 | } 94 | 95 | @external(erlang, "gleam_stdlib", "identity") 96 | fn unsafe_to_string(a: BitArray) -> String 97 | 98 | /// Creates a new bit array by joining multiple binaries. 99 | /// 100 | /// ## Examples 101 | /// 102 | /// ```gleam 103 | /// concat([from_string("butter"), from_string("fly")]) 104 | /// // -> from_string("butterfly") 105 | /// ``` 106 | /// 107 | @external(erlang, "gleam_stdlib", "bit_array_concat") 108 | @external(javascript, "../gleam_stdlib.mjs", "bit_array_concat") 109 | pub fn concat(bit_arrays: List(BitArray)) -> BitArray 110 | 111 | /// Encodes a BitArray into a base 64 encoded string. 112 | /// 113 | /// If the bit array does not contain a whole number of bytes then it is padded 114 | /// with zero bits prior to being encoded. 115 | /// 116 | @external(erlang, "gleam_stdlib", "base64_encode") 117 | @external(javascript, "../gleam_stdlib.mjs", "base64_encode") 118 | pub fn base64_encode(input: BitArray, padding: Bool) -> String 119 | 120 | /// Decodes a base 64 encoded string into a `BitArray`. 121 | /// 122 | pub fn base64_decode(encoded: String) -> Result(BitArray, Nil) { 123 | let padded = case byte_size(from_string(encoded)) % 4 { 124 | 0 -> encoded 125 | n -> string.append(encoded, string.repeat("=", 4 - n)) 126 | } 127 | decode64(padded) 128 | } 129 | 130 | @external(erlang, "gleam_stdlib", "base64_decode") 131 | @external(javascript, "../gleam_stdlib.mjs", "base64_decode") 132 | fn decode64(a: String) -> Result(BitArray, Nil) 133 | 134 | /// Encodes a `BitArray` into a base 64 encoded string with URL and filename 135 | /// safe alphabet. 136 | /// 137 | /// If the bit array does not contain a whole number of bytes then it is padded 138 | /// with zero bits prior to being encoded. 139 | /// 140 | pub fn base64_url_encode(input: BitArray, padding: Bool) -> String { 141 | input 142 | |> base64_encode(padding) 143 | |> string.replace("+", "-") 144 | |> string.replace("/", "_") 145 | } 146 | 147 | /// Decodes a base 64 encoded string with URL and filename safe alphabet into a 148 | /// `BitArray`. 149 | /// 150 | pub fn base64_url_decode(encoded: String) -> Result(BitArray, Nil) { 151 | encoded 152 | |> string.replace("-", "+") 153 | |> string.replace("_", "/") 154 | |> base64_decode() 155 | } 156 | 157 | /// Encodes a `BitArray` into a base 16 encoded string. 158 | /// 159 | /// If the bit array does not contain a whole number of bytes then it is padded 160 | /// with zero bits prior to being encoded. 161 | /// 162 | @external(erlang, "gleam_stdlib", "base16_encode") 163 | @external(javascript, "../gleam_stdlib.mjs", "base16_encode") 164 | pub fn base16_encode(input: BitArray) -> String 165 | 166 | /// Decodes a base 16 encoded string into a `BitArray`. 167 | /// 168 | @external(erlang, "gleam_stdlib", "base16_decode") 169 | @external(javascript, "../gleam_stdlib.mjs", "base16_decode") 170 | pub fn base16_decode(input: String) -> Result(BitArray, Nil) 171 | 172 | /// Converts a bit array to a string containing the decimal value of each byte. 173 | /// 174 | /// Use this over `string.inspect` when you have a bit array you want printed 175 | /// in the array syntax even if it is valid UTF-8. 176 | /// 177 | /// ## Examples 178 | /// 179 | /// ```gleam 180 | /// inspect(<<0, 20, 0x20, 255>>) 181 | /// // -> "<<0, 20, 32, 255>>" 182 | /// 183 | /// inspect(<<100, 5:3>>) 184 | /// // -> "<<100, 5:size(3)>>" 185 | /// ``` 186 | /// 187 | pub fn inspect(input: BitArray) -> String { 188 | inspect_loop(input, "<<") <> ">>" 189 | } 190 | 191 | fn inspect_loop(input: BitArray, accumulator: String) -> String { 192 | case input { 193 | <<>> -> accumulator 194 | 195 | <> -> accumulator <> int.to_string(x) <> ":size(1)" 196 | <> -> accumulator <> int.to_string(x) <> ":size(2)" 197 | <> -> accumulator <> int.to_string(x) <> ":size(3)" 198 | <> -> accumulator <> int.to_string(x) <> ":size(4)" 199 | <> -> accumulator <> int.to_string(x) <> ":size(5)" 200 | <> -> accumulator <> int.to_string(x) <> ":size(6)" 201 | <> -> accumulator <> int.to_string(x) <> ":size(7)" 202 | 203 | <> -> { 204 | let suffix = case rest { 205 | <<>> -> "" 206 | _ -> ", " 207 | } 208 | 209 | let accumulator = accumulator <> int.to_string(x) <> suffix 210 | inspect_loop(rest, accumulator) 211 | } 212 | 213 | _ -> accumulator 214 | } 215 | } 216 | 217 | /// Compare two bit arrays as sequences of bytes. 218 | /// 219 | /// ## Examples 220 | /// 221 | /// ```gleam 222 | /// compare(<<1>>, <<2>>) 223 | /// // -> Lt 224 | /// 225 | /// compare(<<"AB":utf8>>, <<"AA":utf8>>) 226 | /// // -> Gt 227 | /// 228 | /// compare(<<1, 2:size(2)>>, with: <<1, 2:size(2)>>) 229 | /// // -> Eq 230 | /// ``` 231 | /// 232 | pub fn compare(a: BitArray, with b: BitArray) -> order.Order { 233 | case a, b { 234 | <>, <> -> 235 | case first_byte, second_byte { 236 | f, s if f > s -> order.Gt 237 | f, s if f < s -> order.Lt 238 | _, _ -> compare(first_rest, second_rest) 239 | } 240 | 241 | <<>>, <<>> -> order.Eq 242 | // First has more items, example: "AB" > "A": 243 | _, <<>> -> order.Gt 244 | // Second has more items, example: "A" < "AB": 245 | <<>>, _ -> order.Lt 246 | // This happens when there's unusually sized elements. 247 | // Handle these special cases via custom erlang function. 248 | first, second -> 249 | case bit_array_to_int_and_size(first), bit_array_to_int_and_size(second) { 250 | #(a, _), #(b, _) if a > b -> order.Gt 251 | #(a, _), #(b, _) if a < b -> order.Lt 252 | #(_, size_a), #(_, size_b) if size_a > size_b -> order.Gt 253 | #(_, size_a), #(_, size_b) if size_a < size_b -> order.Lt 254 | _, _ -> order.Eq 255 | } 256 | } 257 | } 258 | 259 | @external(erlang, "gleam_stdlib", "bit_array_to_int_and_size") 260 | @external(javascript, "../gleam_stdlib.mjs", "bit_array_to_int_and_size") 261 | fn bit_array_to_int_and_size(a: BitArray) -> #(Int, Int) 262 | 263 | /// Checks whether the first `BitArray` starts with the second one. 264 | /// 265 | /// ## Examples 266 | /// 267 | /// ```gleam 268 | /// starts_with(<<1, 2, 3, 4>>, <<1, 2>>) 269 | /// // -> True 270 | /// ``` 271 | /// 272 | @external(javascript, "../gleam_stdlib.mjs", "bit_array_starts_with") 273 | pub fn starts_with(bits: BitArray, prefix: BitArray) -> Bool { 274 | let prefix_size = bit_size(prefix) 275 | 276 | case bits { 277 | <> if pref == prefix -> True 278 | _ -> False 279 | } 280 | } 281 | -------------------------------------------------------------------------------- /test/gleam/int_test.gleam: -------------------------------------------------------------------------------- 1 | import gleam/int 2 | import gleam/list 3 | import gleam/order 4 | 5 | pub fn absolute_value_test() { 6 | assert int.absolute_value(123) == 123 7 | 8 | assert int.absolute_value(-123) == 123 9 | } 10 | 11 | pub fn clamp_test() { 12 | assert int.clamp(40, min: 30, max: 50) == 40 13 | 14 | assert int.clamp(20, min: 30, max: 50) == 30 15 | 16 | assert int.clamp(60, min: 30, max: 50) == 50 17 | 18 | assert int.clamp(100, min: 50, max: 30) == 50 19 | 20 | assert int.clamp(40, min: 50, max: 30) == 40 21 | } 22 | 23 | pub fn to_string_test() { 24 | assert int.to_string(123) == "123" 25 | 26 | assert int.to_string(-123) == "-123" 27 | 28 | assert int.to_string(123) == "123" 29 | } 30 | 31 | pub fn parse_test() { 32 | assert int.parse("123") == Ok(123) 33 | 34 | assert int.parse("-123") == Ok(-123) 35 | 36 | assert int.parse("0123") == Ok(123) 37 | 38 | assert int.parse("") == Error(Nil) 39 | 40 | assert int.parse("what") == Error(Nil) 41 | 42 | assert int.parse("1.23") == Error(Nil) 43 | } 44 | 45 | pub fn base_parse_test() { 46 | assert int.base_parse("100", 16) == Ok(256) 47 | 48 | assert int.base_parse("-100", 16) == Ok(-256) 49 | 50 | assert int.base_parse("100", 1) == Error(Nil) 51 | 52 | assert int.base_parse("100", 37) == Error(Nil) 53 | 54 | assert int.base_parse("AG", 16) == Error(Nil) 55 | } 56 | 57 | pub fn to_base_string_test() { 58 | assert int.to_base_string(100, 16) == Ok("64") 59 | 60 | assert int.to_base_string(-100, 16) == Ok("-64") 61 | 62 | assert int.to_base_string(100, 1) == Error(Nil) 63 | 64 | assert int.to_base_string(100, 37) == Error(Nil) 65 | } 66 | 67 | pub fn to_base2_test() { 68 | assert int.to_base2(100) == "1100100" 69 | 70 | assert int.to_base2(-100) == "-1100100" 71 | } 72 | 73 | pub fn to_base8_test() { 74 | assert int.to_base8(100) == "144" 75 | 76 | assert int.to_base8(-100) == "-144" 77 | } 78 | 79 | pub fn to_base16_test() { 80 | assert int.to_base16(100) == "64" 81 | 82 | assert int.to_base16(-100) == "-64" 83 | 84 | assert int.to_base16(43_981) == "ABCD" 85 | 86 | assert int.to_base16(-43_981) == "-ABCD" 87 | } 88 | 89 | pub fn to_base36_test() { 90 | assert int.to_base36(100) == "2S" 91 | 92 | assert int.to_base36(-100) == "-2S" 93 | } 94 | 95 | pub fn to_float_test() { 96 | assert int.to_float(1) == 1.0 97 | 98 | assert int.to_float(5) == 5.0 99 | 100 | assert int.to_float(0) == 0.0 101 | 102 | assert int.to_float(-5) == -5.0 103 | } 104 | 105 | pub fn compare_test() { 106 | assert int.compare(0, 0) == order.Eq 107 | 108 | assert int.compare(1, 1) == order.Eq 109 | 110 | assert int.compare(0, 1) == order.Lt 111 | 112 | assert int.compare(-2, -1) == order.Lt 113 | 114 | assert int.compare(2, 1) == order.Gt 115 | 116 | assert int.compare(-1, -2) == order.Gt 117 | } 118 | 119 | pub fn min_test() { 120 | assert int.min(0, 0) == 0 121 | 122 | assert int.min(0, 1) == 0 123 | 124 | assert int.min(1, 0) == 0 125 | 126 | assert int.min(-1, 2) == -1 127 | 128 | assert int.min(2, -2) == -2 129 | 130 | assert int.min(-1, -1) == -1 131 | } 132 | 133 | pub fn max_test() { 134 | assert int.max(0, 0) == 0 135 | 136 | assert int.max(0, 1) == 1 137 | 138 | assert int.max(1, 0) == 1 139 | 140 | assert int.max(-1, 2) == 2 141 | 142 | assert int.max(2, -2) == 2 143 | 144 | assert int.max(-1, -1) == -1 145 | } 146 | 147 | pub fn is_even_test() { 148 | assert int.is_even(0) 149 | 150 | assert int.is_even(2) 151 | 152 | assert int.is_even(-2) 153 | 154 | assert int.is_even(10_006) 155 | 156 | assert !int.is_even(1) 157 | 158 | assert !int.is_even(-3) 159 | 160 | assert !int.is_even(10_005) 161 | } 162 | 163 | pub fn is_odd_test() { 164 | assert !int.is_odd(0) 165 | 166 | assert !int.is_odd(2) 167 | 168 | assert !int.is_odd(-2) 169 | 170 | assert !int.is_odd(10_006) 171 | 172 | assert int.is_odd(1) 173 | 174 | assert int.is_odd(-3) 175 | 176 | assert int.is_odd(10_005) 177 | } 178 | 179 | pub fn power_test() { 180 | assert int.power(2, 2.0) == Ok(4.0) 181 | 182 | assert int.power(-5, 3.0) == Ok(-125.0) 183 | 184 | assert int.power(10, 0.0) == Ok(1.0) 185 | 186 | assert int.power(16, 0.5) == Ok(4.0) 187 | 188 | assert int.power(2, -1.0) == Ok(0.5) 189 | 190 | // int.power(-1, 0.5) is equivalent to int.square_root(-1) and should 191 | // return an error as an imaginary number would otherwise have to be 192 | // returned 193 | assert int.power(-1, 0.5) == Error(Nil) 194 | 195 | // Check another case with a negative base and fractional exponent 196 | assert int.power(-1, 1.5) == Error(Nil) 197 | 198 | // float.power(0, -1) is equivalent to 1 / 0 and is expected 199 | // to be an error 200 | assert int.power(0, -1.0) == Error(Nil) 201 | 202 | // Check that a negative base and exponent is fine as long as the 203 | // exponent is not fractional 204 | assert int.power(-2, -1.0) == Ok(-0.5) 205 | } 206 | 207 | pub fn square_root_test() { 208 | assert int.square_root(4) == Ok(2.0) 209 | 210 | assert int.square_root(16) == Ok(4.0) 211 | 212 | assert int.square_root(0) == Ok(0.0) 213 | 214 | assert int.square_root(-4) == Error(Nil) 215 | } 216 | 217 | pub fn negate_test() { 218 | assert int.negate(-1) == 1 219 | 220 | assert int.negate(2) == -2 221 | 222 | assert int.negate(0) == 0 223 | } 224 | 225 | pub fn sum_test() { 226 | assert int.sum([]) == 0 227 | 228 | assert int.sum([1, 2, 3]) == 6 229 | } 230 | 231 | pub fn product_test() { 232 | assert int.product([]) == 1 233 | 234 | assert int.product([4]) == 4 235 | 236 | assert int.product([1, 2, 3]) == 6 237 | } 238 | 239 | pub fn random_test() { 240 | use _ <- list.each(list.range(0, 100)) 241 | 242 | assert int.random(0) == 0 243 | 244 | assert int.random(1) == 0 245 | 246 | assert int.random(-1) == -1 247 | 248 | assert list.contains([0, 1], int.random(2)) 249 | 250 | assert list.contains([0, 1, 2], int.random(3)) 251 | } 252 | 253 | pub fn divide_test() { 254 | assert int.divide(1, 1) == Ok(1) 255 | 256 | assert int.divide(1, 0) == Error(Nil) 257 | 258 | assert int.divide(0, by: 1) == Ok(0) 259 | 260 | assert int.divide(1, by: 0) == Error(Nil) 261 | 262 | assert int.divide(5, by: 2) == Ok(2) 263 | 264 | assert int.divide(-99, by: 2) == Ok(-49) 265 | } 266 | 267 | pub fn remainder_test() { 268 | assert int.remainder(3, 2) == Ok(1) 269 | 270 | assert int.remainder(1, 0) == Error(Nil) 271 | 272 | assert int.remainder(10, -1) == Ok(0) 273 | 274 | assert int.remainder(13, by: 3) == Ok(1) 275 | 276 | assert int.remainder(-13, by: 3) == Ok(-1) 277 | 278 | assert int.remainder(13, by: -3) == Ok(1) 279 | 280 | assert int.remainder(-13, by: -3) == Ok(-1) 281 | } 282 | 283 | pub fn modulo_test() { 284 | assert int.modulo(3, 2) == Ok(1) 285 | 286 | assert int.modulo(1, 0) == Error(Nil) 287 | 288 | assert int.modulo(10, -1) == Ok(0) 289 | 290 | assert int.modulo(13, by: 3) == Ok(1) 291 | 292 | assert int.modulo(-13, by: 3) == Ok(2) 293 | 294 | assert int.modulo(13, by: -3) == Ok(-2) 295 | 296 | assert int.modulo(-13, by: -3) == Ok(-1) 297 | } 298 | 299 | pub fn floor_divide_test() { 300 | assert int.floor_divide(1, 1) == Ok(1) 301 | 302 | assert int.floor_divide(1, 0) == Error(Nil) 303 | 304 | assert int.floor_divide(0, by: 1) == Ok(0) 305 | 306 | assert int.floor_divide(1, by: 0) == Error(Nil) 307 | 308 | assert int.floor_divide(5, by: 2) == Ok(2) 309 | 310 | assert int.floor_divide(6, by: -4) == Ok(-2) 311 | 312 | assert int.floor_divide(-99, by: 2) == Ok(-50) 313 | 314 | assert int.floor_divide(-1, by: 2) == Ok(-1) 315 | } 316 | 317 | pub fn add_test() { 318 | assert int.add(1, 2) == 3 319 | 320 | assert int.add(3, 2) == 5 321 | } 322 | 323 | pub fn multiply_test() { 324 | assert int.multiply(2, 4) == 8 325 | 326 | assert int.multiply(3, 2) == 6 327 | } 328 | 329 | pub fn subtract_test() { 330 | assert int.subtract(3, 1) == 2 331 | 332 | assert int.subtract(3, 2) == 1 333 | 334 | assert int.subtract(2, 3) == -1 335 | } 336 | 337 | pub fn and_test() { 338 | assert int.bitwise_and(9, 3) == 1 339 | 340 | // To check compatibility with JavaScript, try a 32 bit unsigned integer 341 | // (signed integers are in the range -2147483648 to +2147483647, while 342 | // 32 bit unsigned integers are in the range 0 to +4294967295). 343 | assert int.bitwise_and(2_147_483_648, 2_147_483_648) == 2_147_483_648 344 | } 345 | 346 | pub fn not_test() { 347 | assert int.bitwise_not(2) == -3 348 | 349 | // To check compatibility with JavaScript, try a 32 bit unsigned integer. 350 | assert int.bitwise_not(2_147_483_648) == -2_147_483_649 351 | } 352 | 353 | pub fn or_test() { 354 | assert int.bitwise_or(9, 3) == 11 355 | 356 | // To check compatibility with JavaScript, try a 32 bit unsigned integer. 357 | assert int.bitwise_or(1, 2_147_483_648) == 2_147_483_649 358 | } 359 | 360 | pub fn exclusive_or_test() { 361 | assert int.bitwise_exclusive_or(9, 3) == 10 362 | 363 | // To check compatibility with JavaScript, try a 32 bit unsigned integer. 364 | assert int.bitwise_exclusive_or(0, 2_147_483_648) == 2_147_483_648 365 | } 366 | 367 | pub fn shift_left_test() { 368 | assert int.bitwise_shift_left(1, 2) == 4 369 | 370 | assert int.bitwise_shift_left(1, -2) == 0 371 | 372 | assert int.bitwise_shift_left(-1, 2) == -4 373 | 374 | assert int.bitwise_shift_left(-1, -2) == -1 375 | } 376 | 377 | pub fn shift_right_test() { 378 | assert int.bitwise_shift_right(1, 2) == 0 379 | 380 | assert int.bitwise_shift_right(1, -2) == 4 381 | 382 | assert int.bitwise_shift_right(-1, 2) == -1 383 | 384 | assert int.bitwise_shift_right(-1, -2) == -4 385 | } 386 | -------------------------------------------------------------------------------- /src/gleam/set.gleam: -------------------------------------------------------------------------------- 1 | import gleam/dict.{type Dict} 2 | import gleam/list 3 | import gleam/result 4 | 5 | // A list is used as the dict value as an empty list has the smallest 6 | // representation in Erlang's binary format 7 | @target(erlang) 8 | type Token = 9 | List(Nil) 10 | 11 | @target(erlang) 12 | const token = [] 13 | 14 | @target(javascript) 15 | type Token = 16 | Nil 17 | 18 | @target(javascript) 19 | const token = Nil 20 | 21 | /// A set is a collection of unique members of the same type. 22 | /// 23 | /// It is implemented using the `gleam/dict` module, so inserts and lookups have 24 | /// logarithmic time complexity. 25 | /// 26 | pub opaque type Set(member) { 27 | Set(dict: Dict(member, Token)) 28 | } 29 | 30 | /// Creates a new empty set. 31 | /// 32 | pub fn new() -> Set(member) { 33 | Set(dict.new()) 34 | } 35 | 36 | /// Gets the number of members in a set. 37 | /// 38 | /// This function runs in constant time. 39 | /// 40 | /// ## Examples 41 | /// 42 | /// ```gleam 43 | /// new() 44 | /// |> insert(1) 45 | /// |> insert(2) 46 | /// |> size 47 | /// // -> 2 48 | /// ``` 49 | /// 50 | pub fn size(set: Set(member)) -> Int { 51 | dict.size(set.dict) 52 | } 53 | 54 | /// Determines whether or not the set is empty. 55 | /// 56 | /// ## Examples 57 | /// 58 | /// ```gleam 59 | /// new() |> is_empty 60 | /// // -> True 61 | /// ``` 62 | /// 63 | /// ```gleam 64 | /// new() |> insert(1) |> is_empty 65 | /// // -> False 66 | /// ``` 67 | /// 68 | pub fn is_empty(set: Set(member)) -> Bool { 69 | set == new() 70 | } 71 | 72 | /// Inserts an member into the set. 73 | /// 74 | /// This function runs in logarithmic time. 75 | /// 76 | /// ## Examples 77 | /// 78 | /// ```gleam 79 | /// new() 80 | /// |> insert(1) 81 | /// |> insert(2) 82 | /// |> size 83 | /// // -> 2 84 | /// ``` 85 | /// 86 | pub fn insert(into set: Set(member), this member: member) -> Set(member) { 87 | Set(dict: dict.insert(set.dict, member, token)) 88 | } 89 | 90 | /// Checks whether a set contains a given member. 91 | /// 92 | /// This function runs in logarithmic time. 93 | /// 94 | /// ## Examples 95 | /// 96 | /// ```gleam 97 | /// new() 98 | /// |> insert(2) 99 | /// |> contains(2) 100 | /// // -> True 101 | /// ``` 102 | /// 103 | /// ```gleam 104 | /// new() 105 | /// |> insert(2) 106 | /// |> contains(1) 107 | /// // -> False 108 | /// ``` 109 | /// 110 | pub fn contains(in set: Set(member), this member: member) -> Bool { 111 | set.dict 112 | |> dict.get(member) 113 | |> result.is_ok 114 | } 115 | 116 | /// Removes a member from a set. If the set does not contain the member then 117 | /// the set is returned unchanged. 118 | /// 119 | /// This function runs in logarithmic time. 120 | /// 121 | /// ## Examples 122 | /// 123 | /// ```gleam 124 | /// new() 125 | /// |> insert(2) 126 | /// |> delete(2) 127 | /// |> contains(1) 128 | /// // -> False 129 | /// ``` 130 | /// 131 | pub fn delete(from set: Set(member), this member: member) -> Set(member) { 132 | Set(dict: dict.delete(set.dict, member)) 133 | } 134 | 135 | /// Converts the set into a list of the contained members. 136 | /// 137 | /// The list has no specific ordering, any unintentional ordering may change in 138 | /// future versions of Gleam or Erlang. 139 | /// 140 | /// This function runs in linear time. 141 | /// 142 | /// ## Examples 143 | /// 144 | /// ```gleam 145 | /// new() |> insert(2) |> to_list 146 | /// // -> [2] 147 | /// ``` 148 | /// 149 | pub fn to_list(set: Set(member)) -> List(member) { 150 | dict.keys(set.dict) 151 | } 152 | 153 | /// Creates a new set of the members in a given list. 154 | /// 155 | /// This function runs in loglinear time. 156 | /// 157 | /// ## Examples 158 | /// 159 | /// ```gleam 160 | /// import gleam/int 161 | /// import gleam/list 162 | /// 163 | /// [1, 1, 2, 4, 3, 2] |> from_list |> to_list |> list.sort(by: int.compare) 164 | /// // -> [1, 2, 3, 4] 165 | /// ``` 166 | /// 167 | pub fn from_list(members: List(member)) -> Set(member) { 168 | let dict = 169 | list.fold(over: members, from: dict.new(), with: fn(m, k) { 170 | dict.insert(m, k, token) 171 | }) 172 | Set(dict) 173 | } 174 | 175 | /// Combines all entries into a single value by calling a given function on each 176 | /// one. 177 | /// 178 | /// Sets are not ordered so the values are not returned in any specific order. 179 | /// Do not write code that relies on the order entries are used by this 180 | /// function as it may change in later versions of Gleam or Erlang. 181 | /// 182 | /// # Examples 183 | /// 184 | /// ```gleam 185 | /// from_list([1, 3, 9]) 186 | /// |> fold(0, fn(accumulator, member) { accumulator + member }) 187 | /// // -> 13 188 | /// ``` 189 | /// 190 | pub fn fold( 191 | over set: Set(member), 192 | from initial: acc, 193 | with reducer: fn(acc, member) -> acc, 194 | ) -> acc { 195 | dict.fold(over: set.dict, from: initial, with: fn(a, k, _) { reducer(a, k) }) 196 | } 197 | 198 | /// Creates a new set from an existing set, minus any members that a given 199 | /// function returns `False` for. 200 | /// 201 | /// This function runs in loglinear time. 202 | /// 203 | /// ## Examples 204 | /// 205 | /// ```gleam 206 | /// import gleam/int 207 | /// 208 | /// from_list([1, 4, 6, 3, 675, 44, 67]) 209 | /// |> filter(keeping: int.is_even) 210 | /// |> to_list 211 | /// // -> [4, 6, 44] 212 | /// ``` 213 | /// 214 | pub fn filter( 215 | in set: Set(member), 216 | keeping predicate: fn(member) -> Bool, 217 | ) -> Set(member) { 218 | Set(dict.filter(in: set.dict, keeping: fn(m, _) { predicate(m) })) 219 | } 220 | 221 | /// Creates a new set from a given set with the result of applying the given 222 | /// function to each member. 223 | /// 224 | /// ## Examples 225 | /// 226 | /// ```gleam 227 | /// from_list([1, 2, 3, 4]) 228 | /// |> map(with: fn(x) { x * 2 }) 229 | /// |> to_list 230 | /// // -> [2, 4, 6, 8] 231 | /// ``` 232 | pub fn map(set: Set(member), with fun: fn(member) -> mapped) -> Set(mapped) { 233 | fold(over: set, from: new(), with: fn(acc, member) { 234 | insert(acc, fun(member)) 235 | }) 236 | } 237 | 238 | /// Creates a new set from a given set with all the same entries except any 239 | /// entry found on the given list. 240 | /// 241 | /// ## Examples 242 | /// 243 | /// ```gleam 244 | /// from_list([1, 2, 3, 4]) 245 | /// |> drop([1, 3]) 246 | /// |> to_list 247 | /// // -> [2, 4] 248 | /// ``` 249 | pub fn drop(from set: Set(member), drop disallowed: List(member)) -> Set(member) { 250 | list.fold(over: disallowed, from: set, with: delete) 251 | } 252 | 253 | /// Creates a new set from a given set, only including any members which are in 254 | /// a given list. 255 | /// 256 | /// This function runs in loglinear time. 257 | /// 258 | /// ## Examples 259 | /// 260 | /// ```gleam 261 | /// from_list([1, 2, 3]) 262 | /// |> take([1, 3, 5]) 263 | /// |> to_list 264 | /// // -> [1, 3] 265 | /// ``` 266 | /// 267 | pub fn take(from set: Set(member), keeping desired: List(member)) -> Set(member) { 268 | Set(dict.take(from: set.dict, keeping: desired)) 269 | } 270 | 271 | /// Creates a new set that contains all members of both given sets. 272 | /// 273 | /// This function runs in loglinear time. 274 | /// 275 | /// ## Examples 276 | /// 277 | /// ```gleam 278 | /// union(from_list([1, 2]), from_list([2, 3])) |> to_list 279 | /// // -> [1, 2, 3] 280 | /// ``` 281 | /// 282 | pub fn union(of first: Set(member), and second: Set(member)) -> Set(member) { 283 | let #(larger, smaller) = order(first, second) 284 | fold(over: smaller, from: larger, with: insert) 285 | } 286 | 287 | fn order(first: Set(member), second: Set(member)) -> #(Set(member), Set(member)) { 288 | case dict.size(first.dict) > dict.size(second.dict) { 289 | True -> #(first, second) 290 | False -> #(second, first) 291 | } 292 | } 293 | 294 | /// Creates a new set that contains members that are present in both given sets. 295 | /// 296 | /// This function runs in loglinear time. 297 | /// 298 | /// ## Examples 299 | /// 300 | /// ```gleam 301 | /// intersection(from_list([1, 2]), from_list([2, 3])) |> to_list 302 | /// // -> [2] 303 | /// ``` 304 | /// 305 | pub fn intersection( 306 | of first: Set(member), 307 | and second: Set(member), 308 | ) -> Set(member) { 309 | let #(larger, smaller) = order(first, second) 310 | take(from: larger, keeping: to_list(smaller)) 311 | } 312 | 313 | /// Creates a new set that contains members that are present in the first set 314 | /// but not the second. 315 | /// 316 | /// ## Examples 317 | /// 318 | /// ```gleam 319 | /// difference(from_list([1, 2]), from_list([2, 3, 4])) |> to_list 320 | /// // -> [1] 321 | /// ``` 322 | /// 323 | pub fn difference( 324 | from first: Set(member), 325 | minus second: Set(member), 326 | ) -> Set(member) { 327 | drop(from: first, drop: to_list(second)) 328 | } 329 | 330 | /// Determines if a set is fully contained by another. 331 | /// 332 | /// ## Examples 333 | /// 334 | /// ```gleam 335 | /// is_subset(from_list([1]), from_list([1, 2])) 336 | /// // -> True 337 | /// ``` 338 | /// 339 | /// ```gleam 340 | /// is_subset(from_list([1, 2, 3]), from_list([3, 4, 5])) 341 | /// // -> False 342 | /// ``` 343 | /// 344 | pub fn is_subset(first: Set(member), of second: Set(member)) -> Bool { 345 | intersection(of: first, and: second) == first 346 | } 347 | 348 | /// Determines if two sets contain no common members 349 | /// 350 | /// ## Examples 351 | /// 352 | /// ```gleam 353 | /// is_disjoint(from_list([1, 2, 3]), from_list([4, 5, 6])) 354 | /// // -> True 355 | /// ``` 356 | /// 357 | /// ```gleam 358 | /// is_disjoint(from_list([1, 2, 3]), from_list([3, 4, 5])) 359 | /// // -> False 360 | /// ``` 361 | /// 362 | pub fn is_disjoint(first: Set(member), from second: Set(member)) -> Bool { 363 | intersection(of: first, and: second) == new() 364 | } 365 | 366 | /// Creates a new set that contains members that are present in either set, but 367 | /// not both. 368 | /// 369 | /// ```gleam 370 | /// symmetric_difference(from_list([1, 2, 3]), from_list([3, 4])) |> to_list 371 | /// // -> [1, 2, 4] 372 | /// ``` 373 | /// 374 | pub fn symmetric_difference( 375 | of first: Set(member), 376 | and second: Set(member), 377 | ) -> Set(member) { 378 | difference( 379 | from: union(of: first, and: second), 380 | minus: intersection(of: first, and: second), 381 | ) 382 | } 383 | 384 | /// Calls a function for each member in a set, discarding the return 385 | /// value. 386 | /// 387 | /// Useful for producing a side effect for every item of a set. 388 | /// 389 | /// ```gleam 390 | /// let set = from_list(["apple", "banana", "cherry"]) 391 | /// 392 | /// each(set, io.println) 393 | /// // -> Nil 394 | /// // apple 395 | /// // banana 396 | /// // cherry 397 | /// ``` 398 | /// 399 | /// The order of elements in the iteration is an implementation detail that 400 | /// should not be relied upon. 401 | /// 402 | pub fn each(set: Set(member), fun: fn(member) -> a) -> Nil { 403 | fold(set, Nil, fn(nil, member) { 404 | fun(member) 405 | nil 406 | }) 407 | } 408 | -------------------------------------------------------------------------------- /src/gleam/result.gleam: -------------------------------------------------------------------------------- 1 | //// Result represents the result of something that may succeed or not. 2 | //// `Ok` means it was successful, `Error` means it was not successful. 3 | 4 | import gleam/list 5 | 6 | /// Checks whether the result is an `Ok` value. 7 | /// 8 | /// ## Examples 9 | /// 10 | /// ```gleam 11 | /// is_ok(Ok(1)) 12 | /// // -> True 13 | /// ``` 14 | /// 15 | /// ```gleam 16 | /// is_ok(Error(Nil)) 17 | /// // -> False 18 | /// ``` 19 | /// 20 | pub fn is_ok(result: Result(a, e)) -> Bool { 21 | case result { 22 | Error(_) -> False 23 | Ok(_) -> True 24 | } 25 | } 26 | 27 | /// Checks whether the result is an `Error` value. 28 | /// 29 | /// ## Examples 30 | /// 31 | /// ```gleam 32 | /// is_error(Ok(1)) 33 | /// // -> False 34 | /// ``` 35 | /// 36 | /// ```gleam 37 | /// is_error(Error(Nil)) 38 | /// // -> True 39 | /// ``` 40 | /// 41 | pub fn is_error(result: Result(a, e)) -> Bool { 42 | case result { 43 | Ok(_) -> False 44 | Error(_) -> True 45 | } 46 | } 47 | 48 | /// Updates a value held within the `Ok` of a result by calling a given function 49 | /// on it. 50 | /// 51 | /// If the result is an `Error` rather than `Ok` the function is not called and the 52 | /// result stays the same. 53 | /// 54 | /// ## Examples 55 | /// 56 | /// ```gleam 57 | /// map(over: Ok(1), with: fn(x) { x + 1 }) 58 | /// // -> Ok(2) 59 | /// ``` 60 | /// 61 | /// ```gleam 62 | /// map(over: Error(1), with: fn(x) { x + 1 }) 63 | /// // -> Error(1) 64 | /// ``` 65 | /// 66 | pub fn map(over result: Result(a, e), with fun: fn(a) -> b) -> Result(b, e) { 67 | case result { 68 | Ok(x) -> Ok(fun(x)) 69 | Error(e) -> Error(e) 70 | } 71 | } 72 | 73 | /// Updates a value held within the `Error` of a result by calling a given function 74 | /// on it. 75 | /// 76 | /// If the result is `Ok` rather than `Error` the function is not called and the 77 | /// result stays the same. 78 | /// 79 | /// ## Examples 80 | /// 81 | /// ```gleam 82 | /// map_error(over: Error(1), with: fn(x) { x + 1 }) 83 | /// // -> Error(2) 84 | /// ``` 85 | /// 86 | /// ```gleam 87 | /// map_error(over: Ok(1), with: fn(x) { x + 1 }) 88 | /// // -> Ok(1) 89 | /// ``` 90 | /// 91 | pub fn map_error( 92 | over result: Result(a, e), 93 | with fun: fn(e) -> f, 94 | ) -> Result(a, f) { 95 | case result { 96 | Ok(x) -> Ok(x) 97 | Error(error) -> Error(fun(error)) 98 | } 99 | } 100 | 101 | /// Merges a nested `Result` into a single layer. 102 | /// 103 | /// ## Examples 104 | /// 105 | /// ```gleam 106 | /// flatten(Ok(Ok(1))) 107 | /// // -> Ok(1) 108 | /// ``` 109 | /// 110 | /// ```gleam 111 | /// flatten(Ok(Error(""))) 112 | /// // -> Error("") 113 | /// ``` 114 | /// 115 | /// ```gleam 116 | /// flatten(Error(Nil)) 117 | /// // -> Error(Nil) 118 | /// ``` 119 | /// 120 | pub fn flatten(result: Result(Result(a, e), e)) -> Result(a, e) { 121 | case result { 122 | Ok(x) -> x 123 | Error(error) -> Error(error) 124 | } 125 | } 126 | 127 | /// "Updates" an `Ok` result by passing its value to a function that yields a result, 128 | /// and returning the yielded result. (This may "replace" the `Ok` with an `Error`.) 129 | /// 130 | /// If the input is an `Error` rather than an `Ok`, the function is not called and 131 | /// the original `Error` is returned. 132 | /// 133 | /// This function is the equivalent of calling `map` followed by `flatten`, and 134 | /// it is useful for chaining together multiple functions that may fail. 135 | /// 136 | /// ## Examples 137 | /// 138 | /// ```gleam 139 | /// try(Ok(1), fn(x) { Ok(x + 1) }) 140 | /// // -> Ok(2) 141 | /// ``` 142 | /// 143 | /// ```gleam 144 | /// try(Ok(1), fn(x) { Ok(#("a", x)) }) 145 | /// // -> Ok(#("a", 1)) 146 | /// ``` 147 | /// 148 | /// ```gleam 149 | /// try(Ok(1), fn(_) { Error("Oh no") }) 150 | /// // -> Error("Oh no") 151 | /// ``` 152 | /// 153 | /// ```gleam 154 | /// try(Error(Nil), fn(x) { Ok(x + 1) }) 155 | /// // -> Error(Nil) 156 | /// ``` 157 | /// 158 | pub fn try( 159 | result: Result(a, e), 160 | apply fun: fn(a) -> Result(b, e), 161 | ) -> Result(b, e) { 162 | case result { 163 | Ok(x) -> fun(x) 164 | Error(e) -> Error(e) 165 | } 166 | } 167 | 168 | /// Extracts the `Ok` value from a result, returning a default value if the result 169 | /// is an `Error`. 170 | /// 171 | /// ## Examples 172 | /// 173 | /// ```gleam 174 | /// unwrap(Ok(1), 0) 175 | /// // -> 1 176 | /// ``` 177 | /// 178 | /// ```gleam 179 | /// unwrap(Error(""), 0) 180 | /// // -> 0 181 | /// ``` 182 | /// 183 | pub fn unwrap(result: Result(a, e), or default: a) -> a { 184 | case result { 185 | Ok(v) -> v 186 | Error(_) -> default 187 | } 188 | } 189 | 190 | /// Extracts the `Ok` value from a result, evaluating the default function if the result 191 | /// is an `Error`. 192 | /// 193 | /// ## Examples 194 | /// 195 | /// ```gleam 196 | /// lazy_unwrap(Ok(1), fn() { 0 }) 197 | /// // -> 1 198 | /// ``` 199 | /// 200 | /// ```gleam 201 | /// lazy_unwrap(Error(""), fn() { 0 }) 202 | /// // -> 0 203 | /// ``` 204 | /// 205 | pub fn lazy_unwrap(result: Result(a, e), or default: fn() -> a) -> a { 206 | case result { 207 | Ok(v) -> v 208 | Error(_) -> default() 209 | } 210 | } 211 | 212 | /// Extracts the `Error` value from a result, returning a default value if the result 213 | /// is an `Ok`. 214 | /// 215 | /// ## Examples 216 | /// 217 | /// ```gleam 218 | /// unwrap_error(Error(1), 0) 219 | /// // -> 1 220 | /// ``` 221 | /// 222 | /// ```gleam 223 | /// unwrap_error(Ok(""), 0) 224 | /// // -> 0 225 | /// ``` 226 | /// 227 | pub fn unwrap_error(result: Result(a, e), or default: e) -> e { 228 | case result { 229 | Ok(_) -> default 230 | Error(e) -> e 231 | } 232 | } 233 | 234 | /// Returns the first value if it is `Ok`, otherwise returns the second value. 235 | /// 236 | /// ## Examples 237 | /// 238 | /// ```gleam 239 | /// or(Ok(1), Ok(2)) 240 | /// // -> Ok(1) 241 | /// ``` 242 | /// 243 | /// ```gleam 244 | /// or(Ok(1), Error("Error 2")) 245 | /// // -> Ok(1) 246 | /// ``` 247 | /// 248 | /// ```gleam 249 | /// or(Error("Error 1"), Ok(2)) 250 | /// // -> Ok(2) 251 | /// ``` 252 | /// 253 | /// ```gleam 254 | /// or(Error("Error 1"), Error("Error 2")) 255 | /// // -> Error("Error 2") 256 | /// ``` 257 | /// 258 | pub fn or(first: Result(a, e), second: Result(a, e)) -> Result(a, e) { 259 | case first { 260 | Ok(_) -> first 261 | Error(_) -> second 262 | } 263 | } 264 | 265 | /// Returns the first value if it is `Ok`, otherwise evaluates the given function for a fallback value. 266 | /// 267 | /// If you need access to the initial error value, use `result.try_recover`. 268 | /// 269 | /// ## Examples 270 | /// 271 | /// ```gleam 272 | /// lazy_or(Ok(1), fn() { Ok(2) }) 273 | /// // -> Ok(1) 274 | /// ``` 275 | /// 276 | /// ```gleam 277 | /// lazy_or(Ok(1), fn() { Error("Error 2") }) 278 | /// // -> Ok(1) 279 | /// ``` 280 | /// 281 | /// ```gleam 282 | /// lazy_or(Error("Error 1"), fn() { Ok(2) }) 283 | /// // -> Ok(2) 284 | /// ``` 285 | /// 286 | /// ```gleam 287 | /// lazy_or(Error("Error 1"), fn() { Error("Error 2") }) 288 | /// // -> Error("Error 2") 289 | /// ``` 290 | /// 291 | pub fn lazy_or( 292 | first: Result(a, e), 293 | second: fn() -> Result(a, e), 294 | ) -> Result(a, e) { 295 | case first { 296 | Ok(_) -> first 297 | Error(_) -> second() 298 | } 299 | } 300 | 301 | /// Combines a list of results into a single result. 302 | /// If all elements in the list are `Ok` then returns an `Ok` holding the list of values. 303 | /// If any element is `Error` then returns the first error. 304 | /// 305 | /// ## Examples 306 | /// 307 | /// ```gleam 308 | /// all([Ok(1), Ok(2)]) 309 | /// // -> Ok([1, 2]) 310 | /// ``` 311 | /// 312 | /// ```gleam 313 | /// all([Ok(1), Error("e")]) 314 | /// // -> Error("e") 315 | /// ``` 316 | /// 317 | pub fn all(results: List(Result(a, e))) -> Result(List(a), e) { 318 | list.try_map(results, fn(result) { result }) 319 | } 320 | 321 | /// Given a list of results, returns a pair where the first element is a list 322 | /// of all the values inside `Ok` and the second element is a list with all the 323 | /// values inside `Error`. The values in both lists appear in reverse order with 324 | /// respect to their position in the original list of results. 325 | /// 326 | /// ## Examples 327 | /// 328 | /// ```gleam 329 | /// partition([Ok(1), Error("a"), Error("b"), Ok(2)]) 330 | /// // -> #([2, 1], ["b", "a"]) 331 | /// ``` 332 | /// 333 | pub fn partition(results: List(Result(a, e))) -> #(List(a), List(e)) { 334 | partition_loop(results, [], []) 335 | } 336 | 337 | fn partition_loop(results: List(Result(a, e)), oks: List(a), errors: List(e)) { 338 | case results { 339 | [] -> #(oks, errors) 340 | [Ok(a), ..rest] -> partition_loop(rest, [a, ..oks], errors) 341 | [Error(e), ..rest] -> partition_loop(rest, oks, [e, ..errors]) 342 | } 343 | } 344 | 345 | /// Replace the value within a result 346 | /// 347 | /// ## Examples 348 | /// 349 | /// ```gleam 350 | /// replace(Ok(1), Nil) 351 | /// // -> Ok(Nil) 352 | /// ``` 353 | /// 354 | /// ```gleam 355 | /// replace(Error(1), Nil) 356 | /// // -> Error(1) 357 | /// ``` 358 | /// 359 | pub fn replace(result: Result(a, e), value: b) -> Result(b, e) { 360 | case result { 361 | Ok(_) -> Ok(value) 362 | Error(error) -> Error(error) 363 | } 364 | } 365 | 366 | /// Replace the error within a result 367 | /// 368 | /// ## Examples 369 | /// 370 | /// ```gleam 371 | /// replace_error(Error(1), Nil) 372 | /// // -> Error(Nil) 373 | /// ``` 374 | /// 375 | /// ```gleam 376 | /// replace_error(Ok(1), Nil) 377 | /// // -> Ok(1) 378 | /// ``` 379 | /// 380 | pub fn replace_error(result: Result(a, e), error: f) -> Result(a, f) { 381 | case result { 382 | Ok(x) -> Ok(x) 383 | Error(_) -> Error(error) 384 | } 385 | } 386 | 387 | /// Given a list of results, returns only the values inside `Ok`. 388 | /// 389 | /// ## Examples 390 | /// 391 | /// ```gleam 392 | /// values([Ok(1), Error("a"), Ok(3)]) 393 | /// // -> [1, 3] 394 | /// ``` 395 | /// 396 | pub fn values(results: List(Result(a, e))) -> List(a) { 397 | list.filter_map(results, fn(result) { result }) 398 | } 399 | 400 | /// Updates a value held within the `Error` of a result by calling a given function 401 | /// on it, where the given function also returns a result. The two results are 402 | /// then merged together into one result. 403 | /// 404 | /// If the result is an `Ok` rather than `Error` the function is not called and the 405 | /// result stays the same. 406 | /// 407 | /// This function is useful for chaining together computations that may fail 408 | /// and trying to recover from possible errors. 409 | /// 410 | /// If you do not need access to the initial error value, use `result.lazy_or`. 411 | /// 412 | /// ## Examples 413 | /// 414 | /// ```gleam 415 | /// Ok(1) |> try_recover(with: fn(_) { Error("failed to recover") }) 416 | /// // -> Ok(1) 417 | /// ``` 418 | /// 419 | /// ```gleam 420 | /// Error(1) |> try_recover(with: fn(error) { Ok(error + 1) }) 421 | /// // -> Ok(2) 422 | /// ``` 423 | /// 424 | /// ```gleam 425 | /// Error(1) |> try_recover(with: fn(error) { Error("failed to recover") }) 426 | /// // -> Error("failed to recover") 427 | /// ``` 428 | /// 429 | pub fn try_recover( 430 | result: Result(a, e), 431 | with fun: fn(e) -> Result(a, f), 432 | ) -> Result(a, f) { 433 | case result { 434 | Ok(value) -> Ok(value) 435 | Error(error) -> fun(error) 436 | } 437 | } 438 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | Copyright 2018, Louis Pilfold . 179 | 180 | Licensed under the Apache License, Version 2.0 (the "License"); 181 | you may not use this file except in compliance with the License. 182 | You may obtain a copy of the License at 183 | 184 | http://www.apache.org/licenses/LICENSE-2.0 185 | 186 | Unless required by applicable law or agreed to in writing, software 187 | distributed under the License is distributed on an "AS IS" BASIS, 188 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 189 | See the License for the specific language governing permissions and 190 | limitations under the License. 191 | 192 | -------------------------------------------------------------------------------- /test/gleam/bit_array_test.gleam: -------------------------------------------------------------------------------- 1 | import gleam/bit_array 2 | import gleam/order 3 | import gleam/result 4 | import gleam/string 5 | 6 | pub fn bit_size_test() { 7 | assert bit_array.bit_size(<<>>) == 0 8 | 9 | assert bit_array.bit_size(<<0>>) == 8 10 | 11 | assert bit_array.bit_size(<<-1:32>>) == 32 12 | 13 | assert bit_array.bit_size(<<0:-8>>) == 0 14 | 15 | assert bit_array.bit_size(<<0:1>>) == 1 16 | 17 | assert bit_array.bit_size(<<7:3>>) == 3 18 | 19 | assert bit_array.bit_size(<<-1:190>>) == 190 20 | 21 | assert bit_array.bit_size(<<0:-1>>) == 0 22 | } 23 | 24 | pub fn byte_size_test() { 25 | assert bit_array.byte_size(<<>>) == 0 26 | 27 | assert bit_array.byte_size(<<0, 1, 2, 3, 4>>) == 5 28 | 29 | assert bit_array.byte_size(<<1, 2, 3:6>>) == 3 30 | } 31 | 32 | pub fn pad_to_bytes_test() { 33 | assert bit_array.pad_to_bytes(<<>>) == <<>> 34 | 35 | assert bit_array.pad_to_bytes(<<0xAB>>) == <<0xAB>> 36 | 37 | assert bit_array.pad_to_bytes(<<0xAB, 0x12>>) == <<0xAB, 0x12>> 38 | 39 | assert bit_array.pad_to_bytes(<<1:1>>) == <<0x80>> 40 | 41 | assert bit_array.pad_to_bytes(<<-1:7>>) == <<0xFE>> 42 | 43 | assert bit_array.pad_to_bytes(<<0xAB, 0x12, 3:3>>) == <<0xAB, 0x12, 0x60>> 44 | 45 | let assert <> = <<0xAB, 0xFF>> 46 | assert bit_array.pad_to_bytes(a) == <<0xAB, 0xF0>> 47 | } 48 | 49 | pub fn not_equal_test() { 50 | assert bit_array.from_string("test") != bit_array.from_string("asdf") 51 | } 52 | 53 | pub fn append_test() { 54 | assert bit_array.append( 55 | bit_array.from_string("Test"), 56 | bit_array.from_string(" Me"), 57 | ) 58 | == bit_array.from_string("Test Me") 59 | 60 | assert bit_array.append(<<1, 2>>, <<>>) == <<1, 2>> 61 | 62 | assert bit_array.append(<<1, 2>>, <<3, 4>>) == <<1, 2, 3, 4>> 63 | 64 | assert bit_array.append(<<1, 2:4>>, <<3>>) == <<1, 2:4, 3>> 65 | } 66 | 67 | pub fn concat_test() { 68 | assert bit_array.concat([<<1, 2>>]) == <<1, 2>> 69 | 70 | assert bit_array.concat([<<1, 2>>, <<3>>, <<4>>]) == <<1, 2, 3, 4>> 71 | 72 | assert bit_array.concat([<<-1:32>>, <<0:1>>, <<0:0>>]) 73 | == <<255, 255, 255, 255, 0:1>> 74 | 75 | assert bit_array.concat([<<-20:6, 2>>, <<3:4>>, <<7:3>>, <<-1:64>>]) 76 | == <<176, 8, 255, 255, 255, 255, 255, 255, 255, 255, 31:size(5)>> 77 | 78 | assert bit_array.concat([<<1, 2:4>>, <<3>>]) == <<1, 2:4, 3>> 79 | 80 | assert bit_array.concat([<<-1:32>>, <<0:1>>, <<0:0>>]) 81 | == <<255, 255, 255, 255, 0:1>> 82 | 83 | assert bit_array.concat([<<-20:6, 2>>, <<3:4>>, <<7:3>>, <<-1:64>>]) 84 | == <<176, 8, 255, 255, 255, 255, 255, 255, 255, 255, 31:size(5)>> 85 | } 86 | 87 | pub fn slice_test() { 88 | assert bit_array.slice(<<"hello":utf8>>, 0, 5) == Ok(<<"hello":utf8>>) 89 | 90 | assert bit_array.slice(<<"hello":utf8>>, 0, 0) == Ok(<<"":utf8>>) 91 | 92 | assert bit_array.slice(<<"hello":utf8>>, 2, 2) == Ok(<<"ll":utf8>>) 93 | 94 | assert bit_array.slice(<<"hello":utf8>>, 5, -2) == Ok(<<"lo":utf8>>) 95 | 96 | assert bit_array.slice(<<"":utf8>>, 0, 0) == Ok(<<"":utf8>>) 97 | 98 | assert bit_array.slice(<<"hello":utf8>>, 6, 0) == Error(Nil) 99 | 100 | assert bit_array.slice(<<"hello":utf8>>, 1, -2) == Error(Nil) 101 | 102 | assert bit_array.slice(bit_array.from_string("hello"), -1, 1) == Error(Nil) 103 | 104 | assert bit_array.slice(bit_array.from_string("hello"), 1, 6) == Error(Nil) 105 | 106 | assert bit_array.from_string("ab") 107 | |> bit_array.slice(1, 1) 108 | |> result.try(bit_array.slice(_, 0, 1)) 109 | == Ok(<<"b":utf8>>) 110 | 111 | assert bit_array.slice(<<0, 1, 2:7>>, 0, 3) == Error(Nil) 112 | 113 | assert bit_array.slice( 114 | <<0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15>>, 115 | 8, 116 | 12, 117 | ) 118 | == Error(Nil) 119 | } 120 | 121 | pub fn to_string_test() { 122 | assert bit_array.to_string(<<>>) == Ok("") 123 | 124 | assert bit_array.to_string(<<"":utf8>>) == Ok("") 125 | 126 | assert bit_array.to_string(<<"Hello":utf8>>) == Ok("Hello") 127 | 128 | assert bit_array.to_string(<<"ø":utf8>>) == Ok("ø") 129 | 130 | assert bit_array.to_string(<<255>>) == Error(Nil) 131 | 132 | assert bit_array.to_string(<<"ø":utf8, 2:4>>) == Error(Nil) 133 | 134 | let assert <<_:3, x:bits>> = <<0:3, "ø":utf8>> 135 | assert bit_array.to_string(x) == Ok("ø") 136 | } 137 | 138 | pub fn is_utf8_test() { 139 | assert bit_array.is_utf8(<<>>) 140 | 141 | assert bit_array.is_utf8(<<"":utf8>>) 142 | 143 | assert bit_array.is_utf8(<<"Hello":utf8>>) 144 | 145 | assert bit_array.is_utf8(<<"ø":utf8>>) 146 | 147 | assert !bit_array.is_utf8(<<255>>) 148 | } 149 | 150 | pub fn base64_encode_test() { 151 | assert bit_array.base64_encode(<<255, 127, 254, 252>>, True) == "/3/+/A==" 152 | 153 | assert bit_array.base64_encode(<<255, 127, 254, 252, 100>>, True) 154 | == "/3/+/GQ=" 155 | 156 | assert bit_array.base64_encode(<<255, 127, 254, 252>>, False) == "/3/+/A" 157 | 158 | assert bit_array.base64_encode(<<0, 0, 0>>, True) == "AAAA" 159 | 160 | assert bit_array.base64_encode(<<>>, True) == "" 161 | 162 | assert string.repeat("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", 1024 * 32) 163 | |> bit_array.from_string 164 | |> bit_array.base64_encode(True) 165 | == string.repeat("QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFB", 1024 * 32) 166 | 167 | assert bit_array.base64_encode(<<-1:7>>, True) == "/g==" 168 | 169 | assert bit_array.base64_encode(<<0xFA, 5:3>>, True) == "+qA=" 170 | 171 | assert bit_array.base64_encode(<<0xFA, 0xBC, 0x6D, 1:1>>, True) == "+rxtgA==" 172 | } 173 | 174 | pub fn base64_decode_test() { 175 | assert bit_array.base64_decode("/3/+/A==") == Ok(<<255, 127, 254, 252>>) 176 | 177 | assert bit_array.base64_decode("/3/+/A") == Ok(<<255, 127, 254, 252>>) 178 | 179 | assert bit_array.base64_decode("AAAA") == Ok(<<0, 0, 0>>) 180 | 181 | assert bit_array.base64_decode("") == Ok(<<>>) 182 | 183 | assert bit_array.base64_decode(")!") == Error(Nil) 184 | 185 | assert bit_array.base64_decode("=AAA") == Error(Nil) 186 | } 187 | 188 | pub fn base64_url_encode_test() { 189 | assert bit_array.base64_url_encode(<<255, 127, 254, 252>>, True) == "_3_-_A==" 190 | 191 | assert bit_array.base64_url_encode(<<255, 127, 254, 252>>, False) == "_3_-_A" 192 | 193 | assert bit_array.base64_url_encode(<<0, 0, 0>>, True) == "AAAA" 194 | 195 | assert bit_array.base64_url_encode(<<>>, True) == "" 196 | } 197 | 198 | pub fn base64_url_decode_test() { 199 | assert bit_array.base64_url_decode("_3_-_A==") == Ok(<<255, 127, 254, 252>>) 200 | 201 | assert bit_array.base64_url_decode("_3_-_A") == Ok(<<255, 127, 254, 252>>) 202 | 203 | assert bit_array.base64_url_decode("AAAA") == Ok(<<0, 0, 0>>) 204 | 205 | assert bit_array.base64_url_decode("") == Ok(<<>>) 206 | 207 | assert bit_array.base64_url_decode(")!") == Error(Nil) 208 | } 209 | 210 | pub fn base64_decode_crash_regression_1_test() { 211 | assert bit_array.base64_decode("aGktdGhlcmU.uWUWvrAleKQ2jsWcU97H-RPJ5qRRcE_s") 212 | == Error(Nil) 213 | } 214 | 215 | pub fn base16_encode_test() { 216 | assert bit_array.base16_encode(<<"":utf8>>) == "" 217 | 218 | assert bit_array.base16_encode(<<"f":utf8>>) == "66" 219 | 220 | assert bit_array.base16_encode(<<"fo":utf8>>) == "666F" 221 | 222 | assert bit_array.base16_encode(<<"foo":utf8>>) == "666F6F" 223 | 224 | assert bit_array.base16_encode(<<"foob":utf8>>) == "666F6F62" 225 | 226 | assert bit_array.base16_encode(<<"fooba":utf8>>) == "666F6F6261" 227 | 228 | assert bit_array.base16_encode(<<"foobar":utf8>>) == "666F6F626172" 229 | 230 | assert bit_array.base16_encode(<<161, 178, 195, 212, 229, 246, 120, 145>>) 231 | == "A1B2C3D4E5F67891" 232 | 233 | assert bit_array.base16_encode(<<-1:7>>) == "FE" 234 | 235 | assert bit_array.base16_encode(<<0xFA, 5:3>>) == "FAA0" 236 | 237 | assert bit_array.base16_encode(<<0xFA, 5:4>>) == "FA50" 238 | 239 | assert bit_array.base16_encode(<<0xFA, 0xBC, 0x6D, 1:1>>) == "FABC6D80" 240 | } 241 | 242 | pub fn base16_decode_test() { 243 | assert bit_array.base16_decode("") == Ok(<<>>) 244 | 245 | assert bit_array.base16_decode("66") == Ok(<<"f":utf8>>) 246 | 247 | assert bit_array.base16_decode("666F") == Ok(<<"fo":utf8>>) 248 | 249 | assert bit_array.base16_decode("666F6F") == Ok(<<"foo":utf8>>) 250 | 251 | assert bit_array.base16_decode("666F6F62") == Ok(<<"foob":utf8>>) 252 | 253 | assert bit_array.base16_decode("666F6F6261") == Ok(<<"fooba":utf8>>) 254 | 255 | assert bit_array.base16_decode("666F6F626172") == Ok(<<"foobar":utf8>>) 256 | 257 | assert bit_array.base16_decode("A1B2C3D4E5F67891") 258 | == Ok(<<161, 178, 195, 212, 229, 246, 120, 145>>) 259 | 260 | // Not a hex string 261 | assert bit_array.base16_decode("?") == Error(Nil) 262 | 263 | // Lowercase hex 264 | assert bit_array.base16_decode("a1b2c3d4e5f67891") 265 | == Ok(<<161, 178, 195, 212, 229, 246, 120, 145>>) 266 | } 267 | 268 | pub fn inspect_test() { 269 | assert bit_array.inspect(<<>>) == "<<>>" 270 | 271 | assert bit_array.inspect(<<80>>) == "<<80>>" 272 | 273 | assert bit_array.inspect(<<0, 20, 0x20, 255>>) == "<<0, 20, 32, 255>>" 274 | 275 | assert bit_array.inspect(<<4:5>>) == "<<4:size(5)>>" 276 | 277 | assert bit_array.inspect(<<100, 5:3>>) == "<<100, 5:size(3)>>" 278 | 279 | assert bit_array.inspect(<<5:3, 11:4, 1:2>>) == "<<182, 1:size(1)>>" 280 | } 281 | 282 | pub fn compare_test() { 283 | assert bit_array.compare(<<4:5>>, <<4:5>>) == order.Eq 284 | 285 | assert bit_array.compare(<<4:5, 3:3>>, <<4:5, 2:3>>) == order.Gt 286 | 287 | assert bit_array.compare(<<4:5, 3:3>>, <<4:5, 4:3>>) == order.Lt 288 | 289 | assert bit_array.compare(<<4:5, 3:3, 0:0>>, <<4:5, 3:3, 0:0>>) == order.Eq 290 | 291 | assert bit_array.compare(<<0:2, 3:4, 0:0>>, <<0:2, 3:3, 0:0>>) == order.Gt 292 | 293 | // first is: <<33, 1:size(1)>> 294 | // second is: <<35>> 295 | assert bit_array.compare(<<4:5, 3:4, 0:0>>, <<4:5, 3:3, 0:0>>) == order.Lt 296 | 297 | assert bit_array.compare(<<3:5>>, <<4:5>>) == order.Lt 298 | 299 | assert bit_array.compare(<<3:7>>, <<4:7>>) == order.Lt 300 | 301 | assert bit_array.compare(<<5:5>>, <<4:5>>) == order.Gt 302 | 303 | assert bit_array.compare(<<4:8>>, <<4:5>>) == order.Gt 304 | 305 | assert bit_array.compare(<<4:5>>, <<4:8>>) == order.Lt 306 | 307 | assert bit_array.compare(<<0:5>>, <<0:8>>) == order.Lt 308 | 309 | assert bit_array.compare(<<0:5>>, <<0:5>>) == order.Eq 310 | 311 | assert bit_array.compare(<<0:2>>, <<0:1>>) == order.Gt 312 | } 313 | 314 | pub fn starts_with_test() { 315 | assert bit_array.starts_with(<<>>, <<>>) 316 | 317 | assert bit_array.starts_with(<<0>>, <<>>) 318 | 319 | assert !bit_array.starts_with(<<>>, <<0>>) 320 | 321 | assert bit_array.starts_with(<<0, 1, 2>>, <<0>>) 322 | 323 | assert bit_array.starts_with(<<0, 1, 2>>, <<0, 1>>) 324 | 325 | assert bit_array.starts_with(<<0, 1, 2>>, <<0, 1, 2>>) 326 | 327 | assert !bit_array.starts_with(<<0, 1, 2>>, <<0, 1, 2, 3>>) 328 | 329 | assert !bit_array.starts_with(<<0, 1, 2>>, <<1>>) 330 | 331 | assert bit_array.starts_with(<<1:1>>, <<1:1>>) 332 | 333 | assert bit_array.starts_with(<<1:1>>, <<>>) 334 | 335 | assert !bit_array.starts_with(<<1:1>>, <<1:2>>) 336 | 337 | assert bit_array.starts_with(<<-1:127>>, <<-1:33>>) 338 | 339 | assert !bit_array.starts_with(<<-1:127>>, <<-1:128>>) 340 | 341 | assert !bit_array.starts_with(<<0:127>>, <<1:127>>) 342 | 343 | assert bit_array.starts_with(<<0xFF, 0x81>>, <<0xFF, 1:1>>) 344 | 345 | assert !bit_array.starts_with(<<0xFF, 0x81>>, <<0xFF, 0:1>>) 346 | } 347 | -------------------------------------------------------------------------------- /test/gleam/float_test.gleam: -------------------------------------------------------------------------------- 1 | import gleam/float 2 | import gleam/int 3 | import gleam/list 4 | import gleam/order 5 | import gleam/result 6 | 7 | pub fn parse_test() { 8 | assert float.parse("1.23") == Ok(1.23) 9 | 10 | assert float.parse("+1.23") == Ok(1.23) 11 | 12 | assert float.parse("-1.23") == Ok(-1.23) 13 | 14 | assert float.parse("5.0") == Ok(5.0) 15 | 16 | assert float.parse("0.123456789") == Ok(0.123456789) 17 | 18 | assert float.parse("1.234e10") == Ok(1.234e10) 19 | 20 | assert float.parse("-1.234e+10") == Ok(-1.234e10) 21 | 22 | assert float.parse("1.234e-10") == Ok(1.234e-10) 23 | 24 | assert float.parse("") == Error(Nil) 25 | 26 | assert float.parse("what") == Error(Nil) 27 | 28 | assert float.parse("1") == Error(Nil) 29 | } 30 | 31 | pub fn to_string_test() { 32 | assert float.to_string(0.0) == "0.0" 33 | 34 | assert float.to_string(0.0123) == "0.0123" 35 | 36 | assert float.to_string(-0.0123) == "-0.0123" 37 | 38 | assert float.to_string(12.67) == "12.67" 39 | 40 | assert float.to_string(-12.67) == "-12.67" 41 | 42 | assert float.to_string(123.0) == "123.0" 43 | 44 | assert float.to_string(-123.0) == "-123.0" 45 | 46 | assert float.to_string(3.0e26) == "3.0e26" 47 | 48 | assert float.to_string(-3.0e26) == "-3.0e26" 49 | 50 | assert float.to_string(3.0e-26) == "3.0e-26" 51 | 52 | assert float.to_string(-3.0e-26) == "-3.0e-26" 53 | 54 | assert float.to_string(456.12e78) == "4.5612e80" 55 | 56 | assert float.to_string(-456.12e78) == "-4.5612e80" 57 | 58 | assert float.to_string(456.12e-78) == "4.5612e-76" 59 | 60 | assert float.to_string(-456.12e-78) == "-4.5612e-76" 61 | } 62 | 63 | pub fn clamp_test() { 64 | assert float.clamp(1.4, min: 1.3, max: 1.5) == 1.4 65 | 66 | assert float.clamp(1.2, min: 1.3, max: 1.5) == 1.3 67 | 68 | assert float.clamp(1.6, min: 1.3, max: 1.5) == 1.5 69 | 70 | assert float.clamp(1.2, min: 1.4, max: 0.6) == 1.2 71 | } 72 | 73 | pub fn compare_test() { 74 | assert float.compare(0.0, 0.0) == order.Eq 75 | 76 | assert float.compare(0.1, 0.1) == order.Eq 77 | 78 | assert float.compare(0.0, 0.1) == order.Lt 79 | 80 | assert float.compare(-2.0, -1.9) == order.Lt 81 | 82 | assert float.compare(2.0, 1.9) == order.Gt 83 | 84 | assert float.compare(-1.9, -2.0) == order.Gt 85 | } 86 | 87 | pub fn loosely_compare_test() { 88 | assert float.loosely_compare(10.2, 10.5, tolerating: 0.0) == order.Lt 89 | 90 | assert float.loosely_compare(10.2, with: 10.5, tolerating: 0.31) == order.Eq 91 | 92 | assert float.loosely_compare(10.5, 10.2, 0.31) == order.Eq 93 | 94 | assert float.loosely_compare(10.2, 10.5, 0.29) == order.Lt 95 | 96 | assert float.loosely_compare(10.5, 10.2, 0.29) == order.Gt 97 | 98 | assert float.loosely_compare(-10.2, -10.5, 0.31) == order.Eq 99 | } 100 | 101 | pub fn loosely_equals_test() { 102 | assert !float.loosely_equals(10.2, 10.5, tolerating: 0.0) 103 | 104 | assert float.loosely_equals(10.2, with: 10.5, tolerating: 0.31) 105 | 106 | assert float.loosely_equals(10.5, 10.2, 0.31) 107 | 108 | assert !float.loosely_equals(10.2, 10.5, 0.29) 109 | 110 | assert !float.loosely_equals(10.5, 10.2, 0.29) 111 | 112 | assert float.loosely_equals(-10.2, -10.5, 0.31) 113 | } 114 | 115 | pub fn ceiling_test() { 116 | assert float.ceiling(8.1) == 9.0 117 | 118 | assert float.ceiling(-8.1) == -8.0 119 | 120 | assert float.ceiling(-8.0) == -8.0 121 | } 122 | 123 | pub fn floor_test() { 124 | assert float.floor(8.1) == 8.0 125 | 126 | assert float.floor(-8.1) == -9.0 127 | 128 | assert float.floor(-8.0) == -8.0 129 | } 130 | 131 | pub fn round_test() { 132 | assert float.round(8.1) == 8 133 | 134 | assert float.round(8.4) == 8 135 | 136 | assert float.round(8.499) == 8 137 | 138 | assert float.round(8.5) == 9 139 | 140 | assert float.round(-8.1) == -8 141 | 142 | assert float.round(-7.5) == -8 143 | } 144 | 145 | pub fn truncate_test() { 146 | assert float.truncate(8.1) == 8 147 | 148 | assert float.truncate(8.4) == 8 149 | 150 | assert float.truncate(8.499) == 8 151 | 152 | assert float.truncate(8.5) == 8 153 | 154 | assert float.truncate(-8.1) == -8 155 | 156 | assert float.truncate(-7.5) == -7 157 | } 158 | 159 | pub fn to_precision_test() { 160 | assert float.to_precision(2.43434348473, 2) == 2.43 161 | 162 | assert float.to_precision(2.43534348473, 2) == 2.44 163 | 164 | assert float.to_precision(-2.43534348473, 2) == -2.44 165 | 166 | assert float.to_precision(547_890.453444, -3) == 548_000.0 167 | 168 | assert float.to_precision(547_490.453444, -3) == 547_000.0 169 | 170 | assert float.to_precision(-547_490.453444, -3) == -547_000.0 171 | 172 | assert float.to_precision(435.3224, 0) == 435.0 173 | 174 | assert float.to_precision(435.3224, -0) == 435.0 175 | 176 | assert float.to_precision(184.20000000000002, 2) == 184.2 177 | 178 | assert float.to_precision(12_345_678_912_345_678_912_345_678.0, -19) 179 | == 1_234_568.0e19 180 | } 181 | 182 | pub fn min_test() { 183 | assert float.min(0.0, 0.0) == 0.0 184 | 185 | assert float.min(0.3, 1.5) == 0.3 186 | 187 | assert float.min(1.0, 0.0) == 0.0 188 | 189 | assert float.min(-1.7, 2.5) == -1.7 190 | 191 | assert float.min(-2.2, -2.2) == -2.2 192 | 193 | assert float.min(-1.0, -1.0) == -1.0 194 | 195 | assert float.min(-1.1, -1.0) == -1.1 196 | } 197 | 198 | pub fn max_test() { 199 | assert float.max(0.0, 0.0) == 0.0 200 | 201 | assert float.max(0.3, 1.5) == 1.5 202 | 203 | assert float.max(1.0, 0.0) == 1.0 204 | 205 | assert float.max(-1.7, 2.5) == 2.5 206 | 207 | assert float.max(-2.2, -2.2) == -2.2 208 | 209 | assert float.max(-1.0, -1.0) == -1.0 210 | 211 | assert float.max(-1.1, -1.0) == -1.0 212 | } 213 | 214 | pub fn absolute_value_test() { 215 | assert float.absolute_value(-1.0) == 1.0 216 | 217 | assert float.absolute_value(-20.6) == 20.6 218 | 219 | assert float.absolute_value(0.0) == 0.0 220 | 221 | assert float.absolute_value(1.0) == 1.0 222 | 223 | assert float.absolute_value(25.2) == 25.2 224 | } 225 | 226 | pub fn power_test() { 227 | assert float.power(2.0, 2.0) == Ok(4.0) 228 | 229 | assert float.power(-5.0, 3.0) == Ok(-125.0) 230 | 231 | assert float.power(10.5, 0.0) == Ok(1.0) 232 | 233 | assert float.power(16.0, 0.5) == Ok(4.0) 234 | 235 | assert float.power(2.0, -1.0) == Ok(0.5) 236 | 237 | assert float.power(2.0, -1.0) == Ok(0.5) 238 | 239 | // float.power(-1.0, 0.5) is equivalent to float.square_root(-1.0) 240 | // and should return an error as an imaginary number would otherwise 241 | // have to be returned 242 | assert float.power(-1.0, 0.5) == Error(Nil) 243 | 244 | // Check another case with a negative base and fractional exponent 245 | assert float.power(-1.5, 1.5) == Error(Nil) 246 | 247 | // float.power(0.0, -1.0) is equivalent to 1. /. 0 and is expected 248 | // to be an error 249 | assert float.power(0.0, -1.0) == Error(Nil) 250 | 251 | // Check that a negative base and exponent is fine as long as the 252 | // exponent is not fractional 253 | assert float.power(-2.0, -1.0) == Ok(-0.5) 254 | } 255 | 256 | pub fn square_root_test() { 257 | assert float.square_root(4.0) == Ok(2.0) 258 | 259 | assert float.square_root(16.0) == Ok(4.0) 260 | 261 | assert float.square_root(0.0) == Ok(0.0) 262 | 263 | assert float.square_root(-4.0) == Error(Nil) 264 | } 265 | 266 | pub fn negate_test() { 267 | assert float.negate(-1.0) == 1.0 268 | 269 | assert float.negate(2.0) == -2.0 270 | 271 | assert float.negate(float.negate(0.0)) == 0.0 272 | } 273 | 274 | pub fn sum_test() { 275 | assert float.sum([]) == 0.0 276 | 277 | assert float.sum([1.0, 2.2, 3.3]) == 6.5 278 | } 279 | 280 | pub fn product_test() { 281 | assert float.product([]) == 1.0 282 | 283 | assert float.product([4.0]) == 4.0 284 | 285 | assert float.product([2.5, 3.2, 4.2]) == 33.6 286 | } 287 | 288 | pub fn random_test() { 289 | let expected_average = 0.5 290 | let iterations = 10_000 291 | let sum = 292 | list.range(0, iterations) 293 | |> list.fold(from: 0.0, with: fn(accumulator, _element) { 294 | let i = float.random() 295 | 296 | assert { i <. 1.0 } 297 | assert { i >=. 0.0 } 298 | 299 | accumulator +. i 300 | }) 301 | let average = sum /. int.to_float(iterations) 302 | 303 | assert { average <. expected_average +. 0.1 } 304 | assert { average >. expected_average -. 0.1 } 305 | } 306 | 307 | pub fn modulo_test() { 308 | assert float.modulo(13.3, by: 0.0) == Error(Nil) 309 | 310 | assert float.modulo(13.3, by: 3.3) 311 | |> result.unwrap(or: 0.0) 312 | |> float.loosely_equals(with: 0.1, tolerating: 0.001) 313 | 314 | assert float.modulo(-13.3, by: 3.3) 315 | |> result.unwrap(or: 0.0) 316 | |> float.loosely_equals(with: 3.2, tolerating: 0.001) 317 | 318 | assert float.modulo(13.3, by: -3.3) 319 | |> result.unwrap(or: 0.0) 320 | |> float.loosely_equals(with: -3.2, tolerating: 0.001) 321 | 322 | assert float.modulo(-13.3, by: -3.3) 323 | |> result.unwrap(or: 0.0) 324 | |> float.loosely_equals(with: -0.1, tolerating: 0.001) 325 | } 326 | 327 | pub fn divide_test() { 328 | assert float.divide(1.0, 1.0) == Ok(1.0) 329 | assert float.divide(1.0, 0.0) == Error(Nil) 330 | 331 | assert float.divide(0.0, by: 1.0) == Ok(0.0) 332 | assert float.divide(1.0, by: 0.0) == Error(Nil) 333 | } 334 | 335 | pub fn add_test() { 336 | assert float.add(1.0, 2.0) == 3.0 337 | 338 | assert float.add(3.0, 2.0) == 5.0 339 | } 340 | 341 | pub fn multiply_test() { 342 | assert float.multiply(2.0, 4.0) == 8.0 343 | 344 | assert float.multiply(3.0, 2.0) == 6.0 345 | } 346 | 347 | pub fn subtract_test() { 348 | assert float.subtract(3.0, 1.0) == 2.0 349 | 350 | assert float.subtract(3.0, 2.0) == 1.0 351 | 352 | assert float.subtract(2.0, 3.0) == -1.0 353 | } 354 | 355 | pub fn logarithm_test() { 356 | assert float.logarithm(1.0) 357 | |> result.unwrap(or: 1.0) 358 | |> float.loosely_equals(with: 0.0, tolerating: 0.001) 359 | 360 | assert float.logarithm(2.718281828459045) 361 | |> result.unwrap(or: 0.0) 362 | |> float.loosely_equals(with: 1.0, tolerating: 0.001) 363 | 364 | assert float.logarithm(10.0) 365 | |> result.unwrap(or: 0.0) 366 | |> float.loosely_equals(with: 2.302585092994046, tolerating: 0.001) 367 | 368 | assert float.logarithm(100.0) 369 | |> result.unwrap(or: 0.0) 370 | |> float.loosely_equals(with: 4.605170185988092, tolerating: 0.001) 371 | 372 | assert float.logarithm(0.5) 373 | |> result.unwrap(or: 0.0) 374 | |> float.loosely_equals(with: -0.6931471805599453, tolerating: 0.001) 375 | 376 | assert float.logarithm(0.1) 377 | |> result.unwrap(or: 0.0) 378 | |> float.loosely_equals(with: -2.3025850929940455, tolerating: 0.001) 379 | 380 | assert float.logarithm(0.0) == Error(Nil) 381 | 382 | assert float.logarithm(-1.0) == Error(Nil) 383 | 384 | assert float.logarithm(-100.0) == Error(Nil) 385 | 386 | assert float.logarithm(-0.1) == Error(Nil) 387 | } 388 | 389 | pub fn exponential_test() { 390 | assert float.loosely_equals( 391 | float.exponential(0.0), 392 | with: 1.0, 393 | tolerating: 0.001, 394 | ) 395 | 396 | assert float.loosely_equals( 397 | float.exponential(1.0), 398 | with: 2.718281828459045, 399 | tolerating: 0.001, 400 | ) 401 | 402 | assert float.loosely_equals( 403 | float.exponential(2.0), 404 | with: 7.38905609893065, 405 | tolerating: 0.001, 406 | ) 407 | 408 | assert float.loosely_equals( 409 | float.exponential(-1.0), 410 | with: 0.36787944117144233, 411 | tolerating: 0.001, 412 | ) 413 | 414 | assert float.loosely_equals( 415 | float.exponential(5.0), 416 | with: 148.4131591025766, 417 | tolerating: 0.001, 418 | ) 419 | 420 | assert float.loosely_equals( 421 | float.exponential(-5.0), 422 | with: 0.006737946999085467, 423 | tolerating: 0.001, 424 | ) 425 | 426 | assert float.loosely_equals( 427 | float.exponential(0.000001), 428 | with: 1.0000010000005, 429 | tolerating: 0.001, 430 | ) 431 | 432 | assert float.loosely_equals( 433 | float.exponential(-100.0), 434 | with: 3.720075976020836e-44, 435 | tolerating: 0.001, 436 | ) 437 | } 438 | -------------------------------------------------------------------------------- /test/gleam/dict_test.gleam: -------------------------------------------------------------------------------- 1 | import gleam/dict 2 | import gleam/int 3 | import gleam/list 4 | import gleam/option.{None, Some} 5 | import gleam/string 6 | 7 | pub fn from_list_test() { 8 | assert [#(4, 0), #(1, 0)] 9 | |> dict.from_list 10 | |> dict.size 11 | == 2 12 | 13 | assert dict.from_list([#(1, 0), #(1, 1)]) == dict.from_list([#(1, 1)]) 14 | 15 | assert dict.from_list([#(1, 0), #(2, 1)]) 16 | != dict.from_list([#(1, 0), #(2, 2)]) 17 | 18 | assert dict.from_list([#(1, 0), #(2, 1)]) 19 | != dict.from_list([#(1, 0), #(3, 1)]) 20 | } 21 | 22 | pub fn has_key_test() { 23 | assert !{ 24 | [] 25 | |> dict.from_list 26 | |> dict.has_key(1) 27 | } 28 | 29 | assert [#(1, 0)] 30 | |> dict.from_list 31 | |> dict.has_key(1) 32 | 33 | assert [#(4, 0), #(1, 0)] 34 | |> dict.from_list 35 | |> dict.has_key(1) 36 | 37 | assert !{ 38 | [#(4, 0), #(1, 0)] 39 | |> dict.from_list 40 | |> dict.has_key(0) 41 | } 42 | } 43 | 44 | pub fn new_test() { 45 | assert dict.size(dict.new()) == 0 46 | 47 | assert dict.to_list(dict.new()) == [] 48 | } 49 | 50 | type Key { 51 | A 52 | B 53 | C 54 | } 55 | 56 | pub fn get_test() { 57 | let proplist = [#(4, 0), #(1, 1)] 58 | let m = dict.from_list(proplist) 59 | 60 | assert dict.get(m, 4) == Ok(0) 61 | 62 | assert dict.get(m, 1) == Ok(1) 63 | 64 | assert dict.get(m, 2) == Error(Nil) 65 | 66 | let proplist = [#(A, 0), #(B, 1)] 67 | let m = dict.from_list(proplist) 68 | 69 | assert dict.get(m, A) == Ok(0) 70 | 71 | assert dict.get(m, B) == Ok(1) 72 | 73 | assert dict.get(m, C) == Error(Nil) 74 | 75 | let proplist = [#(<<1, 2, 3>>, 0), #(<<3, 2, 1>>, 1)] 76 | let m = dict.from_list(proplist) 77 | 78 | assert dict.get(m, <<1, 2, 3>>) == Ok(0) 79 | 80 | assert dict.get(m, <<3, 2, 1>>) == Ok(1) 81 | 82 | assert dict.get(m, <<1, 3, 2>>) == Error(Nil) 83 | } 84 | 85 | pub fn insert_test() { 86 | assert dict.new() 87 | |> dict.insert("a", 0) 88 | |> dict.insert("b", 1) 89 | |> dict.insert("c", 2) 90 | == dict.from_list([#("a", 0), #("b", 1), #("c", 2)]) 91 | } 92 | 93 | pub fn map_values_test() { 94 | assert [#(1, 0), #(2, 1), #(3, 2)] 95 | |> dict.from_list 96 | |> dict.map_values(fn(k, v) { k + v }) 97 | == dict.from_list([#(1, 1), #(2, 3), #(3, 5)]) 98 | } 99 | 100 | pub fn keys_test() { 101 | assert [#("a", 0), #("b", 1), #("c", 2)] 102 | |> dict.from_list 103 | |> dict.keys 104 | |> list.sort(string.compare) 105 | == ["a", "b", "c"] 106 | } 107 | 108 | pub fn values_test() { 109 | assert [#("a", 0), #("b", 1), #("c", 2)] 110 | |> dict.from_list 111 | |> dict.values 112 | |> list.sort(int.compare) 113 | == [0, 1, 2] 114 | } 115 | 116 | pub fn take_test() { 117 | assert [#("a", 0), #("b", 1), #("c", 2)] 118 | |> dict.from_list 119 | |> dict.take(["a", "b", "d"]) 120 | == dict.from_list([#("a", 0), #("b", 1)]) 121 | } 122 | 123 | pub fn drop_test() { 124 | assert [#("a", 0), #("b", 1), #("c", 2)] 125 | |> dict.from_list 126 | |> dict.drop(["a", "b", "d"]) 127 | == dict.from_list([#("c", 2)]) 128 | } 129 | 130 | pub fn merge_same_key_test() { 131 | let a = dict.from_list([#("a", 2)]) 132 | let b = dict.from_list([#("a", 0)]) 133 | 134 | assert dict.merge(a, b) == dict.from_list([#("a", 0)]) 135 | 136 | assert dict.merge(b, a) == dict.from_list([#("a", 2)]) 137 | } 138 | 139 | pub fn merge_test() { 140 | let a = dict.from_list([#("a", 2), #("c", 4), #("d", 3)]) 141 | let b = dict.from_list([#("a", 0), #("b", 1), #("c", 2)]) 142 | 143 | assert dict.merge(a, b) 144 | == dict.from_list([#("a", 0), #("b", 1), #("c", 2), #("d", 3)]) 145 | 146 | assert dict.merge(b, a) 147 | == dict.from_list([#("a", 2), #("b", 1), #("c", 4), #("d", 3)]) 148 | } 149 | 150 | pub fn delete_test() { 151 | assert [#("a", 0), #("b", 1), #("c", 2)] 152 | |> dict.from_list 153 | |> dict.delete("a") 154 | |> dict.delete("d") 155 | == dict.from_list([#("b", 1), #("c", 2)]) 156 | } 157 | 158 | pub fn upsert_test() { 159 | let dict = dict.from_list([#("a", 0), #("b", 1), #("c", 2)]) 160 | 161 | let inc_or_zero = fn(x) { 162 | case x { 163 | Some(i) -> i + 1 164 | None -> 0 165 | } 166 | } 167 | 168 | assert dict.upsert(dict, "a", inc_or_zero) 169 | == dict.from_list([#("a", 1), #("b", 1), #("c", 2)]) 170 | 171 | assert dict.upsert(dict, "b", inc_or_zero) 172 | == dict.from_list([#("a", 0), #("b", 2), #("c", 2)]) 173 | 174 | assert dict.upsert(dict, "z", inc_or_zero) 175 | == dict.from_list([#("a", 0), #("b", 1), #("c", 2), #("z", 0)]) 176 | } 177 | 178 | pub fn fold_test() { 179 | let dict = dict.from_list([#("a", 0), #("b", 1), #("c", 2), #("d", 3)]) 180 | 181 | let add = fn(acc, _, v) { v + acc } 182 | 183 | assert dict.fold(dict, 0, add) == 6 184 | 185 | let prepend = fn(acc, k, _) { list.prepend(acc, k) } 186 | 187 | assert dict 188 | |> dict.fold([], prepend) 189 | |> list.sort(string.compare) 190 | == ["a", "b", "c", "d"] 191 | 192 | assert dict.fold(dict.from_list([]), 0, add) == 0 193 | } 194 | 195 | pub fn each_test() { 196 | let dict = dict.from_list([#("a", 1), #("b", 2), #("c", 3), #("d", 4)]) 197 | 198 | assert dict.each(dict, fn(k, v) { 199 | let assert True = case k, v { 200 | "a", 1 | "b", 2 | "c", 3 | "d", 4 -> True 201 | _, _ -> False 202 | } 203 | }) 204 | == Nil 205 | } 206 | 207 | fn range(start, end, a) { 208 | case end - start { 209 | n if n < 1 -> a 210 | _ -> range(start, end - 1, [end - 1, ..a]) 211 | } 212 | } 213 | 214 | fn list_to_map(list) { 215 | list 216 | |> list.map(fn(n) { #(n, n) }) 217 | |> dict.from_list 218 | } 219 | 220 | fn grow_and_shrink_map(initial_size, final_size) { 221 | range(0, initial_size, []) 222 | |> list_to_map 223 | |> list.fold(range(final_size, initial_size, []), _, fn(map, item) { 224 | dict.delete(map, item) 225 | }) 226 | } 227 | 228 | // maps should be equal even if the insert/removal order was different 229 | pub fn insert_order_equality_test() { 230 | assert grow_and_shrink_map(8, 2) == grow_and_shrink_map(4, 2) 231 | assert grow_and_shrink_map(17, 10) == grow_and_shrink_map(12, 10) 232 | assert grow_and_shrink_map(2000, 1000) == grow_and_shrink_map(1000, 1000) 233 | } 234 | 235 | // ensure operations on a map don't mutate it 236 | pub fn persistence_test() { 237 | let a = list_to_map([0]) 238 | let _ = dict.insert(a, 0, 5) 239 | let _ = dict.insert(a, 1, 6) 240 | let _ = dict.delete(a, 0) 241 | assert dict.get(a, 0) == Ok(0) 242 | } 243 | 244 | // using maps as keys should work (tests hash function) 245 | pub fn map_as_key_test() { 246 | let l = range(0, 1000, []) 247 | let a = list_to_map(l) 248 | let a2 = list_to_map(list.reverse(l)) 249 | let a3 = grow_and_shrink_map(2000, 1000) 250 | let b = grow_and_shrink_map(60, 50) 251 | let c = grow_and_shrink_map(50, 20) 252 | let d = grow_and_shrink_map(2, 2) 253 | 254 | let map1 = 255 | dict.new() 256 | |> dict.insert(a, "a") 257 | |> dict.insert(b, "b") 258 | |> dict.insert(c, "c") 259 | |> dict.insert(d, "d") 260 | 261 | assert dict.get(map1, a) == Ok("a") 262 | assert dict.get(map1, a2) == Ok("a") 263 | assert dict.get(map1, a3) == Ok("a") 264 | assert dict.get(map1, b) == Ok("b") 265 | assert dict.get(map1, c) == Ok("c") 266 | assert dict.get(map1, d) == Ok("d") 267 | assert dict.get(dict.insert(map1, a2, "a2"), a) == Ok("a2") 268 | assert dict.get(dict.insert(map1, a3, "a3"), a) == Ok("a3") 269 | } 270 | 271 | pub fn large_n_test() { 272 | let n = 10_000 273 | let l = range(0, n, []) 274 | 275 | let m = list_to_map(l) 276 | list.map(l, fn(i) { 277 | assert dict.get(m, i) == Ok(i) 278 | }) 279 | 280 | let m = grow_and_shrink_map(n, 0) 281 | list.map(l, fn(i) { 282 | assert dict.get(m, i) == Error(Nil) 283 | }) 284 | } 285 | 286 | pub fn size_test() { 287 | let n = 1000 288 | let m = list_to_map(range(0, n, [])) 289 | assert dict.size(m) == n 290 | 291 | let m = grow_and_shrink_map(n, n / 2) 292 | assert dict.size(m) == n / 2 293 | 294 | let m = 295 | grow_and_shrink_map(n, 0) 296 | |> dict.delete(0) 297 | assert dict.size(m) == 0 298 | 299 | let m = list_to_map(range(0, 18, [])) 300 | 301 | assert dict.size(dict.insert(m, 1, 99)) == 18 302 | assert dict.size(dict.insert(m, 2, 99)) == 18 303 | } 304 | 305 | pub fn is_empty_test() { 306 | assert dict.is_empty(dict.new()) 307 | 308 | assert !{ 309 | dict.new() 310 | |> dict.insert(1, 10) 311 | |> dict.is_empty() 312 | } 313 | 314 | assert dict.new() 315 | |> dict.insert(1, 10) 316 | |> dict.delete(1) 317 | |> dict.is_empty() 318 | } 319 | 320 | // https://github.com/gleam-lang/stdlib/issues/435 321 | pub fn peters_bug_test() { 322 | assert dict.new() 323 | |> dict.insert(22, Nil) 324 | |> dict.insert(21, Nil) 325 | |> dict.insert(23, Nil) 326 | |> dict.insert(18, Nil) 327 | |> dict.insert(17, Nil) 328 | |> dict.insert(19, Nil) 329 | |> dict.insert(14, Nil) 330 | |> dict.insert(13, Nil) 331 | |> dict.insert(15, Nil) 332 | |> dict.insert(10, Nil) 333 | |> dict.insert(9, Nil) 334 | |> dict.insert(11, Nil) 335 | |> dict.insert(6, Nil) 336 | |> dict.insert(5, Nil) 337 | |> dict.insert(7, Nil) 338 | |> dict.insert(2, Nil) 339 | |> dict.insert(1, Nil) 340 | |> dict.insert(3, Nil) 341 | |> dict.get(0) 342 | == Error(Nil) 343 | } 344 | 345 | pub fn zero_must_be_contained_test() { 346 | let map = 347 | dict.new() 348 | |> dict.insert(0, Nil) 349 | 350 | assert dict.get(map, 0) == Ok(Nil) 351 | 352 | assert dict.has_key(map, 0) == True 353 | } 354 | 355 | pub fn empty_map_equality_test() { 356 | let map1 = dict.new() 357 | let map2 = dict.from_list([#(1, 2)]) 358 | 359 | assert map1 != map2 360 | assert map2 != map1 361 | } 362 | 363 | pub fn extra_keys_equality_test() { 364 | let map1 = dict.from_list([#(1, 2), #(3, 4)]) 365 | let map2 = dict.from_list([#(1, 2), #(3, 4), #(4, 5)]) 366 | 367 | assert map1 != map2 368 | assert map2 != map1 369 | } 370 | 371 | pub fn combine_test() { 372 | let map1 = dict.from_list([#("a", 3), #("b", 2)]) 373 | let map2 = dict.from_list([#("a", 2), #("c", 3), #("d", 4)]) 374 | 375 | assert dict.combine(map1, map2, fn(one, other) { one - other }) 376 | == dict.from_list([#("a", 1), #("b", 2), #("c", 3), #("d", 4)]) 377 | } 378 | 379 | pub fn combine_with_empty_test() { 380 | let map1 = dict.from_list([#("a", 3), #("b", 2)]) 381 | 382 | assert dict.combine(map1, dict.new(), fn(one, _) { one }) == map1 383 | 384 | assert dict.combine(dict.new(), map1, fn(one, _) { one }) == map1 385 | } 386 | 387 | pub fn combine_with_no_overlapping_keys_test() { 388 | let map1 = dict.from_list([#("a", 1), #("b", 2)]) 389 | let map2 = dict.from_list([#("c", 3), #("d", 4)]) 390 | 391 | assert dict.combine(map1, map2, fn(one, _) { one }) 392 | == dict.from_list([#("a", 1), #("b", 2), #("c", 3), #("d", 4)]) 393 | } 394 | 395 | // Enums without fields all hash to 0 due to how the hash function works - 396 | // we use this fact here to produce and test collisions. 397 | // 398 | // Object.keys() returns [] for variants without fields, so the hash always 399 | // stays on it's initial value. 400 | type CollidingKey { 401 | CollidingKey1 402 | CollidingKey2 403 | } 404 | 405 | pub fn hash_collision_overflow_test() { 406 | let d = 407 | dict.new() |> dict.insert(CollidingKey1, 1) |> dict.insert(CollidingKey2, 2) 408 | 409 | assert dict.size(d) == 2 410 | assert dict.get(d, CollidingKey1) == Ok(1) 411 | assert dict.get(d, CollidingKey2) == Ok(2) 412 | 413 | let d = dict.delete(d, CollidingKey1) 414 | 415 | assert dict.size(d) == 1 416 | assert dict.get(d, CollidingKey1) == Error(Nil) 417 | assert dict.get(d, CollidingKey2) == Ok(2) 418 | } 419 | 420 | fn test_random_operations( 421 | initial_seed: Int, 422 | num_ops: Int, 423 | key_space: Int, 424 | initial: dict.Dict(Int, Int), 425 | ) -> Nil { 426 | test_random_operations_loop( 427 | initial_seed, 428 | prng(initial_seed), 429 | num_ops, 430 | key_space, 431 | dict.to_list(initial), 432 | initial, 433 | ) 434 | } 435 | 436 | fn test_random_operations_loop( 437 | initial_seed: Int, 438 | seed: Int, 439 | remaining: Int, 440 | key_space: Int, 441 | proplist: List(#(Int, Int)), 442 | dict: dict.Dict(Int, Int), 443 | ) -> Nil { 444 | case remaining > 0 { 445 | False -> { 446 | assert_dict_matches_proplist(dict, proplist, initial_seed) 447 | } 448 | True -> { 449 | let seed = prng(seed) 450 | let op_choice = seed % 2 451 | let seed = prng(seed) 452 | let key = seed % key_space 453 | 454 | case op_choice { 455 | // Insert 456 | 0 -> { 457 | let new_proplist = list.key_set(proplist, key, key * 2) 458 | let new_dict = dict.insert(dict, key, key * 2) 459 | test_random_operations_loop( 460 | initial_seed, 461 | seed, 462 | remaining - 1, 463 | key_space, 464 | new_proplist, 465 | new_dict, 466 | ) 467 | } 468 | // Delete 469 | _ -> { 470 | let new_proplist = case list.key_pop(proplist, key) { 471 | Ok(#(_, remaining)) -> remaining 472 | Error(Nil) -> proplist 473 | } 474 | let new_dict = dict.delete(dict, key) 475 | test_random_operations_loop( 476 | initial_seed, 477 | seed, 478 | remaining - 1, 479 | key_space, 480 | new_proplist, 481 | new_dict, 482 | ) 483 | } 484 | } 485 | } 486 | } 487 | } 488 | 489 | fn run_many_random_tests( 490 | count count: Int, 491 | ops_per_test ops_per_test: Int, 492 | key_space key_space: Int, 493 | initial dict: dict.Dict(Int, Int), 494 | ) -> Nil { 495 | case count { 496 | 0 -> Nil 497 | _ -> { 498 | let start_seed = int.random(0x7fffffff) 499 | test_random_operations(start_seed, ops_per_test, key_space, dict) 500 | run_many_random_tests( 501 | count: count - 1, 502 | ops_per_test: ops_per_test, 503 | key_space: key_space, 504 | initial: dict, 505 | ) 506 | } 507 | } 508 | } 509 | 510 | pub fn random_operations_small_test() { 511 | run_many_random_tests( 512 | count: 100, 513 | ops_per_test: 50, 514 | key_space: 32, 515 | initial: dict.new(), 516 | ) 517 | } 518 | 519 | pub fn random_operations_medium_test() { 520 | run_many_random_tests( 521 | count: 100, 522 | ops_per_test: 50, 523 | key_space: 200, 524 | initial: range_dict(50), 525 | ) 526 | } 527 | 528 | pub fn random_operations_large_test() { 529 | run_many_random_tests( 530 | count: 100, 531 | ops_per_test: 1000, 532 | key_space: 2000, 533 | initial: range_dict(1000), 534 | ) 535 | } 536 | 537 | fn range_dict(size) { 538 | list.range(1, size) 539 | |> list.map(fn(x) { #(x, x) }) 540 | |> dict.from_list 541 | } 542 | 543 | fn prng(state: Int) -> Int { 544 | { state * 48_271 } % 0x7FFFFFFF 545 | } 546 | 547 | fn assert_dict_matches_proplist( 548 | d: dict.Dict(k, v), 549 | proplist: List(#(k, v)), 550 | seed: Int, 551 | ) -> Nil { 552 | case dict.size(d) == list.length(proplist) { 553 | True -> Nil 554 | False -> 555 | panic as { 556 | "Size mismatch with seed " 557 | <> int.to_string(seed) 558 | <> ": dict.size=" 559 | <> int.to_string(dict.size(d)) 560 | <> " proplist.size=" 561 | <> int.to_string(list.length(proplist)) 562 | } 563 | } 564 | 565 | list.each(proplist, fn(pair) { 566 | let #(key, value) = pair 567 | let result = dict.get(d, key) 568 | 569 | case result == Ok(value) { 570 | True -> Nil 571 | False -> 572 | panic as { 573 | "Get mismatch with seed " 574 | <> int.to_string(seed) 575 | <> ": key=" 576 | <> string.inspect(key) 577 | <> ", value=" 578 | <> string.inspect(value) 579 | <> ", dict.get=" 580 | <> string.inspect(result) 581 | } 582 | } 583 | }) 584 | 585 | case d == dict.from_list(proplist) { 586 | True -> Nil 587 | False -> 588 | panic as { 589 | "Structural equality failed with seed " <> int.to_string(seed) 590 | } 591 | } 592 | } 593 | -------------------------------------------------------------------------------- /src/gleam/float.gleam: -------------------------------------------------------------------------------- 1 | //// Functions for working with floats. 2 | //// 3 | //// ## Float representation 4 | //// 5 | //// Floats are represented as 64 bit floating point numbers on both the Erlang 6 | //// and JavaScript runtimes. The floating point behaviour is native to their 7 | //// respective runtimes, so their exact behaviour will be slightly different on 8 | //// the two runtimes. 9 | //// 10 | //// ### Infinity and NaN 11 | //// 12 | //// Under the JavaScript runtime, exceeding the maximum (or minimum) 13 | //// representable value for a floating point value will result in Infinity (or 14 | //// -Infinity). Should you try to divide two infinities you will get NaN as a 15 | //// result. 16 | //// 17 | //// When running on BEAM, exceeding the maximum (or minimum) representable 18 | //// value for a floating point value will raise an error. 19 | //// 20 | //// ## Division by zero 21 | //// 22 | //// Gleam runs on the Erlang virtual machine, which does not follow the IEEE 23 | //// 754 standard for floating point arithmetic and does not have an `Infinity` 24 | //// value. In Erlang division by zero results in a crash, however Gleam does 25 | //// not have partial functions and operators in core so instead division by zero 26 | //// returns zero, a behaviour taken from Pony, Coq, and Lean. 27 | //// 28 | //// This may seem unexpected at first, but it is no less mathematically valid 29 | //// than crashing or returning a special value. Division by zero is undefined 30 | //// in mathematics. 31 | 32 | import gleam/order.{type Order} 33 | 34 | /// Attempts to parse a string as a `Float`, returning `Error(Nil)` if it was 35 | /// not possible. 36 | /// 37 | /// ## Examples 38 | /// 39 | /// ```gleam 40 | /// parse("2.3") 41 | /// // -> Ok(2.3) 42 | /// ``` 43 | /// 44 | /// ```gleam 45 | /// parse("ABC") 46 | /// // -> Error(Nil) 47 | /// ``` 48 | /// 49 | @external(erlang, "gleam_stdlib", "parse_float") 50 | @external(javascript, "../gleam_stdlib.mjs", "parse_float") 51 | pub fn parse(string: String) -> Result(Float, Nil) 52 | 53 | /// Returns the string representation of the provided `Float`. 54 | /// 55 | /// ## Examples 56 | /// 57 | /// ```gleam 58 | /// to_string(2.3) 59 | /// // -> "2.3" 60 | /// ``` 61 | /// 62 | @external(erlang, "gleam_stdlib", "float_to_string") 63 | @external(javascript, "../gleam_stdlib.mjs", "float_to_string") 64 | pub fn to_string(x: Float) -> String 65 | 66 | /// Restricts a float between two bounds. 67 | /// 68 | /// Note: If the `min` argument is larger than the `max` argument then they 69 | /// will be swapped, so the minimum bound is always lower than the maximum 70 | /// bound. 71 | /// 72 | /// 73 | /// ## Examples 74 | /// 75 | /// ```gleam 76 | /// clamp(1.2, min: 1.4, max: 1.6) 77 | /// // -> 1.4 78 | /// ``` 79 | /// 80 | /// ```gleam 81 | /// clamp(1.2, min: 1.4, max: 0.6) 82 | /// // -> 1.2 83 | /// ``` 84 | /// 85 | pub fn clamp(x: Float, min min_bound: Float, max max_bound: Float) -> Float { 86 | case min_bound >=. max_bound { 87 | True -> x |> min(min_bound) |> max(max_bound) 88 | False -> x |> min(max_bound) |> max(min_bound) 89 | } 90 | } 91 | 92 | /// Compares two `Float`s, returning an `Order`: 93 | /// `Lt` for lower than, `Eq` for equals, or `Gt` for greater than. 94 | /// 95 | /// ## Examples 96 | /// 97 | /// ```gleam 98 | /// compare(2.0, 2.3) 99 | /// // -> Lt 100 | /// ``` 101 | /// 102 | /// To handle 103 | /// [Floating Point Imprecision](https://en.wikipedia.org/wiki/Floating-point_arithmetic#Accuracy_problems) 104 | /// you may use [`loosely_compare`](#loosely_compare) instead. 105 | /// 106 | pub fn compare(a: Float, with b: Float) -> Order { 107 | case a == b { 108 | True -> order.Eq 109 | False -> 110 | case a <. b { 111 | True -> order.Lt 112 | False -> order.Gt 113 | } 114 | } 115 | } 116 | 117 | /// Compares two `Float`s within a tolerance, returning an `Order`: 118 | /// `Lt` for lower than, `Eq` for equals, or `Gt` for greater than. 119 | /// 120 | /// This function allows Float comparison while handling 121 | /// [Floating Point Imprecision](https://en.wikipedia.org/wiki/Floating-point_arithmetic#Accuracy_problems). 122 | /// 123 | /// Notice: For `Float`s the tolerance won't be exact: 124 | /// `5.3 - 5.0` is not exactly `0.3`. 125 | /// 126 | /// ## Examples 127 | /// 128 | /// ```gleam 129 | /// loosely_compare(5.0, with: 5.3, tolerating: 0.5) 130 | /// // -> Eq 131 | /// ``` 132 | /// 133 | /// If you want to check only for equality you may use 134 | /// [`loosely_equals`](#loosely_equals) instead. 135 | /// 136 | pub fn loosely_compare( 137 | a: Float, 138 | with b: Float, 139 | tolerating tolerance: Float, 140 | ) -> Order { 141 | let difference = absolute_value(a -. b) 142 | case difference <=. tolerance { 143 | True -> order.Eq 144 | False -> compare(a, b) 145 | } 146 | } 147 | 148 | /// Checks for equality of two `Float`s within a tolerance, 149 | /// returning an `Bool`. 150 | /// 151 | /// This function allows Float comparison while handling 152 | /// [Floating Point Imprecision](https://en.wikipedia.org/wiki/Floating-point_arithmetic#Accuracy_problems). 153 | /// 154 | /// Notice: For `Float`s the tolerance won't be exact: 155 | /// `5.3 - 5.0` is not exactly `0.3`. 156 | /// 157 | /// ## Examples 158 | /// 159 | /// ```gleam 160 | /// loosely_equals(5.0, with: 5.3, tolerating: 0.5) 161 | /// // -> True 162 | /// ``` 163 | /// 164 | /// ```gleam 165 | /// loosely_equals(5.0, with: 5.1, tolerating: 0.1) 166 | /// // -> False 167 | /// ``` 168 | /// 169 | pub fn loosely_equals( 170 | a: Float, 171 | with b: Float, 172 | tolerating tolerance: Float, 173 | ) -> Bool { 174 | let difference = absolute_value(a -. b) 175 | difference <=. tolerance 176 | } 177 | 178 | /// Compares two `Float`s, returning the smaller of the two. 179 | /// 180 | /// ## Examples 181 | /// 182 | /// ```gleam 183 | /// min(2.0, 2.3) 184 | /// // -> 2.0 185 | /// ``` 186 | /// 187 | pub fn min(a: Float, b: Float) -> Float { 188 | case a <. b { 189 | True -> a 190 | False -> b 191 | } 192 | } 193 | 194 | /// Compares two `Float`s, returning the larger of the two. 195 | /// 196 | /// ## Examples 197 | /// 198 | /// ```gleam 199 | /// max(2.0, 2.3) 200 | /// // -> 2.3 201 | /// ``` 202 | /// 203 | pub fn max(a: Float, b: Float) -> Float { 204 | case a >. b { 205 | True -> a 206 | False -> b 207 | } 208 | } 209 | 210 | /// Rounds the value to the next highest whole number as a `Float`. 211 | /// 212 | /// ## Examples 213 | /// 214 | /// ```gleam 215 | /// ceiling(2.3) 216 | /// // -> 3.0 217 | /// ``` 218 | /// 219 | @external(erlang, "math", "ceil") 220 | @external(javascript, "../gleam_stdlib.mjs", "ceiling") 221 | pub fn ceiling(x: Float) -> Float 222 | 223 | /// Rounds the value to the next lowest whole number as a `Float`. 224 | /// 225 | /// ## Examples 226 | /// 227 | /// ```gleam 228 | /// floor(2.3) 229 | /// // -> 2.0 230 | /// ``` 231 | /// 232 | @external(erlang, "math", "floor") 233 | @external(javascript, "../gleam_stdlib.mjs", "floor") 234 | pub fn floor(x: Float) -> Float 235 | 236 | /// Rounds the value to the nearest whole number as an `Int`. 237 | /// 238 | /// ## Examples 239 | /// 240 | /// ```gleam 241 | /// round(2.3) 242 | /// // -> 2 243 | /// ``` 244 | /// 245 | /// ```gleam 246 | /// round(2.5) 247 | /// // -> 3 248 | /// ``` 249 | /// 250 | @external(erlang, "erlang", "round") 251 | pub fn round(x: Float) -> Int { 252 | case x >=. 0.0 { 253 | True -> js_round(x) 254 | False -> 0 - js_round(negate(x)) 255 | } 256 | } 257 | 258 | @external(javascript, "../gleam_stdlib.mjs", "round") 259 | fn js_round(a: Float) -> Int 260 | 261 | /// Returns the value as an `Int`, truncating all decimal digits. 262 | /// 263 | /// ## Examples 264 | /// 265 | /// ```gleam 266 | /// truncate(2.4343434847383438) 267 | /// // -> 2 268 | /// ``` 269 | /// 270 | @external(erlang, "erlang", "trunc") 271 | @external(javascript, "../gleam_stdlib.mjs", "truncate") 272 | pub fn truncate(x: Float) -> Int 273 | 274 | /// Converts the value to a given precision as a `Float`. 275 | /// The precision is the number of allowed decimal places. 276 | /// Negative precisions are allowed and force rounding 277 | /// to the nearest tenth, hundredth, thousandth etc. 278 | /// 279 | /// ## Examples 280 | /// 281 | /// ```gleam 282 | /// to_precision(2.43434348473, precision: 2) 283 | /// // -> 2.43 284 | /// ``` 285 | /// 286 | /// ```gleam 287 | /// to_precision(547890.453444, precision: -3) 288 | /// // -> 548000.0 289 | /// ``` 290 | /// 291 | pub fn to_precision(x: Float, precision: Int) -> Float { 292 | case precision <= 0 { 293 | True -> { 294 | let factor = do_power(10.0, do_to_float(-precision)) 295 | do_to_float(round(x /. factor)) *. factor 296 | } 297 | False -> { 298 | let factor = do_power(10.0, do_to_float(precision)) 299 | do_to_float(round(x *. factor)) /. factor 300 | } 301 | } 302 | } 303 | 304 | @external(erlang, "erlang", "float") 305 | @external(javascript, "../gleam_stdlib.mjs", "identity") 306 | fn do_to_float(a: Int) -> Float 307 | 308 | /// Returns the absolute value of the input as a `Float`. 309 | /// 310 | /// ## Examples 311 | /// 312 | /// ```gleam 313 | /// absolute_value(-12.5) 314 | /// // -> 12.5 315 | /// ``` 316 | /// 317 | /// ```gleam 318 | /// absolute_value(10.2) 319 | /// // -> 10.2 320 | /// ``` 321 | /// 322 | pub fn absolute_value(x: Float) -> Float { 323 | case x >=. 0.0 { 324 | True -> x 325 | False -> 0.0 -. x 326 | } 327 | } 328 | 329 | /// Returns the results of the base being raised to the power of the 330 | /// exponent, as a `Float`. 331 | /// 332 | /// ## Examples 333 | /// 334 | /// ```gleam 335 | /// power(2.0, -1.0) 336 | /// // -> Ok(0.5) 337 | /// ``` 338 | /// 339 | /// ```gleam 340 | /// power(2.0, 2.0) 341 | /// // -> Ok(4.0) 342 | /// ``` 343 | /// 344 | /// ```gleam 345 | /// power(8.0, 1.5) 346 | /// // -> Ok(22.627416997969522) 347 | /// ``` 348 | /// 349 | /// ```gleam 350 | /// 4.0 |> power(of: 2.0) 351 | /// // -> Ok(16.0) 352 | /// ``` 353 | /// 354 | /// ```gleam 355 | /// power(-1.0, 0.5) 356 | /// // -> Error(Nil) 357 | /// ``` 358 | /// 359 | pub fn power(base: Float, of exponent: Float) -> Result(Float, Nil) { 360 | let fractional: Bool = ceiling(exponent) -. exponent >. 0.0 361 | // In the following check: 362 | // 1. If the base is negative and the exponent is fractional then 363 | // return an error as it will otherwise be an imaginary number 364 | // 2. If the base is 0 and the exponent is negative then the expression 365 | // is equivalent to the exponent divided by 0 and an error should be 366 | // returned 367 | case base <. 0.0 && fractional || base == 0.0 && exponent <. 0.0 { 368 | True -> Error(Nil) 369 | False -> Ok(do_power(base, exponent)) 370 | } 371 | } 372 | 373 | @external(erlang, "math", "pow") 374 | @external(javascript, "../gleam_stdlib.mjs", "power") 375 | fn do_power(a: Float, b: Float) -> Float 376 | 377 | /// Returns the square root of the input as a `Float`. 378 | /// 379 | /// ## Examples 380 | /// 381 | /// ```gleam 382 | /// square_root(4.0) 383 | /// // -> Ok(2.0) 384 | /// ``` 385 | /// 386 | /// ```gleam 387 | /// square_root(-16.0) 388 | /// // -> Error(Nil) 389 | /// ``` 390 | /// 391 | pub fn square_root(x: Float) -> Result(Float, Nil) { 392 | power(x, 0.5) 393 | } 394 | 395 | /// Returns the negative of the value provided. 396 | /// 397 | /// ## Examples 398 | /// 399 | /// ```gleam 400 | /// negate(1.0) 401 | /// // -> -1.0 402 | /// ``` 403 | /// 404 | pub fn negate(x: Float) -> Float { 405 | -1.0 *. x 406 | } 407 | 408 | /// Sums a list of `Float`s. 409 | /// 410 | /// ## Example 411 | /// 412 | /// ```gleam 413 | /// sum([1.0, 2.2, 3.3]) 414 | /// // -> 6.5 415 | /// ``` 416 | /// 417 | pub fn sum(numbers: List(Float)) -> Float { 418 | sum_loop(numbers, 0.0) 419 | } 420 | 421 | fn sum_loop(numbers: List(Float), initial: Float) -> Float { 422 | case numbers { 423 | [first, ..rest] -> sum_loop(rest, first +. initial) 424 | [] -> initial 425 | } 426 | } 427 | 428 | /// Multiplies a list of `Float`s and returns the product. 429 | /// 430 | /// ## Example 431 | /// 432 | /// ```gleam 433 | /// product([2.5, 3.2, 4.2]) 434 | /// // -> 33.6 435 | /// ``` 436 | /// 437 | pub fn product(numbers: List(Float)) -> Float { 438 | product_loop(numbers, 1.0) 439 | } 440 | 441 | fn product_loop(numbers: List(Float), initial: Float) -> Float { 442 | case numbers { 443 | [first, ..rest] -> product_loop(rest, first *. initial) 444 | [] -> initial 445 | } 446 | } 447 | 448 | /// Generates a random float between the given zero (inclusive) and one 449 | /// (exclusive). 450 | /// 451 | /// On Erlang this updates the random state in the process dictionary. 452 | /// See: 453 | /// 454 | /// ## Examples 455 | /// 456 | /// ```gleam 457 | /// random() 458 | /// // -> 0.646355926896028 459 | /// ``` 460 | /// 461 | @external(erlang, "rand", "uniform") 462 | @external(javascript, "../gleam_stdlib.mjs", "random_uniform") 463 | pub fn random() -> Float 464 | 465 | /// Computes the modulo of an float division of inputs as a `Result`. 466 | /// 467 | /// Returns division of the inputs as a `Result`: If the given divisor equals 468 | /// `0`, this function returns an `Error`. 469 | /// 470 | /// The computed value will always have the same sign as the `divisor`. 471 | /// 472 | /// ## Examples 473 | /// 474 | /// ```gleam 475 | /// modulo(13.3, by: 3.3) 476 | /// // -> Ok(0.1) 477 | /// ``` 478 | /// 479 | /// ```gleam 480 | /// modulo(-13.3, by: 3.3) 481 | /// // -> Ok(3.2) 482 | /// ``` 483 | /// 484 | /// ```gleam 485 | /// modulo(13.3, by: -3.3) 486 | /// // -> Ok(-3.2) 487 | /// ``` 488 | /// 489 | /// ```gleam 490 | /// modulo(-13.3, by: -3.3) 491 | /// // -> Ok(-0.1) 492 | /// ``` 493 | /// 494 | pub fn modulo(dividend: Float, by divisor: Float) -> Result(Float, Nil) { 495 | case divisor { 496 | 0.0 -> Error(Nil) 497 | _ -> Ok(dividend -. floor(dividend /. divisor) *. divisor) 498 | } 499 | } 500 | 501 | /// Returns division of the inputs as a `Result`. 502 | /// 503 | /// ## Examples 504 | /// 505 | /// ```gleam 506 | /// divide(0.0, 1.0) 507 | /// // -> Ok(0.0) 508 | /// ``` 509 | /// 510 | /// ```gleam 511 | /// divide(1.0, 0.0) 512 | /// // -> Error(Nil) 513 | /// ``` 514 | /// 515 | pub fn divide(a: Float, by b: Float) -> Result(Float, Nil) { 516 | case b { 517 | 0.0 -> Error(Nil) 518 | b -> Ok(a /. b) 519 | } 520 | } 521 | 522 | /// Adds two floats together. 523 | /// 524 | /// It's the function equivalent of the `+.` operator. 525 | /// This function is useful in higher order functions or pipes. 526 | /// 527 | /// ## Examples 528 | /// 529 | /// ```gleam 530 | /// add(1.0, 2.0) 531 | /// // -> 3.0 532 | /// ``` 533 | /// 534 | /// ```gleam 535 | /// import gleam/list 536 | /// 537 | /// list.fold([1.0, 2.0, 3.0], 0.0, add) 538 | /// // -> 6.0 539 | /// ``` 540 | /// 541 | /// ```gleam 542 | /// 3.0 |> add(2.0) 543 | /// // -> 5.0 544 | /// ``` 545 | /// 546 | pub fn add(a: Float, b: Float) -> Float { 547 | a +. b 548 | } 549 | 550 | /// Multiplies two floats together. 551 | /// 552 | /// It's the function equivalent of the `*.` operator. 553 | /// This function is useful in higher order functions or pipes. 554 | /// 555 | /// ## Examples 556 | /// 557 | /// ```gleam 558 | /// multiply(2.0, 4.0) 559 | /// // -> 8.0 560 | /// ``` 561 | /// 562 | /// ```gleam 563 | /// import gleam/list 564 | /// 565 | /// list.fold([2.0, 3.0, 4.0], 1.0, multiply) 566 | /// // -> 24.0 567 | /// ``` 568 | /// 569 | /// ```gleam 570 | /// 3.0 |> multiply(2.0) 571 | /// // -> 6.0 572 | /// ``` 573 | /// 574 | pub fn multiply(a: Float, b: Float) -> Float { 575 | a *. b 576 | } 577 | 578 | /// Subtracts one float from another. 579 | /// 580 | /// It's the function equivalent of the `-.` operator. 581 | /// This function is useful in higher order functions or pipes. 582 | /// 583 | /// ## Examples 584 | /// 585 | /// ```gleam 586 | /// subtract(3.0, 1.0) 587 | /// // -> 2.0 588 | /// ``` 589 | /// 590 | /// ```gleam 591 | /// import gleam/list 592 | /// 593 | /// list.fold([1.0, 2.0, 3.0], 10.0, subtract) 594 | /// // -> 4.0 595 | /// ``` 596 | /// 597 | /// ```gleam 598 | /// 3.0 |> subtract(_, 2.0) 599 | /// // -> 1.0 600 | /// ``` 601 | /// 602 | /// ```gleam 603 | /// 3.0 |> subtract(2.0, _) 604 | /// // -> -1.0 605 | /// ``` 606 | /// 607 | pub fn subtract(a: Float, b: Float) -> Float { 608 | a -. b 609 | } 610 | 611 | /// Returns the natural logarithm (base e) of the given as a `Result`. If the 612 | /// input is less than or equal to 0, returns `Error(Nil)`. 613 | /// 614 | /// ## Examples 615 | /// 616 | /// ```gleam 617 | /// logarithm(1.0) 618 | /// // -> Ok(0.0) 619 | /// ``` 620 | /// 621 | /// ```gleam 622 | /// logarithm(2.718281828459045) // e 623 | /// // -> Ok(1.0) 624 | /// ``` 625 | /// 626 | /// ```gleam 627 | /// logarithm(0.0) 628 | /// // -> Error(Nil) 629 | /// ``` 630 | /// 631 | /// ```gleam 632 | /// logarithm(-1.0) 633 | /// // -> Error(Nil) 634 | /// ``` 635 | /// 636 | pub fn logarithm(x: Float) -> Result(Float, Nil) { 637 | // In the following check: 638 | // 1. If x is negative then return an error as the natural logarithm 639 | // of a negative number is undefined (would be a complex number) 640 | // 2. If x is 0 then return an error as the natural logarithm of 0 641 | // approaches negative infinity 642 | case x <=. 0.0 { 643 | True -> Error(Nil) 644 | False -> Ok(do_log(x)) 645 | } 646 | } 647 | 648 | @external(erlang, "math", "log") 649 | @external(javascript, "../gleam_stdlib.mjs", "log") 650 | fn do_log(x: Float) -> Float 651 | 652 | /// Returns e (Euler's number) raised to the power of the given exponent, as 653 | /// a `Float`. 654 | /// 655 | /// ## Examples 656 | /// 657 | /// ```gleam 658 | /// exponential(0.0) 659 | /// // -> Ok(1.0) 660 | /// ``` 661 | /// 662 | /// ```gleam 663 | /// exponential(1.0) 664 | /// // -> Ok(2.718281828459045) 665 | /// ``` 666 | /// 667 | /// ```gleam 668 | /// exponential(-1.0) 669 | /// // -> Ok(0.36787944117144233) 670 | /// ``` 671 | /// 672 | @external(erlang, "math", "exp") 673 | @external(javascript, "../gleam_stdlib.mjs", "exp") 674 | pub fn exponential(x: Float) -> Float 675 | -------------------------------------------------------------------------------- /src/gleam/dict.gleam: -------------------------------------------------------------------------------- 1 | import gleam/option.{type Option} 2 | 3 | /// A dictionary of keys and values. 4 | /// 5 | /// Any type can be used for the keys and values of a dict, but all the keys 6 | /// must be of the same type and all the values must be of the same type. 7 | /// 8 | /// Each key can only be present in a dict once. 9 | /// 10 | /// Dicts are not ordered in any way, and any unintentional ordering is not to 11 | /// be relied upon in your code as it may change in future versions of Erlang 12 | /// or Gleam. 13 | /// 14 | /// See [the Erlang map module](https://erlang.org/doc/man/maps.html) for more 15 | /// information. 16 | /// 17 | pub type Dict(key, value) 18 | 19 | /// "TransientDict" is a mutable view on a dictionary used internally by the 20 | /// javascript target. No mutable API is exposed to the user. 21 | /// 22 | /// Transients are to be treated as having a linear (single-use, think rust) type. 23 | /// A transient value becomes invalid as soon as it's passed to one of the functions. 24 | type TransientDict(key, value) 25 | 26 | /// Convert a normal Dict to a transient dict. 27 | /// A transient dict is a mutable copy of the original. 28 | @external(erlang, "gleam_stdlib", "identity") 29 | @external(javascript, "../dict.mjs", "toTransient") 30 | fn to_transient(dict: Dict(key, value)) -> TransientDict(key, value) 31 | 32 | /// Convert a transient dict back into a normal dict, freezing its contents. 33 | /// Using the transient after this point is highly unsafe and leads to undefined behavior. 34 | @external(erlang, "gleam_stdlib", "identity") 35 | @external(javascript, "../dict.mjs", "fromTransient") 36 | fn from_transient(transient: TransientDict(key, value)) -> Dict(key, value) 37 | 38 | /// Determines the number of key-value pairs in the dict. 39 | /// This function runs in constant time and does not need to iterate the dict. 40 | /// 41 | /// ## Examples 42 | /// 43 | /// ```gleam 44 | /// new() |> size 45 | /// // -> 0 46 | /// ``` 47 | /// 48 | /// ```gleam 49 | /// new() |> insert("key", "value") |> size 50 | /// // -> 1 51 | /// ``` 52 | /// 53 | @external(erlang, "maps", "size") 54 | @external(javascript, "../dict.mjs", "size") 55 | pub fn size(dict: Dict(k, v)) -> Int 56 | 57 | /// Determines whether or not the dict is empty. 58 | /// 59 | /// ## Examples 60 | /// 61 | /// ```gleam 62 | /// new() |> is_empty 63 | /// // -> True 64 | /// ``` 65 | /// 66 | /// ```gleam 67 | /// new() |> insert("b", 1) |> is_empty 68 | /// // -> False 69 | /// ``` 70 | /// 71 | pub fn is_empty(dict: Dict(k, v)) -> Bool { 72 | size(dict) == 0 73 | } 74 | 75 | /// Converts the dict to a list of 2-element tuples `#(key, value)`, one for 76 | /// each key-value pair in the dict. 77 | /// 78 | /// The tuples in the list have no specific order. 79 | /// 80 | /// ## Examples 81 | /// 82 | /// Calling `to_list` on an empty `dict` returns an empty list. 83 | /// 84 | /// ```gleam 85 | /// new() |> to_list 86 | /// // -> [] 87 | /// ``` 88 | /// 89 | /// The ordering of elements in the resulting list is an implementation detail 90 | /// that should not be relied upon. 91 | /// 92 | /// ```gleam 93 | /// new() |> insert("b", 1) |> insert("a", 0) |> insert("c", 2) |> to_list 94 | /// // -> [#("a", 0), #("b", 1), #("c", 2)] 95 | /// ``` 96 | /// 97 | @external(erlang, "maps", "to_list") 98 | pub fn to_list(dict: Dict(k, v)) -> List(#(k, v)) { 99 | fold(dict, from: [], with: fn(acc, key, value) { [#(key, value), ..acc] }) 100 | } 101 | 102 | /// Converts a list of 2-element tuples `#(key, value)` to a dict. 103 | /// 104 | /// If two tuples have the same key the last one in the list will be the one 105 | /// that is present in the dict. 106 | /// 107 | @external(erlang, "maps", "from_list") 108 | pub fn from_list(list: List(#(k, v))) -> Dict(k, v) { 109 | from_list_loop(to_transient(new()), list) 110 | } 111 | 112 | fn from_list_loop( 113 | transient: TransientDict(k, v), 114 | list: List(#(k, v)), 115 | ) -> Dict(k, v) { 116 | case list { 117 | [] -> from_transient(transient) 118 | [#(key, value), ..rest] -> 119 | from_list_loop(transient_insert(key, value, transient), rest) 120 | } 121 | } 122 | 123 | /// Determines whether or not a value present in the dict for a given key. 124 | /// 125 | /// ## Examples 126 | /// 127 | /// ```gleam 128 | /// new() |> insert("a", 0) |> has_key("a") 129 | /// // -> True 130 | /// ``` 131 | /// 132 | /// ```gleam 133 | /// new() |> insert("a", 0) |> has_key("b") 134 | /// // -> False 135 | /// ``` 136 | /// 137 | @external(javascript, "../dict.mjs", "has") 138 | pub fn has_key(dict: Dict(k, v), key: k) -> Bool { 139 | do_has_key(key, dict) 140 | } 141 | 142 | @external(erlang, "maps", "is_key") 143 | fn do_has_key(key: k, dict: Dict(k, v)) -> Bool 144 | 145 | /// Creates a fresh dict that contains no values. 146 | /// 147 | @external(erlang, "maps", "new") 148 | @external(javascript, "../dict.mjs", "make") 149 | pub fn new() -> Dict(k, v) 150 | 151 | /// Fetches a value from a dict for a given key. 152 | /// 153 | /// The dict may not have a value for the key, so the value is wrapped in a 154 | /// `Result`. 155 | /// 156 | /// ## Examples 157 | /// 158 | /// ```gleam 159 | /// new() |> insert("a", 0) |> get("a") 160 | /// // -> Ok(0) 161 | /// ``` 162 | /// 163 | /// ```gleam 164 | /// new() |> insert("a", 0) |> get("b") 165 | /// // -> Error(Nil) 166 | /// ``` 167 | /// 168 | @external(erlang, "gleam_stdlib", "map_get") 169 | @external(javascript, "../dict.mjs", "get") 170 | pub fn get(from: Dict(k, v), get: k) -> Result(v, Nil) 171 | 172 | /// Inserts a value into the dict with the given key. 173 | /// 174 | /// If the dict already has a value for the given key then the value is 175 | /// replaced with the new value. 176 | /// 177 | /// ## Examples 178 | /// 179 | /// ```gleam 180 | /// new() |> insert("a", 0) 181 | /// // -> from_list([#("a", 0)]) 182 | /// ``` 183 | /// 184 | /// ```gleam 185 | /// new() |> insert("a", 0) |> insert("a", 5) 186 | /// // -> from_list([#("a", 5)]) 187 | /// ``` 188 | /// 189 | @external(javascript, "../dict.mjs", "insert") 190 | pub fn insert(into dict: Dict(k, v), for key: k, insert value: v) -> Dict(k, v) { 191 | do_insert(key, value, dict) 192 | } 193 | 194 | @external(erlang, "maps", "put") 195 | fn do_insert(key: k, value: v, dict: Dict(k, v)) -> Dict(k, v) 196 | 197 | @external(erlang, "maps", "put") 198 | @external(javascript, "../dict.mjs", "destructiveTransientInsert") 199 | fn transient_insert( 200 | key: k, 201 | value: v, 202 | transient: TransientDict(k, v), 203 | ) -> TransientDict(k, v) 204 | 205 | /// Updates all values in a given dict by calling a given function on each key 206 | /// and value. 207 | /// 208 | /// ## Examples 209 | /// 210 | /// ```gleam 211 | /// from_list([#(3, 3), #(2, 4)]) 212 | /// |> map_values(fn(key, value) { key * value }) 213 | /// // -> from_list([#(3, 9), #(2, 8)]) 214 | /// ``` 215 | /// 216 | @external(javascript, "../dict.mjs", "map") 217 | pub fn map_values(in dict: Dict(k, v), with fun: fn(k, v) -> a) -> Dict(k, a) { 218 | do_map_values(fun, dict) 219 | } 220 | 221 | @external(erlang, "maps", "map") 222 | fn do_map_values(f: fn(k, v) -> a, dict: Dict(k, v)) -> Dict(k, a) 223 | 224 | /// Gets a list of all keys in a given dict. 225 | /// 226 | /// Dicts are not ordered so the keys are not returned in any specific order. Do 227 | /// not write code that relies on the order keys are returned by this function 228 | /// as it may change in later versions of Gleam or Erlang. 229 | /// 230 | /// ## Examples 231 | /// 232 | /// ```gleam 233 | /// from_list([#("a", 0), #("b", 1)]) |> keys 234 | /// // -> ["a", "b"] 235 | /// ``` 236 | /// 237 | @external(erlang, "maps", "keys") 238 | pub fn keys(dict: Dict(k, v)) -> List(k) { 239 | fold(dict, [], fn(acc, key, _value) { [key, ..acc] }) 240 | } 241 | 242 | /// Gets a list of all values in a given dict. 243 | /// 244 | /// Dicts are not ordered so the values are not returned in any specific order. Do 245 | /// not write code that relies on the order values are returned by this function 246 | /// as it may change in later versions of Gleam or Erlang. 247 | /// 248 | /// ## Examples 249 | /// 250 | /// ```gleam 251 | /// from_list([#("a", 0), #("b", 1)]) |> values 252 | /// // -> [0, 1] 253 | /// ``` 254 | /// 255 | @external(erlang, "maps", "values") 256 | pub fn values(dict: Dict(k, v)) -> List(v) { 257 | fold(dict, [], fn(acc, _key, value) { [value, ..acc] }) 258 | } 259 | 260 | /// Creates a new dict from a given dict, minus any entries that a given function 261 | /// returns `False` for. 262 | /// 263 | /// ## Examples 264 | /// 265 | /// ```gleam 266 | /// from_list([#("a", 0), #("b", 1)]) 267 | /// |> filter(fn(key, value) { value != 0 }) 268 | /// // -> from_list([#("b", 1)]) 269 | /// ``` 270 | /// 271 | /// ```gleam 272 | /// from_list([#("a", 0), #("b", 1)]) 273 | /// |> filter(fn(key, value) { True }) 274 | /// // -> from_list([#("a", 0), #("b", 1)]) 275 | /// ``` 276 | /// 277 | pub fn filter( 278 | in dict: Dict(k, v), 279 | keeping predicate: fn(k, v) -> Bool, 280 | ) -> Dict(k, v) { 281 | do_filter(predicate, dict) 282 | } 283 | 284 | @external(erlang, "maps", "filter") 285 | fn do_filter(f: fn(k, v) -> Bool, dict: Dict(k, v)) -> Dict(k, v) { 286 | to_transient(new()) 287 | |> fold(over: dict, with: fn(transient, key, value) { 288 | case f(key, value) { 289 | True -> transient_insert(key, value, transient) 290 | False -> transient 291 | } 292 | }) 293 | |> from_transient 294 | } 295 | 296 | /// Creates a new dict from a given dict, only including any entries for which the 297 | /// keys are in a given list. 298 | /// 299 | /// ## Examples 300 | /// 301 | /// ```gleam 302 | /// from_list([#("a", 0), #("b", 1)]) 303 | /// |> take(["b"]) 304 | /// // -> from_list([#("b", 1)]) 305 | /// ``` 306 | /// 307 | /// ```gleam 308 | /// from_list([#("a", 0), #("b", 1)]) 309 | /// |> take(["a", "b", "c"]) 310 | /// // -> from_list([#("a", 0), #("b", 1)]) 311 | /// ``` 312 | /// 313 | pub fn take(from dict: Dict(k, v), keeping desired_keys: List(k)) -> Dict(k, v) { 314 | do_take(desired_keys, dict) 315 | } 316 | 317 | @external(erlang, "maps", "with") 318 | fn do_take(desired_keys: List(k), dict: Dict(k, v)) -> Dict(k, v) { 319 | do_take_loop(dict, desired_keys, to_transient(new())) 320 | } 321 | 322 | fn do_take_loop( 323 | dict: Dict(k, v), 324 | desired_keys: List(k), 325 | acc: TransientDict(k, v), 326 | ) -> Dict(k, v) { 327 | case desired_keys { 328 | [] -> from_transient(acc) 329 | [key, ..rest] -> 330 | case get(dict, key) { 331 | Ok(value) -> do_take_loop(dict, rest, transient_insert(key, value, acc)) 332 | Error(_) -> do_take_loop(dict, rest, acc) 333 | } 334 | } 335 | } 336 | 337 | /// Creates a new dict from a pair of given dicts by combining their entries. 338 | /// 339 | /// If there are entries with the same keys in both dicts the entry from the 340 | /// second dict takes precedence. 341 | /// 342 | /// ## Examples 343 | /// 344 | /// ```gleam 345 | /// let a = from_list([#("a", 0), #("b", 1)]) 346 | /// let b = from_list([#("b", 2), #("c", 3)]) 347 | /// merge(a, b) 348 | /// // -> from_list([#("a", 0), #("b", 2), #("c", 3)]) 349 | /// ``` 350 | /// 351 | @external(erlang, "maps", "merge") 352 | pub fn merge(into dict: Dict(k, v), from new_entries: Dict(k, v)) -> Dict(k, v) { 353 | combine(dict, new_entries, fn(_, new_entry) { new_entry }) 354 | } 355 | 356 | /// Creates a new dict from a given dict with all the same entries except for the 357 | /// one with a given key, if it exists. 358 | /// 359 | /// ## Examples 360 | /// 361 | /// ```gleam 362 | /// from_list([#("a", 0), #("b", 1)]) |> delete("a") 363 | /// // -> from_list([#("b", 1)]) 364 | /// ``` 365 | /// 366 | /// ```gleam 367 | /// from_list([#("a", 0), #("b", 1)]) |> delete("c") 368 | /// // -> from_list([#("a", 0), #("b", 1)]) 369 | /// ``` 370 | /// 371 | pub fn delete(from dict: Dict(k, v), delete key: k) -> Dict(k, v) { 372 | to_transient(dict) |> transient_delete(key, _) |> from_transient 373 | } 374 | 375 | @external(erlang, "maps", "remove") 376 | @external(javascript, "../dict.mjs", "destructiveTransientDelete") 377 | fn transient_delete(a: k, b: TransientDict(k, v)) -> TransientDict(k, v) 378 | 379 | /// Creates a new dict from a given dict with all the same entries except any with 380 | /// keys found in a given list. 381 | /// 382 | /// ## Examples 383 | /// 384 | /// ```gleam 385 | /// from_list([#("a", 0), #("b", 1)]) |> drop(["a"]) 386 | /// // -> from_list([#("b", 1)]) 387 | /// ``` 388 | /// 389 | /// ```gleam 390 | /// from_list([#("a", 0), #("b", 1)]) |> drop(["c"]) 391 | /// // -> from_list([#("a", 0), #("b", 1)]) 392 | /// ``` 393 | /// 394 | /// ```gleam 395 | /// from_list([#("a", 0), #("b", 1)]) |> drop(["a", "b", "c"]) 396 | /// // -> from_list([]) 397 | /// ``` 398 | /// 399 | pub fn drop(from dict: Dict(k, v), drop disallowed_keys: List(k)) -> Dict(k, v) { 400 | do_drop(disallowed_keys, dict) 401 | } 402 | 403 | @external(erlang, "maps", "without") 404 | fn do_drop(disallowed_keys: List(k), dict: Dict(k, v)) -> Dict(k, v) { 405 | drop_loop(to_transient(dict), disallowed_keys) 406 | } 407 | 408 | fn drop_loop( 409 | transient: TransientDict(k, v), 410 | disallowed_keys: List(k), 411 | ) -> Dict(k, v) { 412 | case disallowed_keys { 413 | [] -> from_transient(transient) 414 | [key, ..rest] -> drop_loop(transient_delete(key, transient), rest) 415 | } 416 | } 417 | 418 | /// Creates a new dict with one entry inserted or updated using a given function. 419 | /// 420 | /// If there was not an entry in the dict for the given key then the function 421 | /// gets `None` as its argument, otherwise it gets `Some(value)`. 422 | /// 423 | /// ## Example 424 | /// 425 | /// ```gleam 426 | /// let dict = from_list([#("a", 0)]) 427 | /// let increment = fn(x) { 428 | /// case x { 429 | /// Some(i) -> i + 1 430 | /// None -> 0 431 | /// } 432 | /// } 433 | /// 434 | /// upsert(dict, "a", increment) 435 | /// // -> from_list([#("a", 1)]) 436 | /// 437 | /// upsert(dict, "b", increment) 438 | /// // -> from_list([#("a", 0), #("b", 0)]) 439 | /// ``` 440 | /// 441 | pub fn upsert( 442 | in dict: Dict(k, v), 443 | update key: k, 444 | with fun: fn(Option(v)) -> v, 445 | ) -> Dict(k, v) { 446 | case get(dict, key) { 447 | Ok(value) -> insert(dict, key, fun(option.Some(value))) 448 | Error(_) -> insert(dict, key, fun(option.None)) 449 | } 450 | } 451 | 452 | /// Combines all entries into a single value by calling a given function on each 453 | /// one. 454 | /// 455 | /// Dicts are not ordered so the values are not returned in any specific order. Do 456 | /// not write code that relies on the order entries are used by this function 457 | /// as it may change in later versions of Gleam or Erlang. 458 | /// 459 | /// # Examples 460 | /// 461 | /// ```gleam 462 | /// let dict = from_list([#("a", 1), #("b", 3), #("c", 9)]) 463 | /// fold(dict, 0, fn(accumulator, key, value) { accumulator + value }) 464 | /// // -> 13 465 | /// ``` 466 | /// 467 | /// ```gleam 468 | /// import gleam/string 469 | /// 470 | /// let dict = from_list([#("a", 1), #("b", 3), #("c", 9)]) 471 | /// fold(dict, "", fn(accumulator, key, value) { 472 | /// string.append(accumulator, key) 473 | /// }) 474 | /// // -> "abc" 475 | /// ``` 476 | /// 477 | @external(javascript, "../dict.mjs", "fold") 478 | pub fn fold( 479 | over dict: Dict(k, v), 480 | from initial: acc, 481 | with fun: fn(acc, k, v) -> acc, 482 | ) -> acc { 483 | let fun = fn(key, value, acc) { fun(acc, key, value) } 484 | do_fold(fun, initial, dict) 485 | } 486 | 487 | @external(erlang, "maps", "fold") 488 | fn do_fold(fun: fn(k, v, acc) -> acc, initial: acc, dict: Dict(k, v)) -> acc 489 | 490 | /// Calls a function for each key and value in a dict, discarding the return 491 | /// value. 492 | /// 493 | /// Useful for producing a side effect for every item of a dict. 494 | /// 495 | /// ```gleam 496 | /// import gleam/io 497 | /// 498 | /// let dict = from_list([#("a", "apple"), #("b", "banana"), #("c", "cherry")]) 499 | /// 500 | /// each(dict, fn(k, v) { 501 | /// io.println(key <> " => " <> value) 502 | /// }) 503 | /// // -> Nil 504 | /// // a => apple 505 | /// // b => banana 506 | /// // c => cherry 507 | /// ``` 508 | /// 509 | /// The order of elements in the iteration is an implementation detail that 510 | /// should not be relied upon. 511 | /// 512 | pub fn each(dict: Dict(k, v), fun: fn(k, v) -> a) -> Nil { 513 | fold(dict, Nil, fn(nil, k, v) { 514 | fun(k, v) 515 | nil 516 | }) 517 | } 518 | 519 | /// Creates a new dict from a pair of given dicts by combining their entries. 520 | /// 521 | /// If there are entries with the same keys in both dicts the given function is 522 | /// used to determine the new value to use in the resulting dict. 523 | /// 524 | /// ## Examples 525 | /// 526 | /// ```gleam 527 | /// let a = from_list([#("a", 0), #("b", 1)]) 528 | /// let b = from_list([#("a", 2), #("c", 3)]) 529 | /// combine(a, b, fn(one, other) { one + other }) 530 | /// // -> from_list([#("a", 2), #("b", 1), #("c", 3)]) 531 | /// ``` 532 | /// 533 | pub fn combine( 534 | dict: Dict(k, v), 535 | other: Dict(k, v), 536 | with fun: fn(v, v) -> v, 537 | ) -> Dict(k, v) { 538 | do_combine(fn(_, l, r) { fun(l, r) }, dict, other) 539 | } 540 | 541 | @external(erlang, "maps", "merge_with") 542 | fn do_combine( 543 | combine: fn(k, v, v) -> v, 544 | left: Dict(k, v), 545 | right: Dict(k, v), 546 | ) -> Dict(k, v) { 547 | let #(big, small, combine) = case size(left) >= size(right) { 548 | True -> #(left, right, combine) 549 | False -> #(right, left, fn(k, l, r) { combine(k, r, l) }) 550 | } 551 | 552 | to_transient(big) 553 | |> fold(over: small, with: fn(transient, key, value) { 554 | let update = fn(existing) { combine(key, existing, value) } 555 | transient_update_with(key, update, value, transient) 556 | }) 557 | |> from_transient 558 | } 559 | 560 | @external(erlang, "maps", "update_with") 561 | @external(javascript, "../dict.mjs", "destructiveTransientUpdateWith") 562 | fn transient_update_with( 563 | key: k, 564 | fun: fn(v) -> v, 565 | init: v, 566 | transient: TransientDict(k, v), 567 | ) -> TransientDict(k, v) 568 | 569 | @internal 570 | pub fn group(key: fn(v) -> k, list: List(v)) -> Dict(k, List(v)) { 571 | group_loop(to_transient(new()), key, list) 572 | } 573 | 574 | fn group_loop( 575 | transient: TransientDict(k, List(v)), 576 | to_key: fn(v) -> k, 577 | list: List(v), 578 | ) -> Dict(k, List(v)) { 579 | case list { 580 | [] -> from_transient(transient) 581 | [value, ..rest] -> { 582 | let key = to_key(value) 583 | let update = fn(existing) { [value, ..existing] } 584 | 585 | transient 586 | |> transient_update_with(key, update, [value], _) 587 | |> group_loop(to_key, rest) 588 | } 589 | } 590 | } 591 | --------------------------------------------------------------------------------