Gem Version

Lab42::Result

A result encapsulation class, like the Either type in Haskell.

Context Quick Starting Guide

Given


    require "lab42/result/autoimport" # equivalent to: the following lines:
    # require "lab42/result"
    # Result = Lab42::Result

    let(:ok) { Result.ok(42) }
    let(:error) { Result.error("oh no!") }

Accessing a result

If OK

Example: It's ok

  expect( ok ).to be_ok

Example: Its value might be of interest

  expect( ok.value ).to eq(42)

And it will execute the block passed to the if_ok method

    x = nil
    expect(ok.if_ok {x = 42}).to eq(42)
    expect(x).to eq(42)

And the value is passed in

    expect(ok.if_ok{ _1/2}).to eq(21)

But not the one passed to the if_error method

    x = nil
    expect(ok.if_error {x = 42}).to be_nil
    expect(x).to be_nil

Example: And will not raise any error

  expect( ok.raise! ).to eq(42)

And the same holds for raise! with a replacement Exception

  expect( ok.raise!(ArgumentError) ).to eq(42)

If Error

Example: It's h(error)

  expect( error ).not_to be_ok

Example: Its value might (still) be of interest


  expect( error.value ).to eq("oh no!")

Example: And will certainly raise this time

  expect{ error.raise! }.to raise_error(RuntimeError, "oh no!") 

And as often times you will match on an error case only and raise a custom exception the following shortcut comes in handy

  expect{ error.raise!(KeyError) }.to raise_error(KeyError, "oh no!") 

And also you might like to have access to the original message

    expect{ error.raise!(KeyError) { "key not found #{_1}"} }
      .to raise_error(KeyError, "key not found oh no!") 

And it will execute the block passed to the if_error method

    x = nil
    expect(error.if_error {x = 42}).to eq(42)
    expect(x).to eq(42)

And the error and message are passed in

    expect(error.if_error {[_1, _2]}).to eq([RuntimeError, "oh no!"])    

But not the one passed to the if_ok method

    x = nil
    expect(error.if_ok {x = 42}).to be_nil
    expect(x).to be_nil

Capturing Exceptions

If you have a piece of code like this:

    Result.ok(some_computation)
  rescue MyError
    Result.error("Oh my", error: MyError)

you can replace it with the convenient

    Result.from("Oh my") {some_computation}

Given

  def divide by
    Result.from("Zero Division") { 100 / by }
  end

Example: Zero Division, not a problem anymore

  error = divide(0)
  expect( error ).not_to be_ok
  expect( error.status ).to eq(ZeroDivisionError)

Example: Correct Division still works

    divide(4) in [_, value]
    expect( value ).to eq(25)

Context A More Detailed View

Ok without a value

Sometimes all we want is an :ok to get back or :error with a message, than we can use the default value of nil

Example: Nil is default for ok

   expect(Result.ok.value).to be_nil
   expect( Result.ok.ok? ).to be_truthy

Error with a different exception

On the other hand you might not want to raise a RuntimeError all the time, that is when the optional keyword parameter error: comes in handy

Example: Error with an Argument

    expect{ Result.error("ooops", error: ArgumentError).raise!}
      .to raise_error(ArgumentError, "ooops")

Context Pattern Matching

Given

    let(:my_error) {Class.new(RuntimeError)}
    let(:message) {"That was bad"}
    let(:surprise) {"Not 42"}
    let(:ok) {Result.ok(surprise)}
    let(:error) {Result.error(message, error: my_error)}

    def match result
      case result
      in [:ok, value]
        value
      in [my_error, message]
        message
      end
    end

Example: Matching The Good

    expect( match(ok) ).to eq(surprise)

Example: Matching The Bad (no, there will be no Ugly)

    expect( match(error) ).to eq(message)

Example: If the Exception I do not want

    error in [_, error_message]
    expect( error_message ).to eq(message)

Context Saveguards

And last but not least, to assure that all instances of Result are frozen we have removed the default constructor (we have not - yet - shadowed Object#allocate though)

Example: Look Mam, no default constructor!

    expect{ Result.new }.to raise_error(NoMethodError)

LICENSE

Copyright 2020 Robert Dober [email protected]

Apache-2.0 c.f LICENSE