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

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:



34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/errgonomic/option.rb', line 34

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(<<~MSG, name)
    undefined method `#{name}' for #{inspect}, an Option, which does not forward methods to its inner value.
    Reach for a combinator instead:
      map, and_then, filter: transform the value if present
      unwrap_or, unwrap_or_else: supply a fallback
      ok_or, ok_or_else: convert to a Result
      some_and?, none_or?: test a predicate against the inner value
    unwrap! and expect! also exist, but are intended for tests rather than application code.
  MSG
end

Instance Method Details

#<=>(other) ⇒ Object

Options order like Rust's: None sorts before any Some, and Somes order by their inner values. Follows Ruby's <=> convention of returning nil for incomparable operands, whether the other object is not an Option or the inner values do not themselves compare.

Examples:

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


140
141
142
143
144
145
146
# File 'lib/errgonomic/option.rb', line 140

def <=>(other)
  return nil unless other.is_a?(Errgonomic::Option::Any)
  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. Anything else, including nil and the raw inner value, is not equal: quietly false, never an error. Rust rejects Some(5) == 5 at compile time; Ruby cannot, and raising here would break the many places Ruby compares heterogeneous operands (Array#include?, assertion diffs, dirty tracking). Compare Options (opt == Some(5)) or test the inner value (opt.some_and? { |v| v == 5 }) instead.

None() == nil is likewise false: None is a value that represents absence, not an absence Ruby can see. (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
Some(1) == 1 # => false
None() == nil # => false


75
76
77
78
79
80
# File 'lib/errgonomic/option.rb', line 75

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

  value == other.value
end

#and(other) ⇒ Object

If self is Some, return the provided other Option.

Examples:

None().and(Some(1)) # => None()
Some(2).and(Some(3)) # => Some(3)


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

def and(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)


498
499
500
501
502
503
504
505
506
507
# File 'lib/errgonomic/option.rb', line 498

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.



564
565
566
# File 'lib/errgonomic/option.rb', line 564

def as_json(*_args)
  raise Errgonomic::SerializeError, 'cannot serialize an unwrapped Option'
end

#blank?Boolean

Examples:

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

Returns:

  • (Boolean)


193
194
195
# File 'lib/errgonomic/option.rb', line 193

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


279
280
281
# File 'lib/errgonomic/option.rb', line 279

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


289
290
291
# File 'lib/errgonomic/option.rb', line 289

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


299
300
301
# File 'lib/errgonomic/option.rb', line 299

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

#deconstructObject

Examples:

measurement = Errgonomic::Option::Some.new(1)
case measurement
in Errgonomic::Option::Some, value
  "Measurement is #{measurement.value}"
in Errgonomic::Option::None
  "Measurement is not available"
else
  "not matched"
end # => "Measurement is 1"


120
121
122
123
124
# File 'lib/errgonomic/option.rb', line 120

def deconstruct
  return [self, value] if some?

  [Errgonomic::Option::None]
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()]

Returns:

  • (Boolean)


93
94
95
96
97
98
# File 'lib/errgonomic/option.rb', line 93

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

  value.eql?(other.value)
end

#expect!(msg) ⇒ Object

returns the inner value if pressent, else raises an error with the given message

Examples:

Some(1).expect!("msg") # => 1
None().expect!("here's why this failed") # => raise Errgonomic::ExpectError, "here's why this failed"

Raises:



330
331
332
333
334
# File 'lib/errgonomic/option.rb', line 330

def expect!(msg)
  raise Errgonomic::ExpectError, 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()


581
582
583
584
585
# File 'lib/errgonomic/option.rb', line 581

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"


596
597
598
599
600
601
602
603
604
605
# File 'lib/errgonomic/option.rb', line 596

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


104
105
106
107
108
# File 'lib/errgonomic/option.rb', line 104

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. Raises a pedantic exception if the return value of the block is not an Option.

Examples:

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


381
382
383
384
385
# File 'lib/errgonomic/option.rb', line 381

def map(&block)
  return self if none?

  Some(block.call(value))
end

#map_or(default, &block) ⇒ Object

Returns the provided default (if none), or applies a function to the contained value (if some). If you want lazy evaluation for the provided value, use map_or_else.

Examples:

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


395
396
397
398
399
# File 'lib/errgonomic/option.rb', line 395

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

  Some(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).

Examples:

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


408
409
410
411
412
413
414
415
# File 'lib/errgonomic/option.rb', line 408

def map_or_else(proc, &block)
  if none?
    val = proc.call
    return val ? Some(val) : None()
  end

  Some(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


168
169
170
171
172
# File 'lib/errgonomic/option.rb', line 168

def none_or(&block)
  return true if none?

  !!block.call(value)
end

#okObject

convert the option into a result where Some is Ok and None is Err

Examples:

None().ok # => Err()
Some(1).ok # => Ok(1)


421
422
423
424
425
# File 'lib/errgonomic/option.rb', line 421

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

  Errgonomic::Result::Err.new
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)


432
433
434
435
436
# File 'lib/errgonomic/option.rb', line 432

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


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

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.new, "other must be an Option, was Integer"

Raises:



456
457
458
459
460
461
462
# File 'lib/errgonomic/option.rb', line 456

def or(other)
  raise ArgumentError, "other must be an Option, was #{other.class.name}" unless other.is_a?(Any)

  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"


470
471
472
473
474
475
476
477
478
479
# File 'lib/errgonomic/option.rb', line 470

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.

Examples:

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

Returns:

  • (Object, nil)

    The inner value of a Some, otherwise nil.



266
267
268
269
270
271
# File 'lib/errgonomic/option.rb', line 266

def presence
  presence_nudge('presence', 'unwrap_or(nil)')
  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)


185
186
187
# File 'lib/errgonomic/option.rb', line 185

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"

Parameters:

  • default (Object)

    The value to return on a None.

Returns:

  • (Object)

    The inner value of a Some, otherwise the default.



234
235
236
237
238
239
# File 'lib/errgonomic/option.rb', line 234

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.



250
251
252
253
254
255
# File 'lib/errgonomic/option.rb', line 250

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

  value
end

#present_or_raise!(message) ⇒ 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.

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"

Parameters:

  • message (String)

    The error message to raise on a None.

Returns:

  • (Object)

    The inner value of a Some.

Raises:



215
216
217
218
219
220
# File 'lib/errgonomic/option.rb', line 215

def present_or_raise!(message)
  presence_nudge('present_or_raise', 'expect!')
  raise Errgonomic::NotPresentError, message if none?

  value
end

#pretty_print(pp) ⇒ Object

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



570
571
572
# File 'lib/errgonomic/option.rb', line 570

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

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

Returns:

  • (Boolean)


51
52
53
# File 'lib/errgonomic/option.rb', line 51

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


154
155
156
157
158
# File 'lib/errgonomic/option.rb', line 154

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


369
370
371
372
# File 'lib/errgonomic/option.rb', line 369

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 # => []


309
310
311
312
313
# File 'lib/errgonomic/option.rb', line 309

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 Option"

Raises:



555
556
557
# File 'lib/errgonomic/option.rb', line 555

def to_json(*_args)
  raise Errgonomic::SerializeError, 'cannot serialize an unwrapped Option'
end

#to_optionObject



265
266
267
# File 'lib/errgonomic/rails/active_record_optional.rb', line 265

def to_option
  self
end

#to_sObject

Refuse to serialize an unwrapped Option as a String. Options must be correctly handled to access their inner value.

Examples:

None().to_s # => raise Errgonomic::SerializeError, "cannot serialize an unwrapped Option"

Raises:



544
545
546
# File 'lib/errgonomic/option.rb', line 544

def to_s
  raise Errgonomic::SerializeError, 'cannot serialize an unwrapped Option'
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:



319
320
321
322
323
# File 'lib/errgonomic/option.rb', line 319

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

Examples:

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


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

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


351
352
353
354
355
# File 'lib/errgonomic/option.rb', line 351

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)


615
616
617
618
619
620
# File 'lib/errgonomic/option.rb', line 615

def xor(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])


518
519
520
521
522
# File 'lib/errgonomic/option.rb', line 518

def zip(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)


532
533
534
535
536
537
# File 'lib/errgonomic/option.rb', line 532

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

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