Last active
April 6, 2025 19:33
-
-
Save havenwood/015713e2e00c54280af16e11c2d09091 to your computer and use it in GitHub Desktop.
An example of a Rust-like `Option` enum in Ruby, see https://doc.rust-lang.org/rust-by-example/std/option.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require_relative 'option' | |
include Option | |
# An integer division that doesn't `raise ZeroDivisionError` | |
def checked_division(dividend, divisor) | |
if divisor.zero? | |
# Failure is represented as the `None` variant | |
None[] | |
else | |
# Result is wrapped in a `Some` variant | |
Some[dividend / divisor] | |
end | |
end | |
# This function handles a division that may not succeed | |
def try_division(dividend, divisor) | |
# `Option` values can be pattern matched, just like other enums | |
case checked_division(dividend, divisor) | |
in None | |
puts "#{dividend} / #{divisor} failed!" | |
in Some(quotient) | |
puts "#{dividend} / #{divisor} = #{quotient}" | |
end | |
end | |
try_division(4, 2) | |
#>> 4 / 2 = 2 | |
try_division(1, 0) | |
#>> 1 / 0 failed! |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'singleton' | |
module Option | |
def self.from(value) | |
return None[] if value.nil? | |
Some[value] | |
end | |
Some = Data.define(:value) do | |
include Comparable | |
def <=>(other) | |
return 1 if other.is_a?(None) | |
value <=> other.value | |
end | |
alias unwrap value | |
alias unwrap_or_else value | |
def unwrap_or(_default) = value | |
def expect(_message) = value | |
def and(other) = other.is_a?(Some) ? other : None[] | |
def and_then = yield value | |
def or(_other) = self | |
def or_else = self | |
def xor(other) = other.is_a?(Some) ? None[] : self | |
def each | |
yield value | |
self | |
end | |
def map = Some[yield value] | |
def filter = yield(value) ? self : None[] | |
def flat_map(&) = map(&).flatten | |
def flatten | |
case value | |
in None | Some | |
value | |
else | |
self | |
end | |
end | |
def map_or(_default) = yield value | |
alias map_or_else map_or | |
def some? = true | |
def none? = false | |
def some_and? = yield value | |
alias none_or? some_and? | |
end | |
None = Data.define | |
class None | |
class UnwrapError < StandardError | |
def initialize(message = 'cannot unwrap a `None` value') = super | |
end | |
include Comparable | |
include Singleton | |
def <=>(other) = other.is_a?(Some) ? -1 : 0 | |
class << self | |
alias [] instance | |
end | |
def unwrap = raise UnwrapError | |
def unwrap_or_else = yield | |
def unwrap_or(default) = default | |
def expect(message) = raise UnwrapError, message | |
def and(_other) = self | |
def and_then = self | |
def or(other) = other.is_a?(Some) ? other : self | |
alias or_else unwrap_or_else | |
alias xor or | |
def each = self | |
alias map each | |
alias filter each | |
alias flat_map each | |
alias flatten each | |
alias map_or unwrap_or | |
def map_or_else(default) = default.call | |
def some? = false | |
def none? = true | |
alias some_and? some? | |
alias none_or? none? | |
end | |
module Refinement | |
refine Object do | |
def to_option = Option::Some[self] | |
end | |
refine NilClass do | |
def to_option = Option::None[] | |
end | |
end | |
end | |
module Kernel | |
private | |
def Option(value) = Option.from(value) | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment