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
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)