Class: Errgonomic::Option::Any

Inherits:
Object
  • Object
show all
Includes:
Comparable
Defined in:
lib/errgonomic/option.rb,
lib/errgonomic/rails/active_record_optional.rb

Overview

An Option is already lifted. Lifting it again would nest it, and the nesting is invisible until something reaches for the inner value.

Direct Known Subclasses

None, Some

Constant Summary collapse

RUST_SPELLINGS =

Rust spellings we accept but do not advertise: they delegate to the Ruby-idiomatic predicate and nudge the caller there via stderr.

{
  is_some: :some?,
  is_none: :none?,
  is_some_and: :some_and?,
  is_none_or: :none_or?
}.freeze
NUDGED =

Names already nudged about. A soft deprecation is a message to a developer, and one per process says it; one per call turns a hot path into a stderr flood.

Set.new

Instance Method Summary collapse

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(name, *args, &block) ⇒ Object

An Option deliberately forwards nothing to its inner value, so a miss here is almost always someone treating the container as its contents. Teach the route out instead of leaving a bare NoMethodError. Rust spellings of the predicates delegate, with a nudge on stderr.

Examples:

begin
  Some(5) + 1
rescue NoMethodError => e
  e.class
end # => Errgonomic::UnwrappedAccessError
Some(5).respond_to?(:+) # => false
Some(1).is_some_and { |x| x > 0 } # => true
None().is_none # => true
Some(5).respond_to?(:is_some) # => true

Raises:



47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/errgonomic/option.rb', line 47

def method_missing(name, *args, &block)
  if (canonical = RUST_SPELLINGS[name])
    warn "Errgonomic: `#{name}` is the Rust spelling; prefer `#{canonical}`. Delegating."
    return public_send(canonical, *args, &block)
  end

  raise Errgonomic::UnwrappedAccessError.new("    undefined method `\#{name}' for \#{inspect}, an Option, which does not forward methods to its inner value.\n    Reach for a combinator instead:\n      map, and_then, filter: transform the value if present\n      unwrap_or, unwrap_or_else: supply a fallback\n      ok_or, ok_or_else: convert to a Result\n      some_and?, none_or?: test a predicate against the inner value\n    unwrap! and expect! also exist, but are intended for tests rather than application code.\n  MSG\nend\n", name)

Instance Method Details

#!=(other) ⇒ Object

Ruby derives != from ==, so a strict-equality message would name the operator the caller did not write.



187
188
189
190
# File 'lib/errgonomic/option.rb', line 187

def !=(other)
  strict_equality!(other, '!=')
  super
end

#<=>(other) ⇒ Object

Options order like Rust's: None sorts before any Some, and Somes order by their inner values. Two Options whose inner values do not themselves compare follow Ruby's convention and answer nil. A non-Option operand raises instead: Comparable turns a nil here into an ArgumentError that names the Option as the operand at fault, where what went wrong is that a wrapper was ordered against a bare value.

Examples:

(Some(5) <=> Some(6)) # => -1
(None() <=> Some(5)) # => -1
(Some(5) <=> None()) # => 1
(None() <=> None()) # => 0
(Some(1) <=> Some("x")) # => nil
[Some(2), None(), Some(1)].sort # => [None(), Some(1), Some(2)]
[Some(2), Some(1)].min # => Some(1)

a bare value is not ordered against an Option

Some(5) <= 6 # => raise Errgonomic::TypeMismatchError, "cannot compare Some(5) with Integer; test the inner value (some_and? { |v| v <= other }) or reach for it (map, unwrap_or)"
Some(5).some_and? { |v| v <= 6 } # => true
Some(5).map { |v| v <= 6 } # => Some(true)


283
284
285
286
287
288
289
290
291
292
293
294
# File 'lib/errgonomic/option.rb', line 283

def <=>(other)
  unless other.is_a?(Errgonomic::Option::Any)
    raise Errgonomic::TypeMismatchError,
          "cannot compare #{inspect} with #{other.class}; test the inner value " \
          '(some_and? { |v| v <= other }) or reach for it (map, unwrap_or)'
  end

  return none? ? 0 : 1 if other.none?
  return -1 if none?

  value <=> other.value
end

#==(other) ⇒ Object

An Option equals another Option of the same class with an equal inner value. Comparing it with anything that is not an Option raises Errgonomic::TypeMismatchError, naming both sides and the spelling to reach for. Some(5) == 5 is the comparison Rust rejects at compile time, and a quiet false there is a silent wrong branch, the same failure as a wrapper written into a string. The raise reaches ==, !=, eql? and ===, and through them every collection operation that compares pairwise. Ruby's hashing compares hash values first and asks eql? only of a candidate whose hash matches, so a Hash lookup, a Set and uniq stay quiet with a wrong-typed key: strict equality never answers wrong, it only sometimes fails to catch. nil == Some(1) is answered by NilClass and cannot be intercepted.

None() == nil raises too: None is a value that represents absence, not an absence Ruby can see, and the message points at none?. (The Rails integration separately makes None#nil? answer true, as an ActiveRecord compromise; equality does not follow it.)

Examples:

Some(1) == Some(1) # => true
Some(1) == Some(2) # => false
Some(1) == None() # => false
None() == None() # => true

a cross-type comparison is an error, never a quiet false

Some(5) == 5 # => raise Errgonomic::TypeMismatchError, "Errgonomic::Option::Some == Integer, which strict equality refuses.\nCompare Options (opt == Some(5)), test the inner value (opt.some_and? { |v| v == 5 }), or unwrap_or a fallback first."
Some(5) != 5 # => raise Errgonomic::TypeMismatchError, "Errgonomic::Option::Some != Integer, which strict equality refuses.\nCompare Options (opt == Some(5)), test the inner value (opt.some_and? { |v| v == 5 }), or unwrap_or a fallback first."
Some(5) === 5 # => raise Errgonomic::TypeMismatchError, "Errgonomic::Option::Some === Integer, which strict equality refuses.\nCompare Options (opt == Some(5)), test the inner value (opt.some_and? { |v| v == 5 }), or unwrap_or a fallback first."
Some(5) === Some(5) # => true
1 == Some(1) # => raise Errgonomic::TypeMismatchError, "Errgonomic::Option::Some == Integer, which strict equality refuses.\nCompare Options (opt == Some(1)), test the inner value (opt.some_and? { |v| v == 1 }), or unwrap_or a fallback first."

a Result is another container, not another Option

Some(1) == Ok(1) # => raise Errgonomic::TypeMismatchError, "Errgonomic::Option::Some == Errgonomic::Result::Ok, which strict equality refuses.\nAn Option and a Result are different containers, and neither is the other. Unwrap the one you meant (opt.unwrap_or(nil) == res.unwrap_or(nil))."

nil is another type, and absence here is the discriminant

None() == nil # => raise Errgonomic::TypeMismatchError, "Errgonomic::Option::None == NilClass, which strict equality refuses.\nAbsence here is the discriminant: ask none?, or nil? under the Rails integration."

the raise reaches every operation that compares pairwise

begin
  [Some(1)].include?(1)
rescue Errgonomic::TypeMismatchError => e
  e.class
end # => Errgonomic::TypeMismatchError
begin
  [Some(1)] == [1]
rescue Errgonomic::TypeMismatchError => e
  e.class
end # => Errgonomic::TypeMismatchError
begin
  [Some(1), 1] - [1]
rescue Errgonomic::TypeMismatchError => e
  e.class
end # => Errgonomic::TypeMismatchError
begin
  { a: Some(1) } == { a: 1 }
rescue Errgonomic::TypeMismatchError => e
  e.class
end # => Errgonomic::TypeMismatchError
begin
  case 5
  when Some(5) then :hit
  end
rescue Errgonomic::TypeMismatchError => e
  e.class
end # => Errgonomic::TypeMismatchError

hashing compares hash values first, so these stay quiet

{ Some(1) => :v }[1] # => nil
Set[Some(1)].include?(1) # => false
[Some(1), 1].uniq # => [Some(1), 1]

a short array compares member by member with eql?, so the side the wrapper is on decides

[1] - [Some(1)] # => [1]
[Some(1)] | [1] # => [Some(1), 1]
[1] | [Some(1)] # => raise Errgonomic::TypeMismatchError, "Errgonomic::Option::Some eql? Integer, which strict equality refuses.\nCompare Options (opt == Some(1)), test the inner value (opt.some_and? { |v| v == 1 }), or unwrap_or a fallback first."

nil and String answer for themselves, and never ask the Option

nil == Some(1) # => false
"a" == Some("a") # => false


147
148
149
150
151
152
153
# File 'lib/errgonomic/option.rb', line 147

def ==(other)
  strict_equality!(other, '==')
  return false if self.class != other.class
  return true if none?

  value == other.value
end

#===(other) ⇒ Object

Object#=== is ==, so a case value when Some(5) and a pinned pattern reach the same check, named for the operator that was written.



157
158
159
160
# File 'lib/errgonomic/option.rb', line 157

def ===(other)
  strict_equality!(other, '===')
  self == other
end

#and(other) ⇒ Object

If self is Some, return the provided other Option. The operand is checked on both variants, so a None-heavy path still learns that it was handed a bare value.

Examples:

None().and(Some(1)) # => None()
Some(2).and(Some(3)) # => Some(3)
Some(2).and(3) # => raise Errgonomic::ArgumentError, "other must be an Option, was Integer"
None().and(3) # => raise Errgonomic::ArgumentError, "other must be an Option, was Integer"


705
706
707
708
709
710
# File 'lib/errgonomic/option.rb', line 705

def and(other)
  option_operand!(other)
  return self if none?

  other
end

#and_then(&block) ⇒ Object

If self is Some, call the given block with the inner value and return its result. Block must return an Option.

Examples:

None().and_then { |x| Some(x + 1) } # => None()
Some(2).and_then { |x| Some(x + 1) } # => Some(3)


718
719
720
721
722
723
724
725
726
727
# File 'lib/errgonomic/option.rb', line 718

def and_then(&block)
  return self if none?

  val = block.call(value)
  if !Errgonomic.give_me_ambiguous_downstream_errors? && !val.is_a?(Errgonomic::Option::Any)
    raise Errgonomic::ArgumentError.new, "block must return an Option, was #{val.class.name}"
  end

  val
end

#as_json(*_args) ⇒ Object

ActiveSupport's Hash#as_json and Array#as_json recurse through their members with as_json rather than to_json, so an Option nested in a payload reaches Object#as_json and serializes as its instance variables. Refuse there too, and the guard holds wherever an Option travels.



804
805
806
# File 'lib/errgonomic/option.rb', line 804

def as_json(*_args)
  raise Errgonomic::SerializeError, serialize_refusal
end

#blank?Boolean

Examples:

None().blank? # => true
Some(1).blank? # => false
Some(nil).blank? # => false

Returns:

  • (Boolean)


341
342
343
# File 'lib/errgonomic/option.rb', line 341

def blank?
  none?
end

#blank_or(_default) ⇒ Object

Examples:

the blank side of the presence family teaches the combinators

begin
  None().blank_or("x")
rescue NoMethodError => e
  e.class
end # => Errgonomic::UnwrappedAccessError


457
458
459
# File 'lib/errgonomic/option.rb', line 457

def blank_or(_default)
  raise_blank_side_teaching(:blank_or)
end

#blank_or_else(&_block) ⇒ Object

Examples:

begin
  Some(1).blank_or_else { :x }
rescue NoMethodError => e
  e.class
end # => Errgonomic::UnwrappedAccessError


467
468
469
# File 'lib/errgonomic/option.rb', line 467

def blank_or_else(&_block)
  raise_blank_side_teaching(:blank_or_else)
end

#blank_or_raise!(_message) ⇒ Object Also known as: blank_or_raise

Examples:

begin
  None().blank_or_raise!("msg")
rescue NoMethodError => e
  e.class
end # => Errgonomic::UnwrappedAccessError


477
478
479
# File 'lib/errgonomic/option.rb', line 477

def blank_or_raise!(_message)
  raise_blank_side_teaching(:blank_or_raise!)
end

#deconstructObject

The Rust shape: a Some deconstructs to its one payload and a None to nothing, so in Some(v) binds the value and in None matches. There is no deconstruct_keys, because a one-payload sum type has no named field; a Some wrapping a Hash nests as in Some({id:}) through the Hash's own protocol.

Examples:

Some(1).deconstruct # => [1]
None().deconstruct # => []
Some(1).respond_to?(:deconstruct_keys) # => false

a two-branch case/in with no else is exhaustive

measurement = Some(1)
case measurement
in Some(value)
  "Measurement is #{value}"
in None
  "Measurement is not available"
end # => "Measurement is 1"
case None()
in Some(value)
  "Measurement is #{value}"
in None
  "Measurement is not available"
end # => "Measurement is not available"

the wrong type falls through to Ruby's own exhaustiveness check

begin
  case 1
  in Some(value) then value
  in None then nil
  end
rescue NoMatchingPatternError => e
  [e.class, e.message]
end # => [NoMatchingPatternError, "1"]

a Result that falls through carries a message that refuses to print

begin
  case Ok(1)
  in Some(value) then value
  in None then nil
  end
rescue NoMatchingPatternError => e
  [e.class, (e.message rescue $!.class)]
end # => [NoMatchingPatternError, Errgonomic::SerializeError]

patterns nest through the inner value's own protocol

case Ok(Some(1))
in Ok(Some(value)) then value
end # => 1
case Some({ id: 7, name: 'x' })
in Some({ id: }) then id
end # => 7
case Some(1)
in Errgonomic::Option::Some(value) then "bound #{value}"
else "not matched"
end # => "bound 1"


259
260
261
# File 'lib/errgonomic/option.rb', line 259

def deconstruct
  to_a
end

#each(&block) ⇒ Object

Yields the inner value once for a Some and not at all for a None, so an Option reads as the zero-or-one collection it is, and answers an Enumerator without a block. Option does not include Enumerable: its own filter and first answer Options, where Enumerable's answer plain values, and one name cannot mean both.

Examples:

seen = []
Some(1).each { |x| seen << x } # => Some(1)
seen # => [1]
None().each { |x| seen << x } # => None()
seen # => [1]
Some(1).each.to_a # => [1]
None().each.to_a # => []
Some(2).each.map { |x| x * 3 } # => [6]
Some(1).each.size # => 1
None().each.size # => 0


510
511
512
513
514
515
# File 'lib/errgonomic/option.rb', line 510

def each(&block)
  return to_enum(:each) { some? ? 1 : 0 } unless block

  block.call(value) if some?
  self
end

#eql?(other) ⇒ Boolean

Hash-based collections (Hash keys, Set, uniq, group_by) use eql? and hash, not ==. Follow the inner value's own eql? semantics, so Options behave as keys exactly like their inner values: Some(1) and Some(1.0) are distinct keys, just as 1 and 1.0 are.

Examples:

Some(5).eql?(Some(5)) # => true
Some(1).eql?(Some(1.0)) # => false
None().eql?(None()) # => true
{ Some(5) => 1 }[Some(5)] # => 1
[Some(1), Some(1), None(), None()].uniq # => [Some(1), None()]

a cross-type eql? raises as == does, and hash is untouched

Some(5).eql?(5) # => raise Errgonomic::TypeMismatchError, "Errgonomic::Option::Some eql? Integer, which strict equality refuses.\nCompare Options (opt == Some(5)), test the inner value (opt.some_and? { |v| v == 5 }), or unwrap_or a fallback first."
Some(5).hash == Some(5).hash # => true

Returns:

  • (Boolean)


177
178
179
180
181
182
183
# File 'lib/errgonomic/option.rb', line 177

def eql?(other)
  strict_equality!(other, 'eql?')
  return false if self.class != other.class
  return true if none?

  value.eql?(other.value)
end

#expect!(msg = nil, &block) ⇒ Object

Returns the inner value of a Some, else raises with the given message. A block is called only on the None branch, so a message that interpolates costs nothing on the path that succeeds.

Examples:

Some(1).expect!("msg") # => 1
None().expect!("here's why this failed") # => raise Errgonomic::ExpectError, "here's why this failed"
Some(1).expect! { "built only where it is raised" } # => 1
None().expect! { "no tier for #{7}" } # => raise Errgonomic::ExpectError, "no tier for 7"

Raises:



536
537
538
539
540
# File 'lib/errgonomic/option.rb', line 536

def expect!(msg = nil, &block)
  raise Errgonomic::ExpectError, block ? block.call : msg if none?

  value
end

#filter(&block) ⇒ Object

Return self if the predicate is truthy for the inner value, else None. None passes through.

Examples:

Some(1).filter(&:odd?) # => Some(1)
Some(2).filter(&:odd?) # => None()
None().filter(&:odd?) # => None()


821
822
823
824
825
# File 'lib/errgonomic/option.rb', line 821

def filter(&block)
  return self if none?

  block.call(value) ? self : None()
end

#flattenObject

Remove one level of Option nesting. Pedantically raises when the inner value is not itself an Option, which in Rust would not have compiled.

Examples:

Some(Some(1)).flatten # => Some(1)
Some(None()).flatten # => None()
None().flatten # => None()
Some(Some(Some(1))).flatten # => Some(Some(1))
Some(1).flatten # => raise Errgonomic::TypeMismatchError, "cannot flatten Integer; it is not an Option"


836
837
838
839
840
841
842
843
844
845
# File 'lib/errgonomic/option.rb', line 836

def flatten
  return self if none?

  unless value.is_a?(Errgonomic::Option::Any)
    raise Errgonomic::TypeMismatchError,
          "cannot flatten #{value.class}; it is not an Option"
  end

  value
end

#hashObject

Examples:

Some(5).hash == Some(5).hash # => true
None().hash == None().hash # => true
Some(5).hash == None().hash # => false


196
197
198
199
200
# File 'lib/errgonomic/option.rb', line 196

def hash
  return self.class.hash if none?

  [self.class, value].hash
end

#map(&block) ⇒ Object

Maps the Option to another Option by applying a function to the contained value (if Some) or returns None. Whatever the block returns is wrapped, as in Rust: a block that returns an Option gives Some(Some(x)). and_then is the spelling for a block that returns an Option.

Examples:

Some(1).map { |x| x + 1 } # => Some(2)
None().map { |x| x + 1 } # => None()
Some(1).map { |x| Some(x + 1) } # => Some(Some(2))
Some(1).and_then { |x| Some(x + 1) } # => Some(2)


594
595
596
597
598
# File 'lib/errgonomic/option.rb', line 594

def map(&block)
  return self if none?

  Some(block.call(value))
end

#map_or(default, &block) ⇒ Object

Returns the provided default (if none), or the block applied to the contained value (if some). Both come back bare, as Rust's map_or gives: this is the exit from the Option, where map stays inside it. Use map_or_else when the default is expensive to build.

Examples:

None().map_or(1) { 100 } # => 1
Some(1).map_or(100) { |x| x + 1 } # => 2
Some("foo").map_or(0) { |str| str.length } # => 3
Some(2).map_or(0) { |x| x * 2 } # => 4


610
611
612
613
614
# File 'lib/errgonomic/option.rb', line 610

def map_or(default, &block)
  return default if none?

  block.call(value)
end

#map_or_else(proc, &block) ⇒ Object

Computes a default from the given Proc if None, or applies the block to the contained value (if Some). Both come back bare, as map_or's do.

Examples:

None().map_or_else(-> { :foo }) { :bar } # => :foo
Some("str").map_or_else(-> { 100 }) { |str| str.length } # => 3
None().map_or_else(-> { nil }) { |str| str.length } # => nil


623
624
625
626
627
# File 'lib/errgonomic/option.rb', line 623

def map_or_else(proc, &block)
  return proc.call if none?

  block.call(value)
end

#none_or(&block) ⇒ Object Also known as: none_or?

return true if the contained value is None or the block returns truthy

Examples:

None().none_or { false } # => true
Some(1).none_or { |x| x > 0 } # => true
Some(1).none_or { |x| x < 0 } # => false


316
317
318
319
320
# File 'lib/errgonomic/option.rb', line 316

def none_or(&block)
  return true if none?

  !!block.call(value)
end

#ok_or(err) ⇒ Object

Transforms the option into a result, mapping Some(v) to Ok(v) and None to Err(err)

Examples:

None().ok_or("wow") # => Err("wow")
Some(1).ok_or("such err") # => Ok(1)

there is no bare ok: an Err always names its error

begin
  None().ok
rescue NoMethodError => e
  e.class
end # => Errgonomic::UnwrappedAccessError
begin
  Some(1).ok
rescue NoMethodError => e
  e.class
end # => Errgonomic::UnwrappedAccessError
Some(1).respond_to?(:ok) # => false


647
648
649
650
651
# File 'lib/errgonomic/option.rb', line 647

def ok_or(err)
  return Errgonomic::Result::Ok.new(value) if some?

  Errgonomic::Result::Err.new(err)
end

#ok_or_else(&block) ⇒ Object

Transforms the option into a result, mapping Some(v) to Ok(v) and None to Err(err). TODO: block or proc?

Examples:

None().ok_or_else { "wow" } # => Err("wow")
Some("foo").ok_or_else { "such err" } # => Ok("foo")


659
660
661
662
663
# File 'lib/errgonomic/option.rb', line 659

def ok_or_else(&block)
  return Errgonomic::Result::Ok.new(value) if some?

  Errgonomic::Result::Err.new(block.call)
end

#or(other) ⇒ Object

Returns the option if it contains a value, otherwise returns the provided Option. Returns an Option.

Examples:

None().or(Some(1)) # => Some(1)
Some(2).or(Some(3)) # => Some(2)
None().or(2) # => raise Errgonomic::ArgumentError, "other must be an Option, was Integer"
Some(1).or(2) # => raise Errgonomic::ArgumentError, "other must be an Option, was Integer"


672
673
674
675
676
677
# File 'lib/errgonomic/option.rb', line 672

def or(other)
  option_operand!(other)
  return self if some?

  other
end

#or_else(&block) ⇒ Object

Returns the option if it contains a value, otherwise calls the block and returns the result. Returns an Option.

Examples:

None().or_else { Some(1) } # => Some(1)
Some(2).or_else { Some(3) } # => Some(2)
None().or_else { 2 } # => raise Errgonomic::ArgumentError.new, "block must return an Option, was Integer"


685
686
687
688
689
690
691
692
693
694
# File 'lib/errgonomic/option.rb', line 685

def or_else(&block)
  return self if some?

  val = block.call
  if !val.is_a?(Errgonomic::Option::Any) && !Errgonomic.give_me_ambiguous_downstream_errors?
    raise Errgonomic::ArgumentError.new, "block must return an Option, was #{val.class.name}"
  end

  val
end

#presenceObject?

Returns the inner value of a Some, and nil on a None, so the Rails presence || default idiom reaches the value rather than the wrapper. Presence follows the discriminant, so a blank inner value is still a value: Some("").presence is "", where Object#presence answers nil.

Examples:

Some("secret").presence # => "secret"
Some("").presence # => ""
None().presence # => nil
None().presence || "fallback" # => "fallback"

the Rails spelling of unwrap_or(nil), and no nudge with it

nudges = StringIO.new
original = $stderr
begin
  $stderr = nudges
  captured = Some("").presence
  None().presence
ensure
  $stderr = original
end
captured # => ""
nudges.string # => ""

Returns:

  • (Object, nil)

    The inner value of a Some, otherwise nil.



445
446
447
448
449
# File 'lib/errgonomic/option.rb', line 445

def presence
  return nil if none?

  value
end

#present?Boolean

Presence follows the discriminant, not the inner value: Some is present, None is blank. So Some(false) and Some(nil) are present, unlike their unwrapped values.

Examples:

Some(1).present? # => true
Some(false).present? # => true
Some("").present? # => true
None().present? # => false

Returns:

  • (Boolean)


333
334
335
# File 'lib/errgonomic/option.rb', line 333

def present?
  some?
end

#present_or(default) ⇒ Object

Returns the inner value of a Some, and the given default on a None. No pedantic type check on the default: this family is deprecated on Options, and unwrap_or, which the nudge points to, has none either.

Examples:

Some("secret").present_or("fallback") # => "secret"
None().present_or("fallback") # => "fallback"

the nudge fires once per process, so a hot path stays quiet

Some(1).present_or(2)
nudges = StringIO.new
original = $stderr
begin
  $stderr = nudges
  Some(1).present_or(2)
ensure
  $stderr = original
end
nudges.string # => ""

Parameters:

  • default (Object)

    The value to return on a None.

Returns:

  • (Object)

    The inner value of a Some, otherwise the default.



397
398
399
400
401
402
# File 'lib/errgonomic/option.rb', line 397

def present_or(default)
  presence_nudge('present_or', 'unwrap_or')
  return default if none?

  value
end

#present_or_else(&block) ⇒ Object

Returns the inner value of a Some, and the result of the block on a None.

Examples:

Some("secret").present_or_else { "fallback" } # => "secret"
None().present_or_else { "fallback" } # => "fallback"

Parameters:

  • block (Proc)

    The block to call on a None.

Returns:

  • (Object)

    The inner value of a Some, otherwise the block's value.



413
414
415
416
417
418
# File 'lib/errgonomic/option.rb', line 413

def present_or_else(&block)
  presence_nudge('present_or_else', 'unwrap_or_else')
  return block.call if none?

  value
end

#present_or_raise!(message = nil, &block) ⇒ Object Also known as: present_or_raise

Returns the inner value of a Some, and raises on a None. Presence follows the discriminant, so Some(nil) yields nil. A block is called only on the None branch, as it is for expect!.

Examples:

Some("secret").present_or_raise!("no secret") # => "secret"
Some(nil).present_or_raise!("no secret") # => nil
None().present_or_raise!("no secret") # => raise Errgonomic::NotPresentError, "no secret"
None().present_or_raise! { "no secret for #{7}" } # => raise Errgonomic::NotPresentError, "no secret for 7"

Parameters:

  • message (String) (defaults to: nil)

    The error message to raise on a None.

Returns:

  • (Object)

    The inner value of a Some.

Raises:



366
367
368
369
370
371
# File 'lib/errgonomic/option.rb', line 366

def present_or_raise!(message = nil, &block)
  presence_nudge('present_or_raise!', 'expect!')
  raise Errgonomic::NotPresentError, block ? block.call : message if none?

  value
end

#pretty_print(pp) ⇒ Object

pp uses its own object dump unless told otherwise; keep it consistent with inspect.



810
811
812
# File 'lib/errgonomic/option.rb', line 810

def pretty_print(pp)
  pp.text(inspect)
end

#respond_to_missing?(name, include_private = false) ⇒ Boolean

Returns:

  • (Boolean)


64
65
66
# File 'lib/errgonomic/option.rb', line 64

def respond_to_missing?(name, include_private = false)
  RUST_SPELLINGS.key?(name) || super
end

#some_and(&block) ⇒ Object Also known as: some_and?

return true if the contained value is Some and the block returns truthy

Examples:

Some(1).some_and { |x| x > 0 } # => true
Some(0).some_and { |x| x > 0 } # => false
None().some_and { |x| x > 0 } # => false


302
303
304
305
306
# File 'lib/errgonomic/option.rb', line 302

def some_and(&block)
  return false if none?

  !!block.call(value)
end

#tap_some(&block) ⇒ Object

Calls a function with the inner value, if Some, but returns the original option. In Rust, this is "inspect" but that clashes with Ruby conventions. We call this "tap_some" to avoid further clashing with "tap."

Examples:

tapped = false
Some(1).tap_some { |x| tapped = x } # => Some(1)
tapped # => 1
tapped = false
None().tap_some { tapped = true } # => None()
tapped # => false


578
579
580
581
# File 'lib/errgonomic/option.rb', line 578

def tap_some(&block)
  block.call(value) if some?
  self
end

#to_aObject

return an Array with the contained value, if any

Examples:

Some(1).to_a # => [1]
None().to_a # => []


487
488
489
490
491
# File 'lib/errgonomic/option.rb', line 487

def to_a
  return [] if none?

  [value]
end

#to_json(*_args) ⇒ Object

Refuse to serialize an unwrapped Option as JSON. Not only should we require that options be correctly handled to access their inner value, but without this we will get undefined structures from default Object#to_json implementations.

Examples:

None().to_json # => raise Errgonomic::SerializeError, 'cannot serialize an unwrapped None'
begin
  Some('a' * 100).to_json
rescue Errgonomic::SerializeError => e
  e.message.end_with?('...')
end # => true

Raises:



795
796
797
# File 'lib/errgonomic/option.rb', line 795

def to_json(*_args)
  raise Errgonomic::SerializeError, serialize_refusal
end

#to_optionObject



459
460
461
# File 'lib/errgonomic/rails/active_record_optional.rb', line 459

def to_option
  self
end

#to_sObject

Refuse to render as a String. Rust gives Option a Debug and no Display: a wrapper that reaches a string went unhandled, and a string is where it turns into data, a hostname, a hash key or a page. The refusal names the value and says how to log it or take it.

Examples:

Some(1).to_s # => raise Errgonomic::SerializeError, "Some(1) refuses to_s; use inspect for a log line, or unwrap_or / expect! for the value"
None().to_s # => raise Errgonomic::SerializeError, "None refuses to_s; use inspect for a log line, or unwrap_or / expect! for the value"
"value: #{Some(1)}" # => raise Errgonomic::SerializeError, "Some(1) refuses to_s; use inspect for a log line, or unwrap_or / expect! for the value"
[Some("org"), Some("metrics")].join("/") # => raise Errgonomic::SerializeError, "Some(\"org\") refuses to_s; use inspect for a log line, or unwrap_or / expect! for the value"
format("%s", None()) # => raise Errgonomic::SerializeError, "None refuses to_s; use inspect for a log line, or unwrap_or / expect! for the value"
String(Some(1)) # => raise Errgonomic::SerializeError, "Some(1) refuses to_s; use inspect for a log line, or unwrap_or / expect! for the value"
Some("a" * 100).to_s # => raise Errgonomic::SerializeError, "Some(\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa... refuses to_s; use inspect for a log line, or unwrap_or / expect! for the value"
Some(1).inspect # => "Some(1)"

Raises:



779
780
781
# File 'lib/errgonomic/option.rb', line 779

def to_s
  raise Errgonomic::SerializeError, to_s_refusal
end

#tryObject

ActiveSupport's Object#try asks respond_to?, which an Option answers false for anything it does not define, so try on a wrapper would be a quiet nil for every method. Send it to the value instead: a Some tries what it holds, a None is absent and answers nil, and a method the value does not have is nil as it is for any other receiver.

Examples:

Some("bob").try(:upcase) # => "BOB"
Some("bob").try(:no_such_method) # => nil
None().try(:upcase) # => nil
Some(2).try { |pages| pages * 3 } # => 6
None().try { |pages| pages * 3 } # => nil


475
476
477
478
479
# File 'lib/errgonomic/rails/active_record_optional.rb', line 475

def try(...)
  return nil if none?

  value.try(...)
end

#try!Object

Rails' strict variant: absence is still nil, a method the value does not have raises.

Examples:

Some("bob").try!(:upcase) # => "BOB"
None().try!(:upcase) # => nil
begin
  Some("bob").try!(:no_such_method)
rescue NoMethodError => e
  e.class
end # => NoMethodError


492
493
494
495
496
# File 'lib/errgonomic/rails/active_record_optional.rb', line 492

def try!(...)
  return nil if none?

  value.try!(...)
end

#unwrap!Object

returns the inner value if present, else raises an error

Examples:

Some(1).unwrap! # => 1
None().unwrap! # => raise Errgonomic::UnwrapError, "cannot unwrap None"

Raises:



521
522
523
524
525
# File 'lib/errgonomic/option.rb', line 521

def unwrap!
  raise Errgonomic::UnwrapError, 'cannot unwrap None' if none?

  value
end

#unwrap_or(default) ⇒ Object

returns the inner value if present, else returns the default value. This is the spelling opt || default cannot give you: an Option is truthy, so || never reaches the fallback.

Examples:

Some(1).unwrap_or(2) # => 1
None().unwrap_or(2) # => 2
None() || 2 # => None()


549
550
551
552
553
# File 'lib/errgonomic/option.rb', line 549

def unwrap_or(default)
  return default if none?

  value
end

#unwrap_or_else(&block) ⇒ Object

returns the inner value if present, else returns the result of the provided block

Examples:

Some(1).unwrap_or_else { 2 } # => 1
None().unwrap_or_else { 2 } # => 2


560
561
562
563
564
# File 'lib/errgonomic/option.rb', line 560

def unwrap_or_else(&block)
  return block.call if none?

  value
end

#xor(other) ⇒ Object

Return Some when either self or other are Some, otherwise return None when both are None or both are Some.

Examples:

Some(:left).xor(Some(:right)) # => None()
Some(:left).xor(None()) #=> Some(:left)
None().xor(Some(:right)) #=> Some(:right)
Some(:left).xor(:right) # => raise Errgonomic::ArgumentError, "other must be an Option, was Symbol"
None().xor(:right) # => raise Errgonomic::ArgumentError, "other must be an Option, was Symbol"


856
857
858
859
860
861
862
# File 'lib/errgonomic/option.rb', line 856

def xor(other)
  option_operand!(other)
  return self if some? && other.none?
  return other if other.some? && none?

  None()
end

#zip(other) ⇒ Object

Zips self with another Option.

If self is Some(s) and other is Some(o), this method returns Some([s, o]). Otherwise, None is returned.

Examples:

None().zip(Some(1)) # => None()
Some(1).zip(None()) # => None()
Some(2).zip(Some(3)) # => Some([2, 3])
Some(1).zip(2) # => raise Errgonomic::ArgumentError, "other must be an Option, was Integer"
None().zip(2) # => raise Errgonomic::ArgumentError, "other must be an Option, was Integer"


740
741
742
743
744
745
# File 'lib/errgonomic/option.rb', line 740

def zip(other)
  option_operand!(other)
  return None() unless some? && other.some?

  Some([value, other.value])
end

#zip_with(other, &block) ⇒ Object

Zip two options using the block passed. If self is Some and Other is some, yield both of their values to the block and return its value as Some. Else return None.

Examples:

None().zip_with(Some(1)) { |a, b| a + b } # => None()
Some(1).zip_with(None()) { |a, b| a + b } # => None()
Some(2).zip_with(Some(3)) { |a, b| a + b } # => Some(5)
Some(1).zip_with(2) { |a, b| a + b } # => raise Errgonomic::ArgumentError, "other must be an Option, was Integer"
None().zip_with(2) { |a, b| a + b } # => raise Errgonomic::ArgumentError, "other must be an Option, was Integer"


757
758
759
760
761
762
763
# File 'lib/errgonomic/option.rb', line 757

def zip_with(other, &block)
  option_operand!(other)
  return None() unless some? && other.some?

  other = block.call(value, other.value)
  Some(other)
end