mona-result 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -0
- data/Gemfile.lock +1 -1
- data/lib/mona/dict_result/err.rb +14 -0
- data/lib/mona/dict_result/ok.rb +22 -0
- data/lib/mona/dict_result/sequence.rb +33 -0
- data/lib/mona/dict_result.rb +33 -0
- data/lib/mona/err.rb +28 -0
- data/lib/mona/no_match_error.rb +14 -0
- data/lib/mona/ok.rb +24 -0
- data/lib/mona/result.rb +27 -23
- data/lib/mona/result_action.rb +88 -0
- data/lib/mona/result_match.rb +53 -0
- data/lib/mona.rb +31 -0
- data/sig/mona.rbs +93 -109
- metadata +12 -10
- data/lib/mona/result/action.rb +0 -88
- data/lib/mona/result/dict.rb +0 -58
- data/lib/mona/result/err.rb +0 -28
- data/lib/mona/result/error.rb +0 -19
- data/lib/mona/result/match.rb +0 -55
- data/lib/mona/result/ok.rb +0 -24
- data/lib/mona/result/sequence.rb +0 -33
- data/lib/mona/resultable.rb +0 -63
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b53dbabf6036e587ba320df3ffde82737a8d692a354d38ea2093d5c1046eedd8
|
4
|
+
data.tar.gz: ffd6a0f736e3407fb4c1cbab320572b399bc4091de6c90644520695162d382d7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cd2b54c044638818a3359239c43adeb631b19254bf94153a99127008c58774d2813ce4aa1363dc92aa107a269585d8fee1739ae98ff53643afdbd446fa5c0549
|
7
|
+
data.tar.gz: d9947684fd66ee1394811c1bf2160215699dd51650ed0b91fd04f22415155fd1af48e85ed35a9a68bfc4283a258172fcd0689929613f574a30afb28e0081703f
|
data/CHANGELOG.md
CHANGED
data/Gemfile.lock
CHANGED
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mona
|
4
|
+
module DictResult
|
5
|
+
# OK DictResult
|
6
|
+
class OK < Mona::OK
|
7
|
+
include DictResult
|
8
|
+
|
9
|
+
def set(key, val)
|
10
|
+
key, failure_key = key if key.is_a?(Array)
|
11
|
+
failure_key ||= key
|
12
|
+
|
13
|
+
# @type var meta: Hash[Symbol, untyped]
|
14
|
+
Result[val].either \
|
15
|
+
->(value) { OK.new to_h.merge(key => value) },
|
16
|
+
->(failure, reason, **meta) { Err.new to_h.merge(failure_key => failure), reason, **meta, key: failure_key }
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_h = @value
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mona
|
4
|
+
module DictResult
|
5
|
+
# Sequence.call { ... } allows monadic 'do' notation for DictResult, where #set-ing the first failure skips the
|
6
|
+
# remainder of the block and returns the DictResult
|
7
|
+
class Sequence
|
8
|
+
def self.call(result = DictResult::EMPTY, &) = new(result).call(&)
|
9
|
+
|
10
|
+
def initialize(result = DictResult::EMPTY)
|
11
|
+
@result = result
|
12
|
+
@throw = Object.new
|
13
|
+
end
|
14
|
+
|
15
|
+
def call
|
16
|
+
catch(@throw) do
|
17
|
+
yield self
|
18
|
+
@result
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def set(key, value)
|
23
|
+
@result = @result.set(key, value)
|
24
|
+
ensure
|
25
|
+
throw @throw, @result if @result.err?
|
26
|
+
end
|
27
|
+
|
28
|
+
def get(key) = @result.get(key)
|
29
|
+
|
30
|
+
def key?(key) = @result.key?(key)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "dict_result/ok"
|
4
|
+
require_relative "dict_result/err"
|
5
|
+
require_relative "dict_result/sequence"
|
6
|
+
|
7
|
+
module Mona
|
8
|
+
# Represents a dictionary of results, it is successful if all results are successful, and a failure if one
|
9
|
+
# is a failure. A DictResult can only contain one failure.
|
10
|
+
module DictResult
|
11
|
+
include Result
|
12
|
+
|
13
|
+
# factory method that returns DictResult::OK or DictResult::Err
|
14
|
+
def self.[](initial = {}, &block)
|
15
|
+
result = EMPTY
|
16
|
+
initial.each { |k, v| result = result.set(k, v) }
|
17
|
+
result = result.sequence(&block) if block
|
18
|
+
result
|
19
|
+
end
|
20
|
+
|
21
|
+
def key?(key) = to_h.key?(key)
|
22
|
+
|
23
|
+
def get(key) = to_h.fetch(key)
|
24
|
+
|
25
|
+
def set(_key, _val) = raise(NotImplementedError, "implement #to_h")
|
26
|
+
|
27
|
+
def to_h = raise(NotImplementedError, "implement #to_h")
|
28
|
+
|
29
|
+
def sequence(&) = Sequence.new(self).call(&)
|
30
|
+
|
31
|
+
EMPTY = DictResult::OK.new({}).freeze
|
32
|
+
end
|
33
|
+
end
|
data/lib/mona/err.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mona
|
4
|
+
# An Err (failure) result, with optional reason, and metadata
|
5
|
+
class Err
|
6
|
+
include Result
|
7
|
+
|
8
|
+
def self.[](failure, reason = nil, **meta) = new(failure, reason, **meta)
|
9
|
+
|
10
|
+
def initialize(failure, reason, **meta)
|
11
|
+
raise ArgumentError, "meta can't contain :reason or err: key" if meta.key?(:reason) || meta.key?(:err)
|
12
|
+
|
13
|
+
@failure = failure
|
14
|
+
@reason = reason
|
15
|
+
@meta = meta
|
16
|
+
end
|
17
|
+
|
18
|
+
# @dynamic failure, reason, meta
|
19
|
+
attr_reader :failure, :reason, :meta
|
20
|
+
|
21
|
+
def either(_ok, err) = err.call(@failure, @reason, **@meta)
|
22
|
+
|
23
|
+
def inspect = "Err(#{[failure, *reason, *meta.map { "#{_1}: #{_2}" }].join(", ")})"
|
24
|
+
|
25
|
+
# @dynamic to_s
|
26
|
+
alias to_s inspect
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mona
|
4
|
+
# raised by ResultMatch.call when no match is found
|
5
|
+
class NoMatchError < Mona::Error
|
6
|
+
# @dynamic result
|
7
|
+
attr_reader :result
|
8
|
+
|
9
|
+
def initialize(result)
|
10
|
+
@result = result
|
11
|
+
super("No match found for #{@result}")
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/lib/mona/ok.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mona
|
4
|
+
# A Successful (OK) result
|
5
|
+
class OK
|
6
|
+
include Result
|
7
|
+
|
8
|
+
def self.[](value) = new(value)
|
9
|
+
|
10
|
+
def initialize(value)
|
11
|
+
@value = value
|
12
|
+
end
|
13
|
+
|
14
|
+
# @dynamic value
|
15
|
+
attr_reader :value
|
16
|
+
|
17
|
+
def either(ok, _err) = ok.call(@value)
|
18
|
+
|
19
|
+
def inspect = "OK(#{@value})"
|
20
|
+
|
21
|
+
# @dynamic to_s
|
22
|
+
alias to_s inspect
|
23
|
+
end
|
24
|
+
end
|
data/lib/mona/result.rb
CHANGED
@@ -1,41 +1,45 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "
|
4
|
-
require_relative "resultable"
|
3
|
+
require_relative "../mona"
|
5
4
|
|
6
5
|
module Mona
|
7
|
-
#
|
8
|
-
#
|
9
|
-
# @author Ian White
|
10
|
-
# @since 0.1.0
|
6
|
+
# Provides the result interface, including class must implement #either(on_ok, on_err)
|
11
7
|
module Result
|
12
|
-
VERSION = "0.
|
8
|
+
VERSION = "0.3.0"
|
13
9
|
|
14
|
-
|
15
|
-
autoload :Err, "mona/result/err.rb"
|
16
|
-
autoload :Match, "mona/result/match.rb"
|
17
|
-
autoload :Dict, "mona/result/dict.rb"
|
18
|
-
autoload :Sequence, "mona/result/sequence.rb"
|
19
|
-
autoload :Action, "mona/result/action.rb"
|
10
|
+
def self.[](obj) = obj.is_a?(Result) ? obj : OK[obj]
|
20
11
|
|
21
|
-
def
|
12
|
+
def either(_ok, _err) = raise(NotImplementedError, "implement #either")
|
22
13
|
|
23
|
-
|
14
|
+
def value_or(&block) = either -> { _1 }, ->(*_args) { block.call }
|
24
15
|
|
25
|
-
|
16
|
+
def ok? = either ->(_) { true }, ->(*_args) { false }
|
26
17
|
|
27
|
-
def
|
18
|
+
def err? = either ->(_) { false }, ->(*_args) { true }
|
28
19
|
|
29
|
-
def ok(
|
20
|
+
def ok(&block) = either block, ->(*_args) {}
|
30
21
|
|
31
|
-
def err(
|
22
|
+
def err(&block) = either ->(_) {}, block
|
32
23
|
|
33
|
-
def
|
24
|
+
def and_tap(&block) = tap { either block, ->(*_args) {} }
|
34
25
|
|
35
|
-
def
|
26
|
+
def and_then(&block) = either -> { Result[block.call _1] }, ->(*_args) { self }
|
36
27
|
|
37
|
-
def
|
28
|
+
def or_else(&block)
|
29
|
+
# @type var meta: Hash[Symbol, untyped]
|
30
|
+
either ->(_) { self }, ->(failure, reason, **meta) { Result[block.call failure, reason, **meta] }
|
31
|
+
end
|
38
32
|
|
39
|
-
def
|
33
|
+
def deconstruct
|
34
|
+
# @type var meta: Hash[Symbol, untyped]
|
35
|
+
either ->(value) { [:ok, value] }, ->(failure, reason, **meta) { [:err, failure, reason, meta] }
|
36
|
+
end
|
37
|
+
|
38
|
+
def deconstruct_keys(_keys = nil)
|
39
|
+
# @type var meta: Hash[Symbol, untyped]
|
40
|
+
either ->(ok) { { ok: } }, ->(err, reason, **meta) { meta.merge(err:, reason:) }
|
41
|
+
end
|
42
|
+
|
43
|
+
def ==(other) = deconstruct == other.deconstruct
|
40
44
|
end
|
41
45
|
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mona
|
4
|
+
# mixin for objects that return Result::Dict results
|
5
|
+
#
|
6
|
+
# define #perform to do the work, use #set to add results to the Result::Dict, the first failure will abort the
|
7
|
+
# rest of the #perform method and the result will be returned. This works like monadic 'do' notation.
|
8
|
+
#
|
9
|
+
# After a result is set at a key, it can be accessed via its key name as a method.
|
10
|
+
#
|
11
|
+
# use the Action with #call
|
12
|
+
#
|
13
|
+
# example:
|
14
|
+
#
|
15
|
+
# class UpdateUser
|
16
|
+
# inlcude Mona::ResultAction
|
17
|
+
# include Auditing # for example, adds #audit(model, input) method
|
18
|
+
#
|
19
|
+
# def perform(user_id, attributes)
|
20
|
+
# set :user, UserRepo.find(user_id) # if find is Err, the block exits with failure of :user
|
21
|
+
# set :input, UserInput.valid(attributes) # likewise if valid is Err, the block exits
|
22
|
+
# set [:user, :input], UserRepo.update(user.id, input) # note that #input and #user are available methods
|
23
|
+
# # if update is Err it is set on the :input key
|
24
|
+
# audit(user, input) # this only runs if all of the above set successful results
|
25
|
+
# end
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# UpdateUser.new.call(user_id, attributes) # => Result
|
29
|
+
module ResultAction
|
30
|
+
# You can create an Ephemeral Action as follows:
|
31
|
+
#
|
32
|
+
# Example:
|
33
|
+
# compute = Mona::ResultAction::Ephemeral.new do |x, y|
|
34
|
+
# set :numerator, x
|
35
|
+
# puts "set numerator: #{numerator}"
|
36
|
+
# set :denominator, y.zero? ? Result.failure(y, :zero) : y
|
37
|
+
# puts "set denominator: #{denominator}"
|
38
|
+
# set :answer, numerator / denominator
|
39
|
+
# puts "answer: #{answer}"
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# > compute.call(10,2)
|
43
|
+
# set numerator: 10
|
44
|
+
# set denominator: 2
|
45
|
+
# answer: 5
|
46
|
+
# => #<Result success: {:numerator=>10, :denominator=>2, :answer=>5}>
|
47
|
+
#
|
48
|
+
# > compute.call(10,0)
|
49
|
+
# set numerator: 10
|
50
|
+
# => #<Result failure: {:numerator=>10, :denominator=>0}, error: {:error=>:zero, :on=>:denominator}>
|
51
|
+
#
|
52
|
+
# Mona::ResultAction() is a shortcut for this
|
53
|
+
class Ephemeral
|
54
|
+
include ResultAction
|
55
|
+
|
56
|
+
def initialize(&perform)
|
57
|
+
@perform = perform
|
58
|
+
end
|
59
|
+
|
60
|
+
def perform(*args, **kwargs) = instance_exec(*args, **kwargs, &@perform)
|
61
|
+
end
|
62
|
+
|
63
|
+
def call(*args, **kwargs)
|
64
|
+
@sequence = DictResult::Sequence.new
|
65
|
+
@sequence.call { |_sequence| perform(*args, **kwargs) }
|
66
|
+
ensure
|
67
|
+
remove_instance_variable :@sequence
|
68
|
+
end
|
69
|
+
|
70
|
+
def perform(*args, **kwargs) = raise(NotImplementedError, "implement `perform'")
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def get(key) = @sequence.get(key)
|
75
|
+
|
76
|
+
def set(key, value) = @sequence.set(key, value)
|
77
|
+
|
78
|
+
def key?(key) = @sequence.key?(key)
|
79
|
+
|
80
|
+
def respond_to_missing?(key, _include_private = false) = key?(key)
|
81
|
+
|
82
|
+
def method_missing(key, *args)
|
83
|
+
return get(key) if args.empty? && key?(key)
|
84
|
+
|
85
|
+
raise NoMethodError, "no method `#{key}' for #{self}"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mona
|
4
|
+
# Use ResultMatch.call to respond to result success or failure
|
5
|
+
#
|
6
|
+
# ResultMatch.call(result) do |r|
|
7
|
+
# r.ok { |value| ... }
|
8
|
+
# r.err(reason, **meta) { |failure, reason, **meta| ... }
|
9
|
+
# r.err(**meta) { |failure, reason, **meta| ... }
|
10
|
+
# r.err(reason) { |failure, reason, **meta| ... }
|
11
|
+
# r.err { |failure, reason, **meta| ... }
|
12
|
+
# end
|
13
|
+
class ResultMatch
|
14
|
+
def self.call(result, &) = new(result).call(&)
|
15
|
+
|
16
|
+
def initialize(result)
|
17
|
+
@throw = Object.new
|
18
|
+
@result = result
|
19
|
+
end
|
20
|
+
|
21
|
+
def call
|
22
|
+
catch @throw do
|
23
|
+
yield self
|
24
|
+
raise NoMatchError, @result
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def ok
|
29
|
+
@result.and_then do |value|
|
30
|
+
throw @throw, yield(value)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def err(match_reason = nil, **match_meta)
|
35
|
+
@result.or_else do |failure, reason, **meta|
|
36
|
+
# @type var meta: Hash[Symbol, untyped]
|
37
|
+
if match_reason?(match_reason, reason) && match_meta?(match_meta, meta)
|
38
|
+
throw @throw, yield(failure, reason, **meta)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def match_reason?(match_reason, reason)
|
46
|
+
match_reason.nil? || match_reason === reason # rubocop:disable Style/CaseEquality
|
47
|
+
end
|
48
|
+
|
49
|
+
def match_meta?(match_meta, meta)
|
50
|
+
match_meta.all? { |key, val| val.nil? || val === meta[key] } # rubocop:disable Style/CaseEquality
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
data/lib/mona.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Mona top level module contains utility methods
|
4
|
+
module Mona
|
5
|
+
autoload :OK, "mona/ok.rb"
|
6
|
+
autoload :Err, "mona/err.rb"
|
7
|
+
autoload :DictResult, "mona/dict_result.rb"
|
8
|
+
autoload :ResultMatch, "mona/result_match.rb"
|
9
|
+
autoload :ResultAction, "mona/result_action.rb"
|
10
|
+
autoload :NoMatchError, "mona/no_match_error.rb"
|
11
|
+
|
12
|
+
class Error < StandardError; end
|
13
|
+
|
14
|
+
module_function
|
15
|
+
|
16
|
+
# @dynamic self.result, self.ok, self.err, self.dict_result, self.on_result, self.on_ok, self.result_action
|
17
|
+
|
18
|
+
def result(obj) = obj.is_a?(Result) ? obj : OK[obj]
|
19
|
+
|
20
|
+
def ok(value) = OK[value]
|
21
|
+
|
22
|
+
def err(failure, reason = nil, **meta) = Err[failure, reason, **meta]
|
23
|
+
|
24
|
+
def dict_result(initial = {}, &) = DictResult[initial, &]
|
25
|
+
|
26
|
+
def on_result(result, &) = ResultMatch.call(result, &)
|
27
|
+
|
28
|
+
def on_ok(result, &) = ResultMatch.call(result) { _1.ok(&) }
|
29
|
+
|
30
|
+
def result_action(&) = ResultAction::Ephemeral.new(&)
|
31
|
+
end
|
data/sig/mona.rbs
CHANGED
@@ -1,126 +1,84 @@
|
|
1
1
|
module Mona
|
2
|
+
Result::VERSION: String
|
3
|
+
|
2
4
|
type dict = Hash[Symbol, untyped]
|
3
|
-
type result
|
4
|
-
type dict_result =
|
5
|
+
type result = Result[untyped, untyped]
|
6
|
+
type dict_result = DictResult[untyped]
|
5
7
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
8
|
+
def self?.result: (untyped) -> result
|
9
|
+
def self?.ok: [T] (T) -> OK[T]
|
10
|
+
def self?.err: [T, ReasonT] (T, ?ReasonT?, **untyped) -> Err[T, ReasonT?]
|
11
|
+
def self?.dict_result: (?dict) ?{ (DictResult::Sequence) -> void } -> dict_result
|
12
|
+
def self?.on_result: (result) { (ResultMatch) -> void } -> untyped
|
13
|
+
def self?.on_ok: (result) { (untyped) -> void } -> untyped
|
14
|
+
def self?.result_action: { (*untyped) -> void } -> ResultAction::Ephemeral
|
15
|
+
|
16
|
+
module Result[T, ReasonT]
|
17
|
+
def self.[]: (untyped) -> result
|
10
18
|
|
19
|
+
def either: [OKR, ErrR] (^(T) -> OKR, ^(T, ?ReasonT?, **untyped) -> ErrR) -> (OKR | ErrR)
|
11
20
|
def ok?: -> bool
|
12
21
|
def err?: -> bool
|
13
22
|
def ok: [R] () { (T) -> R } -> (R | nil)
|
14
|
-
def err: [R] () { (T, ?
|
23
|
+
def err: [R] () { (T, ?ReasonT?, **untyped) -> R } -> (R | nil)
|
15
24
|
def value_or: [R] { () -> R } -> (R | T)
|
16
|
-
def and_then: [R] () { (T) -> R } -> result
|
25
|
+
def and_then: [R] () { (T) -> R } -> result
|
17
26
|
def and_tap: () { (T) -> void } -> self
|
18
|
-
def or_else: [R] () { (T, ?
|
27
|
+
def or_else: [R] () { (T, ?ReasonT?, **untyped) -> R } -> result
|
19
28
|
def deconstruct: -> Array[untyped]
|
20
29
|
def deconstruct_keys: (?Array[Symbol]?) -> dict
|
21
30
|
end
|
22
31
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
def self.[]: (untyped) -> result[untyped]
|
27
|
-
def self?.to_result: (untyped) -> result[untyped]
|
28
|
-
def self?.ok: [ValueT] (ValueT) -> OK[ValueT]
|
29
|
-
def self?.err: [FailureT] (FailureT, ?untyped, **untyped) -> Err[FailureT]
|
30
|
-
def self?.dict: (?dict) ?{ (Sequence) -> void } -> dict_result
|
31
|
-
def self?.on_result: (result[untyped]) { (Match) -> void } -> untyped
|
32
|
-
def self?.on_ok: (result[untyped]) { (untyped) -> void } -> untyped
|
33
|
-
def self?.action: () { (*untyped) -> void } -> Action::Ephemeral
|
34
|
-
|
35
|
-
class Error < StandardError
|
36
|
-
end
|
37
|
-
|
38
|
-
class NoMatchError < Error
|
39
|
-
attr_reader result: result[untyped]
|
40
|
-
def initialize: (result[untyped]) -> void
|
41
|
-
end
|
32
|
+
class OK[T]
|
33
|
+
include Result[T, nil]
|
42
34
|
|
43
|
-
|
44
|
-
include Resultable[ValueT]
|
35
|
+
attr_reader value: T
|
45
36
|
|
46
|
-
|
47
|
-
|
48
|
-
def initialize: (ValueT) -> void
|
49
|
-
def either: [OKR, ErrR] (^(ValueT) -> OKR, ^(untyped, ?untyped, **untyped) -> ErrR) -> OKR
|
50
|
-
def inspect: -> String
|
51
|
-
alias to_s inspect
|
52
|
-
end
|
37
|
+
def self.[]: [T] (T) -> OK[T]
|
53
38
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
attr_reader meta: dict
|
60
|
-
|
61
|
-
def initialize: (FailureT, ?untyped, **untyped) -> void
|
62
|
-
def either: [OKR, ErrR] (^(untyped) -> OKR, ^(FailureT, ?untyped, **untyped) -> ErrR) -> ErrR
|
63
|
-
def inspect: -> String
|
64
|
-
alias to_s inspect
|
65
|
-
end
|
66
|
-
|
67
|
-
class Dict
|
68
|
-
EMPTY: dict_result
|
69
|
-
|
70
|
-
module Read : _ToH
|
71
|
-
interface _ToH
|
72
|
-
def to_h: -> dict
|
73
|
-
end
|
39
|
+
def initialize: (T) -> void
|
40
|
+
def either: [OKR] (^(T) -> OKR, ^(T, ?untyped?, **untyped) -> void) -> OKR
|
41
|
+
def inspect: -> String
|
42
|
+
alias to_s inspect
|
43
|
+
end
|
74
44
|
|
75
|
-
|
76
|
-
|
77
|
-
def [] : (Symbol) -> untyped
|
78
|
-
end
|
45
|
+
class Err[T, ReasonT]
|
46
|
+
include Result[T, ReasonT]
|
79
47
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
def to_h: -> dict
|
84
|
-
end
|
48
|
+
attr_reader failure: T
|
49
|
+
attr_reader reason: ReasonT
|
50
|
+
attr_reader meta: dict
|
85
51
|
|
86
|
-
|
87
|
-
include Read
|
52
|
+
def self.[]: [T, ReasonT] (T, ?ReasonT?, **untyped) -> Err[T, ReasonT?]
|
88
53
|
|
89
|
-
|
54
|
+
def initialize: (T, ReasonT, **untyped) -> void
|
55
|
+
def either: [ErrR] (^(T) -> void, ^(T, ?ReasonT?, **untyped) -> ErrR) -> ErrR
|
56
|
+
def inspect: -> String
|
57
|
+
alias to_s inspect
|
58
|
+
end
|
90
59
|
|
91
|
-
|
92
|
-
|
60
|
+
module DictResult[ReasonT]
|
61
|
+
include Result[dict, ReasonT]
|
93
62
|
|
94
|
-
|
95
|
-
include Read
|
63
|
+
EMPTY: dict_result
|
96
64
|
|
97
|
-
|
65
|
+
def self.[]: (?dict) ?{ (Sequence) -> void } -> dict_result
|
98
66
|
|
99
|
-
|
100
|
-
|
67
|
+
def key? : (Symbol) -> bool
|
68
|
+
def get : (Symbol) -> untyped
|
69
|
+
def set: (Symbol | [Symbol, Symbol], untyped) -> dict_result
|
70
|
+
def sequence: () { (Sequence) -> void } -> dict_result
|
71
|
+
def to_h: -> dict
|
101
72
|
|
102
|
-
|
73
|
+
class OK < Mona::OK[dict]
|
74
|
+
include DictResult[nil]
|
103
75
|
end
|
104
76
|
|
105
|
-
class
|
106
|
-
|
107
|
-
@result: result[untyped]
|
108
|
-
|
109
|
-
def self.call: (result[untyped]) { (Match) -> void } -> untyped
|
110
|
-
def initialize: (result[untyped]) -> void
|
111
|
-
def call: () { (Match) -> void } -> untyped
|
112
|
-
def ok: () { (untyped) -> void } -> void
|
113
|
-
def err: (?untyped, **untyped) { (untyped, ?untyped, **untyped) -> void } -> void
|
114
|
-
|
115
|
-
private
|
116
|
-
|
117
|
-
def match_reason?: (untyped, untyped) -> bool
|
118
|
-
def match_meta?: (untyped, untyped) -> bool
|
77
|
+
class Err[ReasonT] < Mona::Err[dict, ReasonT]
|
78
|
+
include DictResult[ReasonT]
|
119
79
|
end
|
120
80
|
|
121
81
|
class Sequence
|
122
|
-
include Dict::Read
|
123
|
-
|
124
82
|
@throw: Object
|
125
83
|
@result: dict_result
|
126
84
|
|
@@ -128,30 +86,56 @@ module Mona
|
|
128
86
|
def initialize: (?dict_result) -> void
|
129
87
|
def call: () { (Sequence) -> void } -> dict_result
|
130
88
|
def set: (Symbol | [Symbol,Symbol], untyped) -> void
|
131
|
-
def
|
89
|
+
def get: (Symbol) -> untyped
|
90
|
+
def key?: (Symbol) -> bool
|
132
91
|
end
|
92
|
+
end
|
133
93
|
|
134
|
-
|
135
|
-
|
136
|
-
|
94
|
+
class Error < StandardError
|
95
|
+
end
|
96
|
+
|
97
|
+
class NoMatchError < Error
|
98
|
+
attr_reader result: result
|
99
|
+
def initialize: (result) -> void
|
100
|
+
end
|
137
101
|
|
138
|
-
|
102
|
+
class ResultMatch
|
103
|
+
@throw: Object
|
104
|
+
@result: result
|
139
105
|
|
140
|
-
|
141
|
-
|
142
|
-
|
106
|
+
def self.call: (result) { (ResultMatch) -> void } -> untyped
|
107
|
+
def initialize: (result) -> void
|
108
|
+
def call: () { (ResultMatch) -> void } -> untyped
|
109
|
+
def ok: () { (untyped) -> void } -> void
|
110
|
+
def err: (?untyped, **untyped) { (untyped, ?untyped, **untyped) -> void } -> void
|
143
111
|
|
144
|
-
|
112
|
+
private
|
145
113
|
|
146
|
-
|
147
|
-
|
114
|
+
def match_reason?: (untyped, untyped) -> bool
|
115
|
+
def match_meta?: (untyped, untyped) -> bool
|
116
|
+
end
|
148
117
|
|
149
|
-
|
118
|
+
module ResultAction
|
119
|
+
class Ephemeral
|
120
|
+
include ResultAction
|
150
121
|
|
151
|
-
|
152
|
-
|
153
|
-
def
|
154
|
-
def
|
122
|
+
@perform: ^(*untyped, **untyped) -> void
|
123
|
+
|
124
|
+
def initialize: () { (*untyped, **untyped) -> void } -> void
|
125
|
+
def perform: (*untyped, **untyped) -> void
|
155
126
|
end
|
127
|
+
|
128
|
+
@sequence: DictResult::Sequence
|
129
|
+
|
130
|
+
def call: (*untyped, **untyped) -> dict_result
|
131
|
+
def perform: (*untyped, **untyped) -> void
|
132
|
+
|
133
|
+
private
|
134
|
+
|
135
|
+
def get: (Symbol) -> untyped
|
136
|
+
def set: (Symbol | [Symbol,Symbol], untyped) -> void
|
137
|
+
def key?: (Symbol) -> bool
|
138
|
+
def respond_to_missing?: (Symbol, ?bool) -> bool
|
139
|
+
def method_missing: (Symbol, *untyped) -> untyped
|
156
140
|
end
|
157
141
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mona-result
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ian White
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-08-
|
11
|
+
date: 2022-08-25 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Mona::Result provides a result monad, and dict result monad with do-notation
|
14
14
|
email:
|
@@ -25,15 +25,17 @@ files:
|
|
25
25
|
- README.md
|
26
26
|
- Rakefile
|
27
27
|
- Steepfile
|
28
|
+
- lib/mona.rb
|
29
|
+
- lib/mona/dict_result.rb
|
30
|
+
- lib/mona/dict_result/err.rb
|
31
|
+
- lib/mona/dict_result/ok.rb
|
32
|
+
- lib/mona/dict_result/sequence.rb
|
33
|
+
- lib/mona/err.rb
|
34
|
+
- lib/mona/no_match_error.rb
|
35
|
+
- lib/mona/ok.rb
|
28
36
|
- lib/mona/result.rb
|
29
|
-
- lib/mona/
|
30
|
-
- lib/mona/
|
31
|
-
- lib/mona/result/err.rb
|
32
|
-
- lib/mona/result/error.rb
|
33
|
-
- lib/mona/result/match.rb
|
34
|
-
- lib/mona/result/ok.rb
|
35
|
-
- lib/mona/result/sequence.rb
|
36
|
-
- lib/mona/resultable.rb
|
37
|
+
- lib/mona/result_action.rb
|
38
|
+
- lib/mona/result_match.rb
|
37
39
|
- sig/mona.rbs
|
38
40
|
homepage: https://github.com/mona-rb/mona-result
|
39
41
|
licenses:
|
data/lib/mona/result/action.rb
DELETED
@@ -1,88 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Mona
|
4
|
-
module Result
|
5
|
-
# mixin for objects that return Result::Dict results
|
6
|
-
#
|
7
|
-
# define #perform to do the work, use #set to add results to the Result::Dict, the first failure will abort the
|
8
|
-
# rest of the #perform method and the result will be returned. This works like monadic 'do' notation.
|
9
|
-
#
|
10
|
-
# After a result is set at a key, it can be accessed via its key name as a method.
|
11
|
-
#
|
12
|
-
# use the Action with #call
|
13
|
-
#
|
14
|
-
# example:
|
15
|
-
#
|
16
|
-
# class UpdateUser
|
17
|
-
# inlcude Mona::Result::Action
|
18
|
-
# include Auditing # for example, adds #audit(model, input) method
|
19
|
-
#
|
20
|
-
# def perform(user_id, attributes)
|
21
|
-
# set :user, UserRepo.find(user_id) # if find is Err, the block exits with failure of :user
|
22
|
-
# set :input, UserInput.valid(attributes) # likewise if valid is Err, the block exits
|
23
|
-
# set [:user, :input], UserRepo.update(user.id, input) # note that #input and #user are available methods
|
24
|
-
# # if update is Err it is set on the :input key
|
25
|
-
# audit(user, input) # this only runs if all of the above set successful results
|
26
|
-
# end
|
27
|
-
# end
|
28
|
-
#
|
29
|
-
# UpdateUser.new.call(user_id, attributes) # => Result
|
30
|
-
module Action
|
31
|
-
# You can create an Ephemeral Action as follows:
|
32
|
-
#
|
33
|
-
# Example:
|
34
|
-
# compute = Mona::Result::Action::Ephemeral.new do |x, y|
|
35
|
-
# set :numerator, x
|
36
|
-
# puts "set numerator: #{numerator}"
|
37
|
-
# set :denominator, y.zero? ? Result.failure(y, :zero) : y
|
38
|
-
# puts "set denominator: #{denominator}"
|
39
|
-
# set :answer, numerator / denominator
|
40
|
-
# puts "answer: #{answer}"
|
41
|
-
# end
|
42
|
-
#
|
43
|
-
# > compute.call(10,2)
|
44
|
-
# set numerator: 10
|
45
|
-
# set denominator: 2
|
46
|
-
# answer: 5
|
47
|
-
# => #<Result success: {:numerator=>10, :denominator=>2, :answer=>5}>
|
48
|
-
#
|
49
|
-
# > compute.call(10,0)
|
50
|
-
# set numerator: 10
|
51
|
-
# => #<Result failure: {:numerator=>10, :denominator=>0}, error: {:error=>:zero, :on=>:denominator}>
|
52
|
-
#
|
53
|
-
# Mona::Result.action is a shortcut for this
|
54
|
-
class Ephemeral
|
55
|
-
include Action
|
56
|
-
|
57
|
-
def initialize(&perform)
|
58
|
-
@perform = perform
|
59
|
-
end
|
60
|
-
|
61
|
-
def perform(*args, **kwargs) = instance_exec(*args, **kwargs, &@perform)
|
62
|
-
end
|
63
|
-
|
64
|
-
def call(*args, **kwargs)
|
65
|
-
@sequence = Sequence.new
|
66
|
-
@sequence.call { |_sequence| perform(*args, **kwargs) }
|
67
|
-
ensure
|
68
|
-
remove_instance_variable :@sequence
|
69
|
-
end
|
70
|
-
|
71
|
-
def perform(*args, **kwargs) = raise(NotImplementedError, "implement `perform'")
|
72
|
-
|
73
|
-
private
|
74
|
-
|
75
|
-
def get(key) = @sequence.fetch(key)
|
76
|
-
|
77
|
-
def set(key, value) = @sequence.set(key, value)
|
78
|
-
|
79
|
-
def respond_to_missing?(key, _include_private = false) = @sequence.key?(key)
|
80
|
-
|
81
|
-
def method_missing(key, *args)
|
82
|
-
return get(key) if args.empty? && @sequence.key?(key)
|
83
|
-
|
84
|
-
raise NoMethodError, "no method `#{key}' for #{self}"
|
85
|
-
end
|
86
|
-
end
|
87
|
-
end
|
88
|
-
end
|
data/lib/mona/result/dict.rb
DELETED
@@ -1,58 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Mona
|
4
|
-
module Result
|
5
|
-
# Represents a dictionary of results, it is successful if all results are successful, and a failure if one
|
6
|
-
# is a failure. A Result::Dict can only contain one failure.
|
7
|
-
class Dict
|
8
|
-
# factory method that returns {Dict::OK} or {Dict::Err}
|
9
|
-
def self.new(initial = {}, &block)
|
10
|
-
result = Dict::EMPTY
|
11
|
-
initial.each { |k, v| result = result.set(k, v) }
|
12
|
-
result = result.sequence(&block) if block
|
13
|
-
result
|
14
|
-
end
|
15
|
-
|
16
|
-
# Dict read interface
|
17
|
-
module Read
|
18
|
-
def [](key) = to_h[key]
|
19
|
-
|
20
|
-
def key?(key) = to_h.key?(key)
|
21
|
-
|
22
|
-
def fetch(key) = to_h.fetch(key)
|
23
|
-
end
|
24
|
-
|
25
|
-
# OK dict result
|
26
|
-
class OK < Result::OK
|
27
|
-
include Read
|
28
|
-
|
29
|
-
def set(key, to_result)
|
30
|
-
key, failure_key = key if key.is_a?(Array)
|
31
|
-
failure_key ||= key
|
32
|
-
|
33
|
-
Result[to_result].either \
|
34
|
-
->(value) { OK.new to_h.merge(key => value) },
|
35
|
-
# @type var meta: Hash[Symbol, untyped]
|
36
|
-
->(failure, reason, **meta) { Err.new to_h.merge(failure_key => failure), reason, **meta, key: failure_key }
|
37
|
-
end
|
38
|
-
|
39
|
-
def sequence(&) = Sequence.new(self).call(&)
|
40
|
-
|
41
|
-
def to_h = @value
|
42
|
-
end
|
43
|
-
|
44
|
-
# Err dict result
|
45
|
-
class Err < Result::Err
|
46
|
-
include Read
|
47
|
-
|
48
|
-
def set(_key, _val) = raise(Error, "cannot #set on #{self}")
|
49
|
-
|
50
|
-
def sequence(&) = self
|
51
|
-
|
52
|
-
def to_h = @failure
|
53
|
-
end
|
54
|
-
|
55
|
-
EMPTY = OK.new({}).freeze
|
56
|
-
end
|
57
|
-
end
|
58
|
-
end
|
data/lib/mona/result/err.rb
DELETED
@@ -1,28 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Mona
|
4
|
-
module Result
|
5
|
-
# A Error or failure result, with optional reason, and metadata
|
6
|
-
class Err
|
7
|
-
include Resultable
|
8
|
-
|
9
|
-
def initialize(failure, reason = nil, **meta)
|
10
|
-
raise ArgumentError, "meta can't contain :reason or err: key" if meta.key?(:reason) || meta.key?(:err)
|
11
|
-
|
12
|
-
@failure = failure
|
13
|
-
@reason = reason
|
14
|
-
@meta = meta
|
15
|
-
end
|
16
|
-
|
17
|
-
# @dynamic failure, reason, meta
|
18
|
-
attr_reader :failure, :reason, :meta
|
19
|
-
|
20
|
-
def either(_ok, err) = err.call(@failure, @reason, **@meta)
|
21
|
-
|
22
|
-
def inspect = "#<Err #{@failure} #{{ reason: @reason, **@meta }.map { "#{_1}: #{_2}" }.join(", ")}>"
|
23
|
-
|
24
|
-
# @dynamic to_s
|
25
|
-
alias to_s inspect
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
data/lib/mona/result/error.rb
DELETED
@@ -1,19 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Mona
|
4
|
-
module Result
|
5
|
-
# Base error for Result
|
6
|
-
class Error < StandardError; end
|
7
|
-
|
8
|
-
# raised when Result::Match does not match the result
|
9
|
-
class NoMatchError < Error
|
10
|
-
# @dynamic result
|
11
|
-
attr_reader :result
|
12
|
-
|
13
|
-
def initialize(result)
|
14
|
-
@result = result
|
15
|
-
super("No match found for #{result}")
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
data/lib/mona/result/match.rb
DELETED
@@ -1,55 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Mona
|
4
|
-
module Result
|
5
|
-
# Use Match.call to respond to result success or failure
|
6
|
-
#
|
7
|
-
# Result::Match.call(result) do |r|
|
8
|
-
# r.ok { |value| ... }
|
9
|
-
# r.err(reason, **meta) { |failure, reason, **meta| ... }
|
10
|
-
# r.err(**meta) { |failure, reason, **meta| ... }
|
11
|
-
# r.err(reason) { |failure, reason, **meta| ... }
|
12
|
-
# r.err { |failure, reason, **meta| ... }
|
13
|
-
# end
|
14
|
-
class Match
|
15
|
-
def self.call(result, &) = new(result).call(&)
|
16
|
-
|
17
|
-
def initialize(result)
|
18
|
-
@throw = Object.new
|
19
|
-
@result = result
|
20
|
-
end
|
21
|
-
|
22
|
-
def call
|
23
|
-
catch @throw do
|
24
|
-
yield self
|
25
|
-
raise NoMatchError, @result
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
def ok
|
30
|
-
@result.and_then do |value|
|
31
|
-
throw @throw, yield(value)
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
def err(match_reason = nil, **match_meta)
|
36
|
-
@result.or_else do |failure, reason, **meta|
|
37
|
-
# @type var meta: Hash[Symbol, untyped]
|
38
|
-
if match_reason?(match_reason, reason) && match_meta?(match_meta, meta)
|
39
|
-
throw @throw, yield(failure, reason, **meta)
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
private
|
45
|
-
|
46
|
-
def match_reason?(match_reason, reason)
|
47
|
-
match_reason.nil? || match_reason === reason # rubocop:disable Style/CaseEquality
|
48
|
-
end
|
49
|
-
|
50
|
-
def match_meta?(match_meta, meta)
|
51
|
-
match_meta.all? { |key, val| val.nil? || val === meta[key] } # rubocop:disable Style/CaseEquality
|
52
|
-
end
|
53
|
-
end
|
54
|
-
end
|
55
|
-
end
|
data/lib/mona/result/ok.rb
DELETED
@@ -1,24 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Mona
|
4
|
-
module Result
|
5
|
-
# A Successful (OK) result
|
6
|
-
class OK
|
7
|
-
include Resultable
|
8
|
-
|
9
|
-
def initialize(value)
|
10
|
-
@value = value
|
11
|
-
end
|
12
|
-
|
13
|
-
# @dynamic value
|
14
|
-
attr_reader :value
|
15
|
-
|
16
|
-
def either(ok, _err) = ok.call(@value)
|
17
|
-
|
18
|
-
def inspect = "#<OK #{@value}>"
|
19
|
-
|
20
|
-
# @dynamic to_s
|
21
|
-
alias to_s inspect
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
data/lib/mona/result/sequence.rb
DELETED
@@ -1,33 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Mona
|
4
|
-
module Result
|
5
|
-
# Sequence.call { ... } allows monadic 'do' notation for Result::Dict, where #set-ing the first failure skips the
|
6
|
-
# remainder of the block and returns the Result::Dict
|
7
|
-
class Sequence
|
8
|
-
include Dict::Read
|
9
|
-
|
10
|
-
def self.call(result = Dict::EMPTY, &) = new(result).call(&)
|
11
|
-
|
12
|
-
def initialize(result = Dict::EMPTY)
|
13
|
-
@result = result
|
14
|
-
@throw = Object.new
|
15
|
-
end
|
16
|
-
|
17
|
-
def call
|
18
|
-
catch(@throw) do
|
19
|
-
yield self
|
20
|
-
@result
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
def set(key, value)
|
25
|
-
@result = @result.set(key, value)
|
26
|
-
ensure
|
27
|
-
throw @throw, @result if @result.err?
|
28
|
-
end
|
29
|
-
|
30
|
-
def to_h = @result.to_h
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
data/lib/mona/resultable.rb
DELETED
@@ -1,63 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Mona
|
4
|
-
# Provides the result interface
|
5
|
-
# The including class must implement #either(ok, err)
|
6
|
-
module Resultable
|
7
|
-
def value_or(&block)
|
8
|
-
either ->(value) { value },
|
9
|
-
->(_failure, _reason = nil, **_meta) { block.call }
|
10
|
-
end
|
11
|
-
|
12
|
-
def ok?
|
13
|
-
either ->(_value) { true },
|
14
|
-
->(_failure, _reason = nil, **_meta) { false }
|
15
|
-
end
|
16
|
-
|
17
|
-
def err?
|
18
|
-
either ->(_value) { false },
|
19
|
-
->(_failure, _reason = nil, **_meta) { true }
|
20
|
-
end
|
21
|
-
|
22
|
-
def ok(&block)
|
23
|
-
either ->(value) { block.call(value) },
|
24
|
-
->(_failure, _reason = nil, **_meta) {}
|
25
|
-
end
|
26
|
-
|
27
|
-
def err(&block)
|
28
|
-
either ->(_value) {},
|
29
|
-
# @type var meta: Hash[Symbol, untyped]
|
30
|
-
->(failure, reason = nil, **meta) { block.call(failure, reason, **meta) }
|
31
|
-
end
|
32
|
-
|
33
|
-
def and_tap(&block)
|
34
|
-
tap do
|
35
|
-
either ->(value) { block.call(value) },
|
36
|
-
->(_failure, _reason = nil, **_meta) {}
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
def and_then(&block)
|
41
|
-
either ->(value) { Result[block.call(value)] },
|
42
|
-
->(_failure, _reason, **_meta) { self }
|
43
|
-
end
|
44
|
-
|
45
|
-
def or_else(&block)
|
46
|
-
either ->(_value) { self },
|
47
|
-
# @type var meta: Hash[Symbol, untyped]
|
48
|
-
->(failure, reason, **meta) { Result[block.call(failure, reason, **meta)] }
|
49
|
-
end
|
50
|
-
|
51
|
-
def deconstruct
|
52
|
-
either ->(value) { [:ok, value] },
|
53
|
-
# @type var meta: Hash[Symbol, untyped]
|
54
|
-
->(failure, reason, **meta) { [:err, failure, reason, meta] }
|
55
|
-
end
|
56
|
-
|
57
|
-
def deconstruct_keys(_keys = nil)
|
58
|
-
either ->(value) { { ok: value } },
|
59
|
-
# @type var meta: Hash[Symbol, untyped]
|
60
|
-
->(failure, reason, **meta) { { err: failure, reason:, **meta } }
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|