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.

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

Examples
let names = ["ada", "grace"]
names.first.or("nobody")        # => "ada"
names.at(9).or("nobody")        # => "nobody"
names.first.map(~upperCase)        # => Just("ADA")
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? Optionable

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 Optionable

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.

Examples
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? Resultable

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 Resultable

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.

Examples
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

either Eitherable

either(onLeft: (L -> A), onRight: (R -> A)) -> A

Case analysis: applies onLeft to a Left and onRight to a Right.

The way an Either is consumed without a match, and the operation Eitherable requires. Both branches answer the same type, so the result is a plain value rather than another Either.

Parameters
onLeft
applied to a Left
onRight
applied to a Right

Returns whichever branch ran

Examples
Left(2).either(~toString, ~upperCase)       # => "2"
Right("ok").either(~toString, ~upperCase)   # => "OK"

Collapsing an id to text

let render(id: Either<Integer, String>) -> String do
  id.either({ |n| "#${n}" }, { |slug| slug })
end

left?

left? : Bool

Returns true for a Left.

Examples
Left(1).left?    # => true
Right(1).left?   # => false

right?

right? : Bool

Returns true for a Right. The opposite of left?.

Examples
Right(1).right?   # => true
Left(1).right?    # => false

trait Optionable

A value that may be absent. Constrain a generic parameter with it when a function accepts any optional value.

The two operations below are what make the constraint worth having: an empty trait would accept a value and then let you do nothing with it, since there would be no method to call. map is deliberately NOT required — its result type differs per implementer (Y? here, Result<Y, E> for Resultable), which needs a higher-kinded parameter Kex does not have.

Implemented by Optional<X>.

Required methods

An implementor writes these.

set?

set? : Bool

Answers true when a value is present.

The one operation an Optionable type must define; none? is its negation.

Returns true for a present value

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

or

or(default: X) -> X

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

The way out of the optional world, and the reason a constrained parameter is usable at all.

Parameters
default
the value to use when absent

Returns the wrapped value, or default

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

trait Resultable

A value that either succeeded or failed with a reason. Constrain a generic parameter with it when a function accepts any result value.

Implemented by Result<X, E>.

Required methods

An implementor writes these.

ok?

ok? : Bool

Answers true for a success.

The one operation a Resultable type must define; error? is its negation.

Returns true for Ok

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

or

or(default: X) -> X

Returns the success value, or default on failure.

Parameters
default
the value to use on failure

Returns the Ok value, or default

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

trait Eitherable

One of two values, neither meaning failure. Constrain a generic parameter with it when a function accepts any either value.

Implemented by Either<L, R>.

Required methods

An implementor writes these.

either

either(onLeft: (L -> A), onRight: (R -> A)) -> A

Case analysis: applies onLeft to a Left and onRight to a Right.

The one operation an Eitherable type must define — left? and right? are both written in terms of it. A discriminator alone would let you ask which side a value is on without being able to reach it, which is why this is the requirement rather than those.

Parameters
onLeft
applied to a Left
onRight
applied to a Right

Returns whichever branch ran

Examples
Left(2).either(~toString, ~upperCase)      # => "2"
Right("ok").either(~toString, ~upperCase)  # => "OK"

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)