diff options
-rw-r--r-- | CHANGELOG.md | 6 | ||||
-rw-r--r-- | gen/test/gleam@dynamic_test.erl | 16 | ||||
-rw-r--r-- | src/gleam/atom.gleam | 1 | ||||
-rw-r--r-- | src/gleam/bool.gleam | 25 | ||||
-rw-r--r-- | src/gleam/dynamic.gleam | 65 | ||||
-rw-r--r-- | src/gleam/float.gleam | 38 | ||||
-rw-r--r-- | src/gleam/function.gleam | 2 | ||||
-rw-r--r-- | src/gleam/int.gleam | 44 | ||||
-rw-r--r-- | src/gleam/order.gleam | 39 | ||||
-rw-r--r-- | src/gleam/pair.gleam | 10 | ||||
-rw-r--r-- | src/gleam/result.gleam | 114 | ||||
-rw-r--r-- | src/gleam/string.gleam | 308 | ||||
-rw-r--r-- | src/gleam_stdlib.erl | 27 | ||||
-rw-r--r-- | test/gleam/dynamic_test.gleam | 16 |
14 files changed, 434 insertions, 277 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 70c51ab..a3e9ce0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,12 @@ - The `string` module gains `is_empty`, `join` and `concat` functions. - The `int` module gains `is_even` and `is_odd` functions. - The `list.length` function now accepts a labelled argument. +- The `list.length` function now accepts a labelled argument. +- The the second argument of `bool.compare`, `float.compare`, `int.compare`, + and `order.compare` now have the label `with`. +- The `dynamic.unsafe_coerce` function now only accepts Dynamic data. +- The `dynamic` decoder functions no longer print the entire value in their + error messages, to avoid large errors. ## v0.7.0 - 2020-03-03 diff --git a/gen/test/gleam@dynamic_test.erl b/gen/test/gleam@dynamic_test.erl index 587b168..785af15 100644 --- a/gen/test/gleam@dynamic_test.erl +++ b/gen/test/gleam@dynamic_test.erl @@ -14,11 +14,11 @@ string_test() -> ), gleam@should:equal( gleam@dynamic:string(gleam@dynamic:from(1)), - {error, <<"Expected a String, got `1`"/utf8>>} + {error, <<"Expected a string, got an int"/utf8>>} ), gleam@should:equal( gleam@dynamic:string(gleam@dynamic:from([])), - {error, <<"Expected a String, got `[]`"/utf8>>} + {error, <<"Expected a string, got a list"/utf8>>} ). int_test() -> @@ -26,11 +26,11 @@ int_test() -> gleam@should:equal(gleam@dynamic:int(gleam@dynamic:from(2)), {ok, 2}), gleam@should:equal( gleam@dynamic:int(gleam@dynamic:from(1.0)), - {error, <<"Expected an Int, got `1.0`"/utf8>>} + {error, <<"Expected an int, got a float"/utf8>>} ), gleam@should:equal( gleam@dynamic:int(gleam@dynamic:from([])), - {error, <<"Expected an Int, got `[]`"/utf8>>} + {error, <<"Expected an int, got a list"/utf8>>} ). float_test() -> @@ -38,11 +38,11 @@ float_test() -> gleam@should:equal(gleam@dynamic:float(gleam@dynamic:from(2.2)), {ok, 2.2}), gleam@should:equal( gleam@dynamic:float(gleam@dynamic:from(1)), - {error, <<"Expected a Float, got `1`"/utf8>>} + {error, <<"Expected a float, got an int"/utf8>>} ), gleam@should:equal( gleam@dynamic:float(gleam@dynamic:from([])), - {error, <<"Expected a Float, got `[]`"/utf8>>} + {error, <<"Expected a float, got a list"/utf8>>} ). thunk_test() -> @@ -71,11 +71,11 @@ bool_test() -> ), gleam@should:equal( gleam@dynamic:bool(gleam@dynamic:from(1)), - {error, <<"Expected a Bool, got `1`"/utf8>>} + {error, <<"Expected a bool, got an int"/utf8>>} ), gleam@should:equal( gleam@dynamic:bool(gleam@dynamic:from([])), - {error, <<"Expected a Bool, got `[]`"/utf8>>} + {error, <<"Expected a bool, got a list"/utf8>>} ). atom_test() -> diff --git a/src/gleam/atom.gleam b/src/gleam/atom.gleam index a806fc8..8e9cb04 100644 --- a/src/gleam/atom.gleam +++ b/src/gleam/atom.gleam @@ -34,7 +34,6 @@ pub type FromStringError { /// > from_string("some_new_atom") /// Error(AtomNotLoaded) /// -/// pub external fn from_string(String) -> Result(Atom, FromStringError) = "gleam_stdlib" "atom_from_string"; diff --git a/src/gleam/bool.gleam b/src/gleam/bool.gleam index 8a01235..4a9cebf 100644 --- a/src/gleam/bool.gleam +++ b/src/gleam/bool.gleam @@ -11,12 +11,17 @@ import gleam/order.{Order} pub type Bool = Bool -/// Returns the opposite Bool value +/// Returns the opposite bool value. +/// +/// This is the same as the `!` or `not` operators in some other languages. /// /// ## Examples +/// /// > negate(True) /// False /// +/// > negate(False) +/// True /// pub fn negate(bool: Bool) -> Bool { case bool { @@ -28,12 +33,12 @@ pub fn negate(bool: Bool) -> Bool { /// Compares two bools and returns the first values Order to the second. /// /// ## Examples -/// import gleam/order +/// +/// > import gleam/order /// > compare(True, False) /// order.Gt /// -/// -pub fn compare(a: Bool, b: Bool) -> Order { +pub fn compare(a: Bool, with b: Bool) -> Order { case a, b { True, True -> order.Eq True, False -> order.Gt @@ -42,9 +47,10 @@ pub fn compare(a: Bool, b: Bool) -> Order { } } -/// Returns `True` if either Bool value is `True`. +/// Returns True if either bool value is True. /// /// ## Examples +/// /// > max(True, False) /// True /// @@ -54,7 +60,6 @@ pub fn compare(a: Bool, b: Bool) -> Order { /// > max(False, False) /// False /// -/// pub fn max(a: Bool, b: Bool) -> Bool { case a { True -> True @@ -62,9 +67,10 @@ pub fn max(a: Bool, b: Bool) -> Bool { } } -/// Returns `False` if either Bool value is `False`. +/// Returns False if either bool value is False. /// /// ## Examples +/// /// > max(True, False) /// False /// @@ -74,7 +80,6 @@ pub fn max(a: Bool, b: Bool) -> Bool { /// > max(False, False) /// False /// -/// pub fn min(a: Bool, b: Bool) -> Bool { case a { False -> False @@ -82,16 +87,16 @@ pub fn min(a: Bool, b: Bool) -> Bool { } } -/// Returns a numeric representation of the value: +/// Returns a numeric representation of the given bool. /// /// ## Examples +/// /// > to_int(True) /// 1 /// /// > to_int(False) /// 0 /// -/// pub fn to_int(bool: Bool) -> Int { case bool { False -> 0 diff --git a/src/gleam/dynamic.gleam b/src/gleam/dynamic.gleam index 488ad45..4355e59 100644 --- a/src/gleam/dynamic.gleam +++ b/src/gleam/dynamic.gleam @@ -14,13 +14,17 @@ pub external fn from(a) -> Dynamic = "gleam_stdlib" "identity"; /// Unsafely cast a Dynamic value into any other type. /// /// This is an escape hatch for the type system that may be useful when wrapping -/// native Erlang APIs. It is to be used as a last measure only. +/// native Erlang APIs. It is to be used as a last measure only! /// -pub external fn unsafe_coerce(a) -> b = "gleam_stdlib" "identity"; +/// If you can avoid using this function, do! +/// +pub external fn unsafe_coerce(Dynamic) -> a = "gleam_stdlib" "identity"; -/// Safely cast a Dynamic value into a String. +/// Check to see whether a Dynamic value is a string, and return the string if +/// it is. /// /// ## Examples +/// /// > string(from("Hello")) /// Ok("Hello") /// @@ -30,9 +34,11 @@ pub external fn unsafe_coerce(a) -> b = "gleam_stdlib" "identity"; pub external fn string(from: Dynamic) -> Result(String, String) = "gleam_stdlib" "decode_string" -/// Safely cast a Dynamic value into a String. +/// Check to see whether a Dynamic value is an int, and return the int if it +/// is. /// /// ## Examples +/// /// > int(from(123)) /// Ok(123) /// @@ -42,9 +48,11 @@ pub external fn string(from: Dynamic) -> Result(String, String) pub external fn int(from: Dynamic) -> Result(Int, String) = "gleam_stdlib" "decode_int" -/// Safely cast a Dynamic value into a Float. +/// Check to see whether a Dynamic value is an float, and return the float if +/// it is. /// /// ## Examples +/// /// > float(from(2.0)) /// Ok(2.0) /// @@ -54,10 +62,12 @@ pub external fn int(from: Dynamic) -> Result(Int, String) pub external fn float(from: Dynamic) -> Result(Float, String) = "gleam_stdlib" "decode_float" -/// Safely cast a Dynamic value into an Atom. +/// Check to see whether a Dynamic value is an atom, and return the atom if +/// it is. /// /// ## Examples -/// import gleam/atom +/// +/// > import gleam/atom /// > atom(from(atom.create_from_string("hello"))) /// OK("hello") /// @@ -67,9 +77,11 @@ pub external fn float(from: Dynamic) -> Result(Float, String) pub external fn atom(from: Dynamic) -> Result(atom.Atom, String) = "gleam_stdlib" "decode_atom" -/// Safely cast a Dynamic value into a Bool. +/// Check to see whether a Dynamic value is an bool, and return the bool if +/// it is. /// /// ## Examples +/// /// > bool(from(True)) /// Ok(True) /// @@ -79,11 +91,13 @@ pub external fn atom(from: Dynamic) -> Result(atom.Atom, String) pub external fn bool(from: Dynamic) -> Result(Bool, String) = "gleam_stdlib" "decode_bool" -/// Safely cast a Dynamic value into a String. +/// Check to see whether a Dynamic value is a function that takes no arguments, +/// and return the function if it is. /// /// ## Examples -/// import gleam/result -/// let f = fn() { 1 } +/// +/// > import gleam/result +/// > let f = fn() { 1 } /// > thunk(from(f)) |> result.is_ok /// True /// @@ -96,14 +110,19 @@ pub external fn thunk(from: Dynamic) -> Result(fn() -> Dynamic, String) external fn list_dynamic(from: Dynamic) -> Result(List(Dynamic), String) = "gleam_stdlib" "decode_list" -/// Safely cast a Dynamic value into a List of some type. +/// Check to see whether a Dynamic value is a list, and return the list if it +/// is. /// /// ## Examples +/// /// > list(from(["a", "b", "c"]), string) /// Ok(["a", "b", "c"]) /// /// > list(from([1, 2, 3]), string) -/// Error("Expected a List, got `[1, 2, 3]`") +/// Error("Expected an Int, got a binary") +/// +/// > list(from("ok"), string) +/// Error("Expected a List, got a binary") /// pub fn list( from dynamic: Dynamic, @@ -114,28 +133,36 @@ pub fn list( |> result.then(_, list_mod.traverse(_, decoder_type)) } -/// Returns a field from a Dynamic value representing a map if it exists. +/// Check to see if a Dynamic value is a map with a specific field, and return +/// the value of the field if it is. +/// /// This will not succeed on a record. /// /// ## Examples -/// import gleam/map +/// +/// > import gleam/map /// > field(from(map.new("Hello", "World")), "Hello") /// Ok(Dynamic) /// -/// > field(from(123)) -/// Error("Expected a map with key `\"Hello\"`, got `123`") +/// > field(from(123), "Hello") +/// Error("Expected a map with key `\"Hello\"`, got an Int") /// pub external fn field(from: Dynamic, named: a) -> Result(Dynamic, String) = "gleam_stdlib" "decode_field" -/// Returns an element of a Dynamic value representing a tuple if it exists. +/// Check to see if the Dynamic value is a tuple large enough to have a certain +/// index, and return the value of that index if it is. /// /// ## Examples +/// /// > element(from(tuple(1, 2)), 0) /// Ok(Dynamic) /// /// > element(from(tuple(1, 2)), 2) -/// Error("Element position is out-of-bounds") +/// Error("Expected a tuple of at least 3 size, got a tuple of 2 size") +/// +/// > element(from(""), 2) +/// Error("Expected a Tuple, got a binary") /// pub external fn element(from: Dynamic, position: Int) -> Result(Dynamic, String) = "gleam_stdlib" "decode_element" diff --git a/src/gleam/float.gleam b/src/gleam/float.gleam index 499c7fc..918ecc2 100644 --- a/src/gleam/float.gleam +++ b/src/gleam/float.gleam @@ -5,7 +5,8 @@ import gleam/result.{Option} pub type Float = Float -/// Attempts to parse the String as a Float if possible +/// Attempts to parse a string as a float, returning `Error(Nil)` if it was not +/// possible. /// /// ## Examples /// > parse("2.3") @@ -14,33 +15,28 @@ pub type Float = /// > parse("ABC") /// None /// -/// pub external fn parse(String) -> Option(Float) = "gleam_stdlib" "parse_float"; -/// Returns the string representation of the provided -/// `Float` value +/// Return the string representation of the provided float. /// /// ## Examples /// > to_string(2.3) /// "2.3" /// -/// pub fn to_string(f: Float) -> String { f |> iodata.from_float |> iodata.to_string } - -/// Compares two `Floats`, returning an `Order` +/// Compares two floats, returning an order. /// /// ## Examples /// > compare(2.0, 2.3) /// Lt /// -/// -pub fn compare(a: Float, b: Float) -> Order { +pub fn compare(a: Float, with b: Float) -> Order { case a == b { True -> order.Eq False -> @@ -51,13 +47,13 @@ pub fn compare(a: Float, b: Float) -> Order { } } -/// Compares two `Floats`, returning the smaller of the two +/// Compares two floats, returning the smaller of the two. /// /// ## Examples +/// /// > min(2.0, 2.3) /// 2.0 /// -/// pub fn min(a: Float, b: Float) -> Float { case a <. b { True -> a @@ -65,13 +61,13 @@ pub fn min(a: Float, b: Float) -> Float { } } -/// Compares two `Floats`, returning the larger of the two +/// Compares two floats, returning the larger of the two. /// /// ## Examples +/// /// > max(2.0, 2.3) /// 2.3 /// -/// pub fn max(a: Float, b: Float) -> Float { case a >. b { True -> a @@ -79,41 +75,41 @@ pub fn max(a: Float, b: Float) -> Float { } } -/// Rounds the value to the next highest whole number as a Float +/// Rounds the value to the next highest whole number as a float. /// /// ## Examples +/// /// > ceiling(2.3) /// 3.0 /// -/// pub external fn ceiling(Float) -> Float = "math" "ceil"; -/// Rounds the value to the next lowest whole number as a Float +/// Rounds the value to the next lowest whole number as a float. /// /// ## Examples +/// /// > floor(2.3) /// 2.0 /// -/// pub external fn floor(Float) -> Float = "math" "floor"; -/// Rounds the value to the nearest whole number as an Int +/// Rounds the value to the nearest whole number as an int. /// /// ## Examples +/// /// > round(2.3) /// 2 /// /// > round(2.5) /// 3 /// -/// pub external fn round(Float) -> Int = "erlang" "round"; -/// Returns the value as an Int, truncating all decimal digits. +/// Returns the value as an int, truncating all decimal digits. /// /// ## Examples +/// /// > truncate(2.4343434847383438) /// 2 /// -/// pub external fn truncate(Float) -> Int = "erlang" "trunc"; diff --git a/src/gleam/function.gleam b/src/gleam/function.gleam index c0d699d..b92c0ac 100644 --- a/src/gleam/function.gleam +++ b/src/gleam/function.gleam @@ -2,7 +2,7 @@ /// the input from the first and returns the output of the second. /// pub fn compose(fun1: fn(a) -> b, fun2: fn(b) -> c) -> fn(a) -> c { - fn(a) { fun1(a) |> fun2 } + fn(a) { fun2(fun1(a)) } } /// Takes a function that takes two arguments and returns a new function that diff --git a/src/gleam/int.gleam b/src/gleam/int.gleam index 95992fd..a0a0462 100644 --- a/src/gleam/int.gleam +++ b/src/gleam/int.gleam @@ -4,32 +4,31 @@ import gleam/result.{Option} pub type Int = Int -/// Attempts to parse the String as an Int if possible +/// Parse a given string as an int if possible. /// /// ## Examples +/// /// > parse("2") -/// Some(2) +/// Ok(2) /// /// > parse("ABC") -/// None -/// +/// Error(Nil) /// pub external fn parse(String) -> Option(Int) = "gleam_stdlib" "parse_int"; -/// Returns the string representation of the provided -/// `Int` value +/// Print a given int to a string. /// /// ## Examples +/// /// > to_string(2) /// "2" /// -/// pub external fn to_string(Int) -> String = "erlang" "integer_to_binary" -/// Returns the string representation of the provided -/// `Int` value in the base provided. +/// Print a given int to a string using the base number provided. /// /// ## Examples +/// /// > to_base_string(2, 2) /// "10" /// @@ -39,17 +38,22 @@ pub external fn to_string(Int) -> String = "erlang" "integer_to_binary" /// > to_base_string(48, 36) /// "1C" /// -/// pub external fn to_base_string(Int, Int) -> String = "erlang" "integer_to_binary" -/// Compares two `Int`, returning an `Order` +/// Compares two ints, returning an order. /// /// ## Examples +/// /// > compare(2, 3) /// Lt /// +/// > compare(4, 3) +/// Gt +/// +/// > compare(3, 3) +/// Eq /// -pub fn compare(a: Int, b: Int) -> Order { +pub fn compare(a: Int, with b: Int) -> Order { case a == b { True -> order.Eq False -> @@ -60,13 +64,13 @@ pub fn compare(a: Int, b: Int) -> Order { } } -/// Compares two `Int`, returning the smaller of the two +/// Compares two int, returning the smaller of the two. /// /// ## Examples +/// /// > min(2, 3) /// 2 /// -/// pub fn min(a: Int, b: Int) -> Int { case a < b { True -> a @@ -74,13 +78,13 @@ pub fn min(a: Int, b: Int) -> Int { } } -/// Compares two `Int`, returning the larger of the two +/// Compares two int, returning the larger of the two. /// /// ## Examples +/// /// > max(2, 3) /// 3 /// -/// pub fn max(a: Int, b: Int) -> Int { case a > b { True -> a @@ -88,30 +92,30 @@ pub fn max(a: Int, b: Int) -> Int { } } -/// Returns whether the value provided is even +/// Returns whether the value provided is even. /// /// ## Examples +/// /// > is_even(2) /// True /// /// > is_even(3) /// False /// -/// pub fn is_even(x: Int) -> Bool { x % 2 == 0 } -/// Returns whether the value provided is odd +/// Returns whether the value provided is odd. /// /// ## Examples +/// /// > is_odd(3) /// True /// /// > is_odd(2) /// False /// -/// pub fn is_odd(x: Int) -> Bool { x % 2 != 0 } diff --git a/src/gleam/order.gleam b/src/gleam/order.gleam index 5c4df19..c030db4 100644 --- a/src/gleam/order.gleam +++ b/src/gleam/order.gleam @@ -1,17 +1,30 @@ /// Represents the result of a single comparison to determine the precise /// ordering of two values. +/// pub type Order { + /// Less-than Lt + + /// Equal Eq + + /// Greater than Gt } -/// Switches the evaluated ordering from one direction to the other +/// Inverts an order, so less-than becomes greater-than and greater-than +/// becomes less-than. /// /// ## Examples +/// /// > reverse(Lt) /// Gt /// +/// > reverse(Eq) +/// Eq +/// +/// > reverse(Lt) +/// Gt /// pub fn reverse(order: Order) -> Order { case order { @@ -21,12 +34,18 @@ pub fn reverse(order: Order) -> Order { } } -/// Produces a numeric representation of the order +/// Produces a numeric representation of the order. /// /// ## Examples +/// /// > to_int(Lt) /// -1 /// +/// > to_int(Eq) +/// 0 +/// +/// > to_int(Gt) +/// 1 /// pub fn to_int(order: Order) -> Int { case order { @@ -36,14 +55,14 @@ pub fn to_int(order: Order) -> Int { } } -/// Compares two Order values to one another, producing a new Order +/// Compares two Order values to one another, producing a new Order. /// /// ## Examples -/// > compare(Eq, to: Lt) -/// Gt /// +/// > compare(Eq, with: Lt) +/// Gt /// -pub fn compare(a: Order, to b: Order) -> Order { +pub fn compare(a: Order, with b: Order) -> Order { case a, b { x, y if x == y -> Eq Lt, _ | Eq, Gt -> Lt @@ -51,13 +70,13 @@ pub fn compare(a: Order, to b: Order) -> Order { } } -/// Returns the highest of two orders +/// Returns the largest of two orders. /// /// ## Examples +/// /// > max(Eq, Lt) /// Eq /// -/// pub fn max(a: Order, b: Order) -> Order { case a, b { Gt, _ -> Gt @@ -66,13 +85,13 @@ pub fn max(a: Order, b: Order) -> Order { } } -/// Returns the lowest of two orders +/// Returns the smallest of two orders. /// /// ## Examples +/// /// > min(Eq, Lt) /// Lt /// -/// pub fn min(a: Order, b: Order) -> Order { case a, b { Lt, _ -> Lt diff --git a/src/gleam/pair.gleam b/src/gleam/pair.gleam index 4a2e592..f0de5a9 100644 --- a/src/gleam/pair.gleam +++ b/src/gleam/pair.gleam @@ -1,10 +1,10 @@ /// Returns the first element in a pair. /// /// ## Examples +/// /// > first(tuple(1, 2)) /// 1 /// -/// pub fn first(pair: tuple(a, b)) -> a { let tuple(a, _) = pair a @@ -13,10 +13,10 @@ pub fn first(pair: tuple(a, b)) -> a { /// Returns the second element in a pair. /// /// ## Examples +/// /// > second(tuple(1, 2)) /// 2 /// -/// pub fn second(pair: tuple(a, b)) -> b { let tuple(_, a) = pair a @@ -25,10 +25,10 @@ pub fn second(pair: tuple(a, b)) -> b { /// Returns a new pair with the elements swapped. /// /// ## Examples +/// /// > swap(tuple(1, 2)) /// tuple(2, 1) /// -/// pub fn swap(pair: tuple(a, b)) -> tuple(b, a) { let tuple(a, b) = pair tuple(b, a) @@ -38,10 +38,10 @@ pub fn swap(pair: tuple(a, b)) -> tuple(b, a) { /// it. /// /// ## Examples +/// /// > tuple(1, 2) |> map_first(fn(n) { n * 2 }) /// 2 /// -/// pub fn map_first(of pair: tuple(a, b), with fun: fn(a) -> c) -> tuple(c, b) { let tuple(a, b) = pair tuple(fun(a), b) @@ -51,10 +51,10 @@ pub fn map_first(of pair: tuple(a, b), with fun: fn(a) -> c) -> tuple(c, b) { /// it. /// /// ## Examples +/// /// > tuple(1, 2) |> map_second(fn(n) { n * 2 }) /// 4 /// -/// pub fn map_second(of pair: tuple(a, b), with fun: fn(b) -> c) -> tuple(a, c) { let tuple(a, b) = pair tuple(a, fun(b)) diff --git a/src/gleam/result.gleam b/src/gleam/result.gleam index 69dfb8b..b002f8e 100644 --- a/src/gleam/result.gleam +++ b/src/gleam/result.gleam @@ -1,5 +1,6 @@ -/// Result represents the result of something that may succeed or fail. -/// `Ok` means it was successful, `Error` means it failed. +/// Result represents the result of something that may succeed or not. +/// `Ok` means it was successful, `Error` means it was not successful. +/// pub type Result(success, error) = Result(success, error) @@ -9,14 +10,28 @@ pub type Result(success, error) = /// Unlike some other languages values cannot be implicitly nil, value that may /// be absent is typically represented using `Result(TheType, Nil)`. This is /// such a common type that offer the `Option(TheType)` alias. +/// pub type Nil = Nil /// A value that is either there or not there. +/// +/// Some other languages have a dedicated Option type that is not related to +/// Result for this, however this tends to have all the same functions as +/// Result so in Gleam we combine the two. +/// pub type Option(value) = Result(value, Nil) -/// Returns whether the value is Ok +/// Check whether the result is an Ok value. +/// +/// ## Examples +/// +/// > is_ok(Ok(1)) +/// True +/// +/// > is_ok(Error(Nil)) +/// False /// pub fn is_ok(result: Result(a, e)) -> Bool { case result { @@ -25,7 +40,16 @@ pub fn is_ok(result: Result(a, e)) -> Bool { } } -/// Returns whether the value is Error +/// Check whether the result is an Error value. +/// +/// ## Examples +/// +/// > is_error(Ok(1)) +/// False +/// +/// > is_error(Error(Nil)) +/// True +/// pub fn is_error(result: Result(a, e)) -> Bool { case result { Ok(_) -> False @@ -33,8 +57,19 @@ pub fn is_error(result: Result(a, e)) -> Bool { } } -/// Executes the function `with` on inner value when Result is Ok, will noop -/// if it is Error +/// Update a value held within the Ok of a result by calling a given function +/// on it. +/// +/// If the result is an Error rather than OK the function is not called and the +/// result stays the same. +/// +/// ## Examples +/// +/// > map(over: Ok(1), with: fn(x) { x + 1 }) +/// Ok(2) +/// +/// > map(over: Error(1), with: fn(x) { x + 1 }) +/// Error(1) /// pub fn map( over result: Result(a, e), @@ -46,8 +81,19 @@ pub fn map( } } -/// Will execute the function `with` on inner value when Result is Err, will noop -/// if it is Ok +/// Update a value held within the Error of a result by calling a given function +/// on it. +/// +/// If the result is Ok rather than Error the function is not called and the +/// result stays the same. +/// +/// ## Examples +/// +/// > map_error(over: Error(1), with: fn(x) { x + 1 }) +/// Error(2) +/// +/// > map_error(over: Ok(1), with: fn(x) { x + 1 }) +/// Ok(1) /// pub fn map_error( over result: Result(a, e), @@ -59,7 +105,18 @@ pub fn map_error( } } -/// Will unnest the inner value of a result nested within another result +/// Merge a nested Result into a single layer. +/// +/// ## Examples +/// +/// > flatten(Ok(Ok(1))) +/// Ok(1) +/// +/// > flatten(Ok(Error("")) +/// Error("") +/// +/// > flatten(Error(Nil)) +/// Error(Nil) /// pub fn flatten(result: Result(Result(a, e), e)) -> Result(a, e) { case result { @@ -68,9 +125,29 @@ pub fn flatten(result: Result(Result(a, e), e)) -> Result(a, e) { } } -/// Executes the function `apply` on inner value when Result is Ok, will noop -/// if it is Error -/// Equivalent to `map` followed by `flatten`. +/// Update a value held within the Ok of a result by calling a given function +/// on it, where the given function also returns a result. The two results are +/// then merged together into one result. +/// +/// If the result is an Error rather than OK the function is not called and the +/// result stays the same. +/// +/// This function is the equivalent of calling `map` followed by `flatten`, and +/// it is useful for chaining together multiple functions that may fail. +/// +/// ## Examples +/// +/// > then(Ok(1), fn(x) { Ok(x + 1) }) +/// Ok(2) +/// +/// > then(Ok(1), fn(x) { Ok(tuple("a", x)) }) +/// Ok(tuple("a", 1)) +/// +/// > then(Ok(1), fn(x) { Error("Oh no") }) +/// Error("Oh no") +/// +/// > then(Error(Nil), fn(x) { Ok(x + 1) }) +/// Error(Nil) /// pub fn then( result: Result(a, e), @@ -82,8 +159,16 @@ pub fn then( } } -/// Will return the inner value of a Ok value. If an error, will -/// return the value provided as `or` +/// Extract the Ok value from a result, returning a default value if the result +/// is an Error. +/// +/// ## Examples +/// +/// > unwrap(Ok(1), 0) +/// 1 +/// +/// > unwrap(Error(""), 0) +/// 0 /// pub fn unwrap(result: Result(a, e), or default: a) -> a { case result { @@ -99,7 +184,6 @@ pub fn unwrap(result: Result(a, e), or default: a) -> a { /// > none() /// Error(Nil) /// -/// pub fn none() -> Option(a) { Error(Nil) } diff --git a/src/gleam/string.gleam b/src/gleam/string.gleam index 2b59f60..de6f58a 100644 --- a/src/gleam/string.gleam +++ b/src/gleam/string.gleam @@ -1,6 +1,7 @@ -/// A built-in representation for efficient string manipulation. String literals -/// are enclosed in `"double quotes"`. -/// +//// Strings in Gleam are UTF-8 binaries. They can be written in your code a +//// text surrounded by `"double quotes"`. +//// + import gleam/iodata import gleam/list import gleam/order @@ -9,38 +10,48 @@ import gleam/result.{Option} pub type String = String -/// ## Basics - /// Determine if a string is empty. /// /// ## Examples +/// /// > is_empty("") /// True /// /// > is_empty("the world") /// False /// -/// pub fn is_empty(str: String) -> Bool { str == "" } -/// Get the length of a +/// Get the number of grapheme clusters in a given string. +/// +/// This function has to iterate across the whole string to count the number of +/// graphemes, so it runs in linear time. /// /// ## Examples +/// +/// > length("Gleam") +/// 5 +/// +/// > length("ß↑e̊") +/// 3 +/// /// > length("") /// 0 /// -/// pub external fn length(String) -> Int = "string" "length" /// Reverse a string. /// +/// This function has to iterate across the whole string so it runs in linear +/// time. +/// /// ## Examples +/// /// > reverse("stressed") /// "desserts" /// -/// pub fn reverse(string: String) -> String { string |> iodata.new @@ -48,16 +59,16 @@ pub fn reverse(string: String) -> String { |> iodata.to_string } -/// Replace all occurrences of some substring. +/// Create a new string by replacing all occurrences of a given substring. /// /// ## Examples -/// > replace("Json.Decode.succeed", each: ".", with: "-") -/// "Json-Decode-succeed" +/// +/// > replace("www.example.com", each: ".", with: "-") +/// "www-example-com" /// /// > replace("a,b,c,d,e", each: ",", with: "/") /// "a/b/c/d/e" /// -/// pub fn replace( in string: String, each pattern: String, @@ -69,55 +80,60 @@ pub fn replace( |> iodata.to_string } -/// Convert a string to all lower case. Useful for case-insensitive comparisons. +/// Create a new string with all the graphemes in the input string converted to +/// lowercase. +/// +/// Useful for case-insensitive comparisons. /// /// ## Examples +/// /// > lowercase("X-FILES") /// "x-files" /// -/// pub external fn lowercase(String) -> String = "string" "lowercase" -/// Convert a string to all upper case. Useful for case-insensitive comparisons -/// and VIRTUAL YELLING. +/// Create a new string with all the graphemes in the input string converted to +/// uppercase. +/// +/// Useful for case-insensitive comparisons and VIRTUAL YELLING. /// /// ## Examples +/// /// > uppercase("skinner") /// "SKINNER" /// -/// pub external fn uppercase(String) -> String = "string" "uppercase" -/// Determines the order of the two strings. +/// Compares two strings to see which is "larger" by comparing their graphemes. +/// +/// This does not compare the size or length of the given strings. /// /// ## Examples +/// /// > compare("Anthony", "Anthony") /// order.Eq /// /// > compare("A", "B") /// order.Gt /// -/// pub external fn compare(String, String) -> order.Order = "gleam_stdlib" "compare_strings" -/// ## Get Substrings - // TODO -/// Take a substring given a start and end Grapheme indexes. Negative indexes -/// are taken starting from the *end* of the list. -/// -/// ## Examples -/// > slice("gleam", from: 1, to: 3) -/// "lea" -/// -/// > slice("gleam", from: 1, to: 10) -/// "leam" -/// -/// > slice("snakes on a plane!", from: -6, to: -1) -/// "plane" -/// -/// +// Take a substring given a start and end Grapheme indexes. Negative indexes +// are taken starting from the *end* of the list. +// +// ## Examples +// > slice("gleam", from: 1, to: 3) +// "lea" +// +// > slice("gleam", from: 1, to: 10) +// "leam" +// +// > slice("snakes on a plane!", from: -6, to: -1) +// "plane" +// +// // pub fn slice(out_of string: String, from start: Int, end: Int) -> String {} // TODO @@ -131,20 +147,20 @@ pub external fn compare(String, String) -> order.Order = // pub fn drop_left(from string: String, up_to num_graphemes: Int) -> String {} // TODO -/// Drop *n* Graphemes from the right side of a -/// -/// ## Examples -/// > drop_right(from: "Cigarette Smoking Man", up_to: 2) -/// "Cigarette Smoking M" -/// -/// +// Drop *n* Graphemes from the right side of a +// +// ## Examples +// > drop_right(from: "Cigarette Smoking Man", up_to: 2) +// "Cigarette Smoking M" +// +// // pub fn drop_right(from string: String, up_to num_graphemes: Int) -> String {} -/// ## Check for Substrings /// Check if the first string contains the second. /// /// ## Examples +/// /// > contains(does: "theory", contain: "ory") /// True /// @@ -154,7 +170,6 @@ pub external fn compare(String, String) -> order.Order = /// > contains(does: "theory", contain: "THE") /// False /// -/// external fn erl_contains(String, String) -> Bool = "gleam_stdlib" "string_contains" @@ -164,34 +179,32 @@ pub fn contains(does haystack: String, contain needle: String) -> Bool { // TODO // TODO: Not sure about the name and labels here -/// See if the second string starts with the first one. -/// -/// ## Examples -/// > starts_with(does: "theory", start_with: "ory") -/// False -/// -/// +// See if the second string starts with the first one. +// +// ## Examples +// > starts_with(does: "theory", start_with: "ory") +// False +// +// // pub fn starts_with(does string: String, start_with prefix: String) -> String {} // TODO // TODO: Not sure about the name and labels here -/// See if the second string ends with the first one. -/// -/// ## Examples -/// > ends_with(does: "theory", end_with: "ory") -/// True -/// -/// +// See if the second string ends with the first one. +// +// ## Examples +// > ends_with(does: "theory", end_with: "ory") +// True +// +// // pub fn ends_with(does string: String, end_with suffix: String) -> String {} -/// ## Building and Splitting - -/// Split a string using a given separator. +/// Create a list of strings by splitting a given string on a given substring. /// /// ## Examples -/// > split("home/evan/Desktop/", on: "/") -/// ["home","evan","Desktop", ""] /// +/// > split("home/gleam/desktop/", on: "/") +/// ["home","gleam","desktop", ""] /// pub fn split(x: String, on substring: String) -> List(String) { x @@ -201,13 +214,17 @@ pub fn split(x: String, on substring: String) -> List(String) { } -/// Append two strings. +/// Create a new string by joining two strings together. +/// +/// This function copies both strings and runs in linear time. If you find +/// yourself joining strings frequently consider using the [iodata](../iodata) +/// module as it can append strings much faster! /// /// ## Examples +/// /// > append(to: "butter", suffix: "fly") /// "butterfly" /// -/// pub fn append(to first: String, suffix second: String) -> String { first |> iodata.new @@ -215,26 +232,23 @@ pub fn append(to first: String, suffix second: String) -> String { |> iodata.to_string } -/// Concatenate many strings into one. +/// Create a new string by joining many strings together. +/// +/// This function copies both strings and runs in linear time. If you find +/// yourself joining strings frequently consider using the [iodata](../iodata) +/// module as it can append strings much faster! /// /// ## Examples +/// /// > concat(["never", "the", "less"]) /// "nevertheless" /// -/// pub fn concat(strings: List(String)) -> String { strings |> iodata.from_strings |> iodata.to_string } -/// Repeat a string `n` times. -/// -/// ## Examples -/// > repeat("ha", times: 3) -/// "hahaha" -/// -/// fn repeat_help(chunk: String, result: List(String), repeats: Int) -> String { case repeats <= 0 { True -> concat(result) @@ -242,17 +256,27 @@ fn repeat_help(chunk: String, result: List(String), repeats: Int) -> String { } } +/// Create a new string by repeating a string a given number of times. +/// +/// This function runs in linear time. +/// +/// ## Examples +/// > repeat("ha", times: 3) +/// "hahaha" +/// pub fn repeat(string: String, times times: Int) -> String { repeat_help(string, [], times) } /// Join many strings together with a given separator. /// +/// This function runs in linear time. +/// /// ## Examples +/// /// > join(["home","evan","Desktop"], with: "/") /// "home/evan/Desktop" /// -/// pub fn join(strings: List(String), with separator: String) -> String { strings |> list.intersperse(_, with: separator) @@ -260,102 +284,84 @@ pub fn join(strings: List(String), with separator: String) -> String { |> iodata.to_string } -/// ## Formatting - // TODO -/// Pad a string on the left until it has at least given number of Graphemes. -/// -/// ## Examples -/// > pad_left("121", to: 5, with: ".") -/// "..121" -/// -/// > pad_left("121", to: 3, with: ".") -/// "121" -/// -/// > pad_left("121", to: 2, with: ".") -/// "121" -/// -/// +// Pad a string on the left until it has at least given number of Graphemes. +// +// ## Examples +// > pad_left("121", to: 5, with: ".") +// "..121" +// +// > pad_left("121", to: 3, with: ".") +// "121" +// +// > pad_left("121", to: 2, with: ".") +// "121" +// +// // pub fn pad_left(string: String, to size: Int, with: String) {} // TODO -/// Pad a string on the right until it has a given length. -/// -/// ## Examples -/// > pad_right("121", to: 5, with: ".") -/// "121.." -/// -/// > pad_right("121", to: 3, with: ".") -/// "121" -/// -/// > pad_right("121", to: 2, with: ".") -/// "121" -/// -/// +// Pad a string on the right until it has a given length. +// +// ## Examples +// > pad_right("121", to: 5, with: ".") +// "121.." +// +// > pad_right("121", to: 3, with: ".") +// "121" +// +// > pad_right("121", to: 2, with: ".") +// "121" +// +// // pub fn pad_right(string: String, to size: Int, with: String) {} // TODO -/// Get rid of whitespace on both sides of a String. -/// -/// ## Examples -/// > trim(" hats \n") -/// "hats" -/// -/// +// Get rid of whitespace on both sides of a String. +// +// ## Examples +// > trim(" hats \n") +// "hats" +// +// // pub fn trim(string: String) -> String {} // TODO -/// Get rid of whitespace on the left of a String. -/// -/// ## Examples -/// > trim_left(" hats \n") -/// "hats \n" -/// -/// +// Get rid of whitespace on the left of a String. +// +// ## Examples +// > trim_left(" hats \n") +// "hats \n" +// +// // pub fn trim_left(string: String) -> String {} // TODO -/// Get rid of whitespace on the right of a String. -/// -/// ## Examples -/// > trim_right(" hats \n") -/// " hats" -/// -/// +// Get rid of whitespace on the right of a String. +// +// ## Examples +// > trim_right(" hats \n") +// " hats" +// +// // pub fn trim_right(string: String) -> String {} -/// ## Grapheme Conversions - -// These functions convert to and from Grapheme, which currently -// does not exist as a type in Gleam. - // TODO // /// Convert a string to a list of Graphemes. // /// // /// > to_graphemes("abc") -/// ['a','b','c'] -/// +// ['a','b','c'] +// // /// // pub fn to_graphemes(string: String) -> List(String) {} // TODO -// /// Convert a list of characters into a String. Can be useful if you -// /// want to create a string primarily by consing, perhaps for decoding -// /// something. -// /// -// /// > from_list(['a','b','c']) -/// "abc" -/// -// /// -// // pub fn from_graphemes(graphemes: List(Grapheme)) -> String {} - -// TODO -/// Split a non-empty string into its head and tail. This lets you -/// pattern match on strings exactly as you would with lists. -/// -/// ## Examples -/// > next_grapheme("") -/// Error(Nil) -/// -/// +// Split a non-empty string into its head and tail. This lets you +// pattern match on strings exactly as you would with lists. +// +// ## Examples +// > next_grapheme("") +// Error(Nil) +// +// // pub fn next_grapheme(string: String) -> Option(tuple(Grapheme, String)) {} diff --git a/src/gleam_stdlib.erl b/src/gleam_stdlib.erl index 9e6a864..49a7e2a 100644 --- a/src/gleam_stdlib.erl +++ b/src/gleam_stdlib.erl @@ -39,28 +39,37 @@ iodata_prepend(Iodata, String) -> [String, Iodata]. identity(X) -> X. decode_error_msg(Type, Data) -> - {error, iolist_to_binary(io_lib:format("Expected ~s, got `~p`", [Type, Data]))}. + {error, iolist_to_binary(io_lib:format("Expected ~s, got ~s", [Type, classify(Data)]))}. + +classify(X) when is_atom(X) -> "an atom"; +classify(X) when is_binary(X) -> "a binary"; +classify(X) when is_integer(X) -> "an int"; +classify(X) when is_float(X) -> "a float"; +classify(X) when is_list(X) -> "a list"; +classify(X) when is_boolean(X) -> "a bool"; +classify(X) when is_function(X, 0) -> "a zero arity function"; +classify(_) -> "some other type". decode_atom(Data) when is_atom(Data) -> {ok, Data}; -decode_atom(Data) -> decode_error_msg("an Atom", Data). +decode_atom(Data) -> decode_error_msg("an atom", Data). decode_string(Data) when is_binary(Data) -> {ok, Data}; -decode_string(Data) -> decode_error_msg("a String", Data). +decode_string(Data) -> decode_error_msg("a string", Data). decode_int(Data) when is_integer(Data) -> {ok, Data}; -decode_int(Data) -> decode_error_msg("an Int", Data). +decode_int(Data) -> decode_error_msg("an int", Data). decode_float(Data) when is_float(Data) -> {ok, Data}; -decode_float(Data) -> decode_error_msg("a Float", Data). +decode_float(Data) -> decode_error_msg("a float", Data). decode_bool(Data) when is_boolean(Data) -> {ok, Data}; -decode_bool(Data) -> decode_error_msg("a Bool", Data). +decode_bool(Data) -> decode_error_msg("a bool", Data). decode_thunk(Data) when is_function(Data, 0) -> {ok, Data}; decode_thunk(Data) -> decode_error_msg("a zero arity function", Data). decode_list(Data) when is_list(Data) -> {ok, Data}; -decode_list(Data) -> decode_error_msg("a List", Data). +decode_list(Data) -> decode_error_msg("a list", Data). decode_field(Data, Key) -> case Data of @@ -74,7 +83,9 @@ decode_field(Data, Key) -> decode_element(Data, Position) when is_tuple(Data) -> case catch element(Position + 1, Data) of {'EXIT', _Reason} -> - {error, "Element position is out-of-bounds"}; + Reason = io_lib:format("Expected a tuple of at least ~w size, got a tuple of ~w sise", + [Position + 1, tuple_size(Data)]), + {error, Reason}; Value -> {ok, Value} diff --git a/test/gleam/dynamic_test.gleam b/test/gleam/dynamic_test.gleam index 080e6e7..2a777c7 100644 --- a/test/gleam/dynamic_test.gleam +++ b/test/gleam/dynamic_test.gleam @@ -19,12 +19,12 @@ pub fn string_test() { 1 |> dynamic.from |> dynamic.string - |> should.equal(_, Error("Expected a String, got `1`")) + |> should.equal(_, Error("Expected a string, got an int")) [] |> dynamic.from |> dynamic.string - |> should.equal(_, Error("Expected a String, got `[]`")) + |> should.equal(_, Error("Expected a string, got a list")) } pub fn int_test() { @@ -41,12 +41,12 @@ pub fn int_test() { 1.0 |> dynamic.from |> dynamic.int - |> should.equal(_, Error("Expected an Int, got `1.0`")) + |> should.equal(_, Error("Expected an int, got a float")) [] |> dynamic.from |> dynamic.int - |> should.equal(_, Error("Expected an Int, got `[]`")) + |> should.equal(_, Error("Expected an int, got a list")) } pub fn float_test() { @@ -63,12 +63,12 @@ pub fn float_test() { 1 |> dynamic.from |> dynamic.float - |> should.equal(_, Error("Expected a Float, got `1`")) + |> should.equal(_, Error("Expected a float, got an int")) [] |> dynamic.from |> dynamic.float - |> should.equal(_, Error("Expected a Float, got `[]`")) + |> should.equal(_, Error("Expected a float, got a list")) } pub fn thunk_test() { @@ -113,12 +113,12 @@ pub fn bool_test() { 1 |> dynamic.from |> dynamic.bool - |> should.equal(_, Error("Expected a Bool, got `1`")) + |> should.equal(_, Error("Expected a bool, got an int")) [] |> dynamic.from |> dynamic.bool - |> should.equal(_, Error("Expected a Bool, got `[]`")) + |> should.equal(_, Error("Expected a bool, got a list")) } pub fn atom_test() { |