Class: Errgonomic::Option::Any
- 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.
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
-
#!=(other) ⇒ Object
Ruby derives != from ==, so a strict-equality message would name the operator the caller did not write.
-
#<=>(other) ⇒ Object
Options order like Rust's: None sorts before any Some, and Somes order by their inner values.
-
#==(other) ⇒ Object
An Option equals another Option of the same class with an equal inner value.
-
#===(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. -
#and(other) ⇒ Object
If self is Some, return the provided other Option.
-
#and_then(&block) ⇒ Object
If self is Some, call the given block with the inner value and return its result.
-
#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.
- #blank? ⇒ Boolean
- #blank_or(_default) ⇒ Object
- #blank_or_else(&_block) ⇒ Object
- #blank_or_raise!(_message) ⇒ Object (also: #blank_or_raise)
-
#deconstruct ⇒ Object
The Rust shape: a Some deconstructs to its one payload and a None to nothing, so
in Some(v)binds the value andin Nonematches. -
#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.
-
#eql?(other) ⇒ Boolean
Hash-based collections (Hash keys, Set, uniq, group_by) use eql? and hash, not ==.
-
#expect!(msg = nil, &block) ⇒ Object
Returns the inner value of a Some, else raises with the given message.
-
#filter(&block) ⇒ Object
Return self if the predicate is truthy for the inner value, else None.
-
#flatten ⇒ Object
Remove one level of Option nesting.
- #hash ⇒ Object
-
#map(&block) ⇒ Object
Maps the Option to another Option by applying a function to the contained value (if Some) or returns None.
-
#map_or(default, &block) ⇒ Object
Returns the provided default (if none), or the block applied to the contained value (if some).
-
#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).
-
#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.
-
#none_or(&block) ⇒ Object
(also: #none_or?)
return true if the contained value is None or the block returns truthy.
-
#ok_or(err) ⇒ Object
Transforms the option into a result, mapping Some(v) to Ok(v) and None to Err(err).
-
#ok_or_else(&block) ⇒ Object
Transforms the option into a result, mapping Some(v) to Ok(v) and None to Err(err).
-
#or(other) ⇒ Object
Returns the option if it contains a value, otherwise returns the provided Option.
-
#or_else(&block) ⇒ Object
Returns the option if it contains a value, otherwise calls the block and returns the result.
-
#presence ⇒ Object?
Returns the inner value of a Some, and nil on a None, so the Rails
presence || defaultidiom reaches the value rather than the wrapper. -
#present? ⇒ Boolean
Presence follows the discriminant, not the inner value: Some is present, None is blank.
-
#present_or(default) ⇒ Object
Returns the inner value of a Some, and the given default on a None.
-
#present_or_else(&block) ⇒ Object
Returns the inner value of a Some, and the result of the block on a None.
-
#present_or_raise!(message = nil, &block) ⇒ Object
(also: #present_or_raise)
Returns the inner value of a Some, and raises on a None.
-
#pretty_print(pp) ⇒ Object
pp uses its own object dump unless told otherwise; keep it consistent with inspect.
- #respond_to_missing?(name, include_private = false) ⇒ Boolean
-
#some_and(&block) ⇒ Object
(also: #some_and?)
return true if the contained value is Some and the block returns truthy.
-
#tap_some(&block) ⇒ Object
Calls a function with the inner value, if Some, but returns the original option.
-
#to_a ⇒ Object
return an Array with the contained value, if any.
-
#to_json(*_args) ⇒ Object
Refuse to serialize an unwrapped Option as JSON.
- #to_option ⇒ Object
-
#to_s ⇒ Object
Refuse to render as a String.
-
#try ⇒ Object
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.
-
#try! ⇒ Object
Rails' strict variant: absence is still nil, a method the value does not have raises.
-
#unwrap! ⇒ Object
returns the inner value if present, else raises an error.
-
#unwrap_or(default) ⇒ Object
returns the inner value if present, else returns the default value.
-
#unwrap_or_else(&block) ⇒ Object
returns the inner value if present, else returns the result of the provided block.
-
#xor(other) ⇒ Object
Return Some when either self or other are Some, otherwise return None when both are None or both are Some.
-
#zip(other) ⇒ Object
Zips self with another Option.
-
#zip_with(other, &block) ⇒ Object
Zip two options using the block passed.
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.
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.
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.)
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.
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.
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
341 342 343 |
# File 'lib/errgonomic/option.rb', line 341 def blank? none? end |
#blank_or(_default) ⇒ Object
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
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
477 478 479 |
# File 'lib/errgonomic/option.rb', line 477 def blank_or_raise!() raise_blank_side_teaching(:blank_or_raise!) end |
#deconstruct ⇒ Object
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.
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.
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.
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.
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.
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 |
#flatten ⇒ Object
Remove one level of Option nesting. Pedantically raises when the inner value is not itself an Option, which in Rust would not have compiled.
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 |
#hash ⇒ Object
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.
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.
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.
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
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)
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?
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.
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.
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 |
#presence ⇒ Object?
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.
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.
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.
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.
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!.
366 367 368 369 370 371 |
# File 'lib/errgonomic/option.rb', line 366 def present_or_raise!( = nil, &block) presence_nudge('present_or_raise!', 'expect!') raise Errgonomic::NotPresentError, block ? block.call : 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
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
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."
578 579 580 581 |
# File 'lib/errgonomic/option.rb', line 578 def tap_some(&block) block.call(value) if some? self end |
#to_a ⇒ Object
return an Array with the contained value, if any
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.
795 796 797 |
# File 'lib/errgonomic/option.rb', line 795 def to_json(*_args) raise Errgonomic::SerializeError, serialize_refusal end |
#to_option ⇒ Object
459 460 461 |
# File 'lib/errgonomic/rails/active_record_optional.rb', line 459 def to_option self end |
#to_s ⇒ Object
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.
779 780 781 |
# File 'lib/errgonomic/option.rb', line 779 def to_s raise Errgonomic::SerializeError, to_s_refusal end |
#try ⇒ Object
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.
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.
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
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.
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
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.
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.
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.
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 |