kex docs Standard Library

Optional

type Optional<X>

An optional value: Just(x) carries a value, None says there is none.

X? is shorthand for Optional<X>, and is the spelling you will normally write. Kex has no null — anything that might not produce a value returns an Optional instead, so the compiler makes you say what happens when it is empty. Most of the time that is a single .or(default) at the end of a chain.

let names = ["ada", "grace"]
names.first.or("nobody")        # => "ada"
names.at(9).or("nobody")        # => "nobody"
names.first.map(~upperCase)        # => Just("ADA")

Pattern matching handles the cases that need more than a default:

match config.get("port") do
  Just(port) => IO.printLine("listening on ${port}")
  None       => IO.printLine("no port configured")
end

Variants

  • Just(X)
  • None

Implements Blankable, Showable, Optionable, Truthyable.

Methods

set?

set? : Bool

Returns true when a value is present.

Examples
Just(42).set?   # => true
None.set?       # => false

Guarding on presence

let cached = lookup(key)
if cached.set?
  IO.printLine("hit")
end

none?

none? : Bool

Returns true when there is no value. The opposite of set?.

Examples
None.none?       # => true
Just(42).none?   # => false

Reporting a missing entry

if config.get("host").none?
  IO.printError("host is required")
end

or

or(default: X) -> X

Returns the wrapped value, or default when there is none.

This is the usual way an optional leaves the optional world: put .or at the end of a chain and the rest of your code works with a plain value.

Parameters
default
the value to use when None

Returns the wrapped value, or default

Examples
Just(42).or(0)   # => 42
None.or(0)       # => 0

Closing a chain of lookups

["a", "b"].first.or("?")            # => "a"
[].first.or("?")                    # => "?"
"hello".indexOf('z').or(-1)         # => -1

map

map(f: (X -> Y)) -> Y?

Applies f to the wrapped value, keeping the result wrapped. None is returned unchanged, so f never sees a missing value.

Parameters
f
applied to the value when present

Returns Just(f(x)), or None

Examples
Just(2).map { |x| x * 3 }   # => Just(6)
None.map { |x| x * 3 }      # => None

Transforming before supplying a default

["ada"].first.map(~upperCase).or("ANON")   # => "ADA"
[].first.map(~upperCase).or("ANON")        # => "ANON"

flatMap

flatMap(f: (X -> Y?)) -> Y?

Applies f, which itself returns an optional, and flattens the result.

Use it instead of map when the step can also fail — map would give you a doubly wrapped Just(Just(x)), flatMap gives a single layer. A None anywhere in the chain short-circuits the rest.

Parameters
f
the next optional-returning step

Returns the result of f, or None

Examples
Just(4).flatMap { |x| x > 0 then Just(x * 2) else None }   # => Just(8)
Just(-4).flatMap { |x| x > 0 then Just(x * 2) else None }  # => None
None.flatMap { |x| Just(x * 2) }                           # => None

Chaining lookups that may each come up empty

users.get(id)
  .flatMap { |user| user.address }
  .flatMap { |address| address.postcode }
  .or("unknown")

From Showable

Defined in other modules

type Result<X, E>

The outcome of an operation that can fail: Ok(x) on success, Error(e) on failure with a reason.

Use Result over Optional when the *reason* for failure matters to the caller. Parsing is the standard example: "12x".to(Integer) answers None, while Integer.parse("12x") answers an Error that says where it stopped.

Integer.parse("42").or(0)     # => 42
Integer.parse("4x").or(0)     # => 0

match Integer.parse(input) do
  Ok(n)    => IO.printLine("got ${n}")
  Error(e) => IO.printError("bad number: ${e}")
end

Variants

  • Ok(X)
  • Error(E)

Implements Showable, Resultable.

Methods

ok?

ok? : Bool

Returns true when the result is Ok.

Examples
Ok(42).ok?         # => true
Error("!").ok?     # => false

Counting successes

inputs.map(~Integer.parse).count(~ok?)

error?

error? : Bool

Returns true when the result is Error. The opposite of ok?.

Examples
Error("oops").error?   # => true
Ok(42).error?          # => false

or

or(default: X) -> X

Returns the Ok value, or default when the result is an Error.

The error payload is discarded. Match on the result instead when you need to report why it failed.

Parameters
default
the value to use on failure

Returns the Ok value, or default

Examples
Ok(42).or(0)       # => 42
Error("!").or(0)   # => 0

Parsing with a fallback

Integer.parse(input).or(8080)

map

map(f: (X -> Y)) -> Result<Y, E>

Applies f to the Ok value. An Error passes through untouched, so a chain of map calls describes the success path only.

Parameters
f
applied to the value on success

Returns Ok(f(x)), or the original Error

Examples
Ok(2).map { |x| x * 3 }        # => Ok(6)
Error("oops").map { |x| x }    # => Error("oops")

Converting a parsed value

Integer.parse("21").map { |n| n * 2 }   # => Ok(42)

flatMap

flatMap(f: (X -> Result<Y, E>)) -> Result<Y, E>

Applies f, which itself returns a Result, and flattens the result.

The step-by-step form of map for stages that can fail on their own. The first Error ends the chain and is the answer.

Parameters
f
the next fallible step

Returns the result of f, or the original Error

Examples
Ok(4).flatMap { |x| x > 0 then Ok(x * 2) else Error("neg") }    # => Ok(8)
Ok(-4).flatMap { |x| x > 0 then Ok(x * 2) else Error("neg") }   # => Error("neg")

A pipeline where each stage may fail

Integer.parse(raw)
  .flatMap { |n| n > 0 then Ok(n) else Error("must be positive") }
  .flatMap { |n| n < 65536 then Ok(n) else Error("out of range") }

optional

optional : X?

Converts the result to an Optional, dropping the error payload.

Useful when a caller only needs to know whether there is a value, and everything downstream already speaks Optional.

Returns Just of the Ok value, or None

Examples
Ok(42).optional          # => Just(42)
Error("oops").optional   # => None

Keeping only the values that parsed

["1", "x", "3"]
  .map { |s| Integer.parse(s).optional }
  .filter(~set?)
  .map { |o| o.or(0) }    # => [1, 3]

From Showable

Defined in other modules

type Either<L, R>

One of two values, of possibly different types: Left(l) or Right(r).

Unlike Result, neither side means failure — Either is for a value that is legitimately one of two shapes.

type Id = Either<Integer, String>

let describe(id: Id) -> String do
  match id do
    Left(n)  => "numeric id ${n}"
    Right(s) => "slug id ${s}"
  end
end

Variants

  • Left(L)
  • Right(R)

Implements Eitherable.

Methods

trait Optionable

Marker trait for Optional. Constrain a generic parameter with it when a function accepts any optional value.

Implemented by Optional<X>.

trait Resultable

Marker trait for Result. Constrain a generic parameter with it when a function accepts any result value.

Implemented by Result<X, E>.

trait Eitherable

Marker trait for Either. Constrain a generic parameter with it when a function accepts any either value.

Implemented by Either<L, R>.

function or

or(value: A, _) -> A

Returns the value unchanged.

The catch-all clause of or: a value that is neither an Optional nor a Result has already succeeded, so there is nothing to fall back to. This is what lets .or(default) be written after a call whose return type may later stop being optional, without the call site changing.

Parameters
value
any plain value

Returns the same value

Examples
42.or(0)        # => 42
"text".or("")   # => "text"

function to

to(value, t: Type, radix: Integer) -> A?

Converts value to the type t, or None if it cannot be represented.

t is a runtime type value — write the type name itself: String, Integer, Float, List. Conversion to String goes through the Showable protocol, so it works for every value.

The result is an Optional, not a Result, and deliberately so: the reason a conversion failed is usually implied by the types alone. When the reason carries information — where a parse gave up and on what — reach for Integer.parse or Float.parse, which answer with Result<_, ParseError>. So: to for every day, parse when the failure needs handling.

Parameters
t
the target type

Returns the converted value, or None

Examples

Numbers and strings

42.to(String)        # => Just("42")
"42".to(Integer)     # => Just(42)
"4x".to(Integer)     # => None
"3.5".to(Float)      # => Just(3.5)

Closing the conversion immediately

let port = env.get("PORT").flatMap { |s| s.to(Integer) }.or(8080)