Result type with Ok/Err, map, flat_map, and pattern matching
gem install philiprehberger-resultResult type with Ok/Err, map, flat_map, and pattern matching
Add to your Gemfile:
gem "philiprehberger-result"
Or install directly:
gem install philiprehberger-result
require "philiprehberger/result"
# Create results
ok = Philiprehberger::Result.ok(42)
err = Philiprehberger::Result.err("not found")
ok.ok? # => true
err.err? # => true
result = Philiprehberger::Result.ok(5)
result.map { |v| v * 2 } # => Ok(10)
result.flat_map { |v| Result.ok(v + 1) } # => Ok(6)
err = Philiprehberger::Result.err("bad input")
err.map { |v| v * 2 } # => Err("bad input") (no-op)
err.map_err { |e| e.upcase } # => Err("BAD INPUT")
ok = Philiprehberger::Result.ok(42)
ok.unwrap! # => 42
ok.unwrap_or(0) # => 42
err = Philiprehberger::Result.err("fail")
err.unwrap_or(0) # => 0
err.unwrap! # raises UnwrapError
# Compute a fallback from the error:
err.unwrap_or_else { |e| "recovered from #{e}" } # => "recovered from fail"
ok.unwrap_or_else { |e| "recovered from #{e}" } # => 42
result = Philiprehberger::Result.try { Integer("123") }
# => Ok(123)
result = Philiprehberger::Result.try { Integer("abc") }
# => Err(#<ArgumentError: invalid value for Integer(): "abc">)
# Catch specific exceptions
result = Philiprehberger::Result.try(IOError) { File.read("missing") }
# Execute a side-effect on Ok without changing the result
Philiprehberger::Result.ok(42)
.tap_ok { |v| puts "Got value: #{v}" }
.map { |v| v * 2 }
# prints "Got value: 42", returns Ok(84)
# Execute a side-effect on Err without changing the result
Philiprehberger::Result.err("fail")
.tap_err { |e| logger.error(e) }
.or_else { |e| Philiprehberger::Result.ok("default") }
# logs "fail", returns Ok("default")
result = Philiprehberger::Result.ok(5)
# Convert Ok to Err when predicate fails
result.filter(-> { "must be negative" }) { |v| v < 0 }
# => Err("must be negative")
result.filter(-> { "must be positive" }) { |v| v > 0 }
# => Ok(5)
# Err passes through unchanged
Philiprehberger::Result.err("already failed")
.filter(-> { "unused" }) { |v| v > 0 }
# => Err("already failed")
ok1 = Philiprehberger::Result.ok(1)
ok2 = Philiprehberger::Result.ok(2)
err = Philiprehberger::Result.err("fail")
Philiprehberger::Result.all([ok1, ok2])
# => Ok([1, 2])
Philiprehberger::Result.all([ok1, err, ok2])
# => Err("fail")
result = Philiprehberger::Result.err("not found")
# Recover with or_else
recovered = result.or_else { |e| Philiprehberger::Result.ok("default") }
# => Ok("default")
# Chain with and_then (alias for flat_map)
Philiprehberger::Result.ok(5)
.and_then { |v| Philiprehberger::Result.ok(v * 2) }
.and_then { |v| Philiprehberger::Result.ok(v + 1) }
# => Ok(11)
Philiprehberger::Result.ok(42).to_h # => { ok: 42 }
Philiprehberger::Result.err("fail").to_h # => { err: "fail" }
anyresults = [Result.err("timeout"), Result.ok(42), Result.ok(99)]
Result.any(results).unwrap! # => 42
zipa = Result.ok(1)
b = Result.ok(2)
a.zip(b).unwrap! # => [1, 2]
nested = Philiprehberger::Result.ok(Philiprehberger::Result.ok(42))
Philiprehberger::Result.flatten(nested).unwrap! # => 42
nested = Philiprehberger::Result.ok(Philiprehberger::Result.err("fail"))
Philiprehberger::Result.flatten(nested).err? # => true
Result.err(ArgumentError.new("bad"))
.recover(ArgumentError) { |e| "default" }
.unwrap! # => "default"
# Extract the value, collapsing Err to nil
Philiprehberger::Result.ok(42).to_maybe # => 42
Philiprehberger::Result.err("fail").to_maybe # => nil
# Note: Ok(nil) and Err(anything) both return nil
Philiprehberger::Result.ok(nil).to_maybe # => nil
# Test whether an Ok holds a specific value
Philiprehberger::Result.ok(42).contains?(42) # => true
Philiprehberger::Result.ok(42).contains?(99) # => false
Philiprehberger::Result.err("fail").contains?(42) # => false
# Test whether an Err holds a specific error
Philiprehberger::Result.err("fail").contains_err?("fail") # => true
Philiprehberger::Result.err("fail").contains_err?("other") # => false
Philiprehberger::Result.ok(42).contains_err?("fail") # => false
results = [
Philiprehberger::Result.ok(1),
Philiprehberger::Result.err("a"),
Philiprehberger::Result.ok(2),
Philiprehberger::Result.err("b")
]
values, errors = Philiprehberger::Result.partition(results)
# values => [1, 2]
# errors => ["a", "b"]
case Philiprehberger::Result.ok(42)
in Philiprehberger::Result::Ok[value]
puts "Success: #{value}"
in Philiprehberger::Result::Err[error]
puts "Error: #{error}"
end
| Method / Class | Description |
|---|---|
Result.ok(value) | Create a success result |
Result.err(error) | Create a failure result |
Result.try(*exceptions, &block) | Wrap a block, capturing exceptions as Err |
Result.all(results) | Combine results: Ok with values array, or first Err |
Ok#map { |v| ... } | Transform the success value |
Ok#flat_map { |v| ... } | Chain a result-returning operation |
Ok#unwrap! | Return the value |
Ok#unwrap_or(default) | Return the value (ignores default) |
Ok#unwrap_or_else { |e| ... } | Return the value (block is ignored) |
#tap_ok { |v| ... } | Side-effect on Ok value, returns self |
#tap_err { |e| ... } | Side-effect on Err value, returns self |
#filter(error_fn) { |v| ... } | Convert Ok to Err if predicate fails |
Err#map_err { |e| ... } | Transform the error value |
Err#unwrap! | Raise UnwrapError |
Err#unwrap_or(default) | Return the default |
Err#unwrap_or_else { |e| ... } | Call block with the error and return its result |
Ok#or_else { |e| ... } | Return self (no-op on Ok) |
Err#or_else { |e| ... } | Call block with error for recovery |
#and_then { |v| ... } | Alias for flat_map |
Result.any(results) | First Ok, or Err with all errors |
#zip(other) | Combine two Ok results into Ok([a, b]) |
#recover(error_class) { block } | Recover from specific error types |
Ok#unwrap_err! | Raise UnwrapError (no error to extract) |
Err#unwrap_err! | Return the error value |
Result.flatten(result) | Flatten nested Result (Ok(Ok(v)) to Ok(v)) |
#map_or(default) { |v| ... } | Map value with fallback for Err |
#to_h | Serialize to { ok: value } or { err: error } |
#to_maybe | Return the value on Ok, nil on Err |
#contains?(value) | Return true if Ok equals value |
#contains_err?(error) | Return true if Err equals error |
Result.partition(results) | Split into [ok_values, err_values] arrays |
bundle install
bundle exec rspec
bundle exec rubocop
If you find this project useful: