Skip to content

Instantly share code, notes, and snippets.

@justgage
Created January 16, 2017 20:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save justgage/0e1bcc90eff10a2b7ee49d442d4d7a7c to your computer and use it in GitHub Desktop.
Save justgage/0e1bcc90eff10a2b7ee49d442d4d7a7c to your computer and use it in GitHub Desktop.
A simple result monad in ruby
# This class helps to captue possible errors as a value. While you could capture these conditions as
# `nil` it's easy to forget to check for it everywhere.
#
# ResultValue helps with this by making the only way to get at the value is with #case_of.
class ResultValue
# This will rescue any exceptions and return it as an "errored" ResultValue
# if there was none it will return it as an "ok" ResultValue with the value
# wrapped inside
def self.capture_result
begin
self.new(yield, true)
rescue StandardError => e
self.new(e, false)
end
end
def initialize(value, ok)
@value = value
@ok = ok
end
# This is how you use the value wrapped inside of the Result. You must pass a lambda
# for both the good case and the error case.
#
# Examples
#
# val = 10
#
# result = ResultValue.capture_result do
# raise "Must be bigger than 0" if val <= 0
# val
# end
#
# result.case_of(
# ok: lambda {|v| puts "here's the valid value: #{v}"},
# error: lambda {|_e| puts "We encountered an error along the way"}
# )
def case_of(ok:, error:)
if ok?
ok.call(@value)
else
error.call(@value)
end
self
end
# This method will call fn passing value to it if it's not an error, otherwise it will noop. This can be nice when you want to
# only handle the happy path initally. Note you only need to use multiple with_values when you
# suspect that it will error out at some point.
def with_value(fn)
if ok?
self.class.capture_result do
fn.call(@value)
end
else
self
end
end
def ok?() @ok end
def error?() !@ok end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment