djk_monads 0.1.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 +7 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +10 -0
- data/LICENSE.txt +21 -0
- data/README.md +157 -0
- data/lib/djk/monads/err.rb +149 -0
- data/lib/djk/monads/ok.rb +143 -0
- data/lib/djk/monads/option.rb +160 -0
- data/lib/djk/monads/result.rb +237 -0
- data/lib/djk/monads/return_error.rb +9 -0
- data/lib/djk/monads/version.rb +7 -0
- data/lib/djk/monads.rb +43 -0
- data/lib/djk.rb +6 -0
- metadata +57 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 351088f4c8de33dd072afb7fa93ef5cdbeb4bed569dde6bc1c8e4ef147f3d1d6
|
|
4
|
+
data.tar.gz: ba3766f358d52203f3de0fca4571804b5bfa0923572a896c0259147f7f2929c7
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 100910e5eeb79c53b5c21fac1eb9d645848b49524c80447ed7530b437b7a1b6f80c4d7c4e83dbeb2751414217ece7718304002a2583bd5fc440b88c412be1ac5
|
|
7
|
+
data.tar.gz: 0c7eef839bba5d57f6d4cb233de7a8503de1fb3faadebda36646ce1294be648cca1f77b8f658c4735c16773e5267502b7a3071d27d5aca2b791554460421b458
|
data/CHANGELOG.md
ADDED
data/CODE_OF_CONDUCT.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Code of Conduct
|
|
2
|
+
|
|
3
|
+
DJK Monads follows [The Ruby Community Conduct Guideline](https://www.ruby-lang.org/en/conduct) in all "collaborative space", which is defined as community communications channels (such as mailing lists, submitted patches, commit comments, etc.):
|
|
4
|
+
|
|
5
|
+
* Participants will be tolerant of opposing views.
|
|
6
|
+
* Participants must ensure that their language and actions are free of personal attacks and disparaging personal remarks.
|
|
7
|
+
* When interpreting the words and actions of others, participants should always assume good intentions.
|
|
8
|
+
* Behaviour which can be reasonably considered harassment will not be tolerated.
|
|
9
|
+
|
|
10
|
+
If you have any concerns about behavior within this project, please contact me at ["dan@dankotowski.dev"](mailto:"dan@dankotowski.dev").
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Dan Kotowski
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# DJK Monads [](https://github.com/djkotowski/djk_monads/actions/workflows/ci.yml)
|
|
2
|
+
|
|
3
|
+
Rust-style `Result` and `Option` monads for Ruby.
|
|
4
|
+
|
|
5
|
+
- **`Result`**: the outcome of an operation that either succeeds with a value (`Ok`) or fails with an error (`Err`).
|
|
6
|
+
- **`Option`**: a value that is either present (Some) or absent (None).
|
|
7
|
+
|
|
8
|
+
Both support chaining (`map`, `flat_map`), unwrapping with fallbacks, and pattern matching.
|
|
9
|
+
|
|
10
|
+
## Requirements
|
|
11
|
+
|
|
12
|
+
Ruby 4.0 or newer.
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
The gem hasn't been published to RubyGems yet. Install it from GitHub by adding it to your Gemfile:
|
|
17
|
+
|
|
18
|
+
```ruby
|
|
19
|
+
gem "djk_monads", github: "djkotowski/djk_monads"
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Then require it:
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
require "djk"
|
|
26
|
+
|
|
27
|
+
include DJK::Monads # optional, lets you write Result and Option unqualified
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
To use the types without the namespace everywhere, define them as top-level constants instead:
|
|
31
|
+
|
|
32
|
+
```ruby
|
|
33
|
+
DJK::Monads.apply_aliases!
|
|
34
|
+
# => [:ReturnError, :Err, :Ok, :Option, :Result]
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
This defines `ReturnError`, `Err`, `Ok`, `Option` and `Result` at the top level. Any of those names that's already defined is left alone and left out of the returned list, and a warning naming it is printed to stderr.
|
|
38
|
+
|
|
39
|
+
## Usage
|
|
40
|
+
|
|
41
|
+
### Result
|
|
42
|
+
|
|
43
|
+
Build results with `Result.ok`, `Result.err` or `Result.from`. Don't instantiate `Ok` or `Err` directly.
|
|
44
|
+
|
|
45
|
+
```ruby
|
|
46
|
+
Result.ok(1).map { it + 1 }
|
|
47
|
+
# => Ok<2>
|
|
48
|
+
|
|
49
|
+
Result.err("boom").unwrap_or(0)
|
|
50
|
+
# => 0
|
|
51
|
+
|
|
52
|
+
# Result.from runs the block and wraps any StandardError it raises in an Err
|
|
53
|
+
Result.from { Integer("oops") }.map_err(&:message)
|
|
54
|
+
# => Err<"invalid value for Integer(): \"oops\"">
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Chain operations that can fail with `flat_map`. The block must return a `Result`, or a `DJK::Monads::ReturnError` is raised:
|
|
58
|
+
|
|
59
|
+
```ruby
|
|
60
|
+
Result.ok(2).flat_map { |n| n.even? ? Result.ok(n / 2) : Result.err("odd") }
|
|
61
|
+
# => Ok<1>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Pattern match on the variant:
|
|
65
|
+
|
|
66
|
+
```ruby
|
|
67
|
+
case Result.from { Integer("42") }
|
|
68
|
+
in Ok[value] then value
|
|
69
|
+
in Err[error] then error.message
|
|
70
|
+
end
|
|
71
|
+
# => 42
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Hash patterns work too, with `in Ok[value:]` and `in Err[error:]`.
|
|
75
|
+
|
|
76
|
+
#### Instance methods
|
|
77
|
+
|
|
78
|
+
| Method | `Ok` | `Err` |
|
|
79
|
+
| --- | --- | --- |
|
|
80
|
+
| `ok?` / `err?` | `true` / `false` | `false` / `true` |
|
|
81
|
+
| `map { }` | wraps the block's return value in `Ok` | returns `self` |
|
|
82
|
+
| `map_err { }` | returns `self` | wraps the block's return value in `Err` |
|
|
83
|
+
| `flat_map { }` | returns the block's `Result` | returns `self` |
|
|
84
|
+
| `flat_map_err { }` | returns `self` | returns the block's `Result` |
|
|
85
|
+
| `on_ok { }` / `on_err { }` | runs the block for side effects, returns `self` | same |
|
|
86
|
+
| `flatten` | returns the innermost nested `Result` | same |
|
|
87
|
+
| `unwrap!` | returns the value | raises the wrapped error |
|
|
88
|
+
| `unwrap_err!` | raises `ReturnError` | returns the error |
|
|
89
|
+
| `unwrap_or(default)` | returns the value | returns `default` |
|
|
90
|
+
| `unwrap_or_else { \|error\| }` | returns the value | returns the block's return value |
|
|
91
|
+
| `to_h` | `{ variant: :ok, value: }` | `{ variant: :err, error: }` |
|
|
92
|
+
|
|
93
|
+
`map` does not flatten: if its block returns a `Result`, you get a nested result. Use `flat_map`, or call `flatten`.
|
|
94
|
+
|
|
95
|
+
`Err#unwrap!` raises the wrapped error as is, so it works best when the error is an exception or a message string.
|
|
96
|
+
|
|
97
|
+
#### Working with collections
|
|
98
|
+
|
|
99
|
+
```ruby
|
|
100
|
+
# Split results into their values and their errors
|
|
101
|
+
Result.partition([Result.ok(1), Result.err("boom"), Result.ok(2)])
|
|
102
|
+
# => [[1, 2], ["boom"]]
|
|
103
|
+
|
|
104
|
+
# Map each element to a Result and collect the values, stopping at the first Err
|
|
105
|
+
Result.traverse(%w[1 2 3]) { |s| Result.from { Integer(s) } }
|
|
106
|
+
# => Ok<[1, 2, 3]>
|
|
107
|
+
|
|
108
|
+
Result.traverse(%w[1 x 3]) { |s| Result.from { Integer(s) } }.map_err(&:message)
|
|
109
|
+
# => Err<"invalid value for Integer(): \"x\"">
|
|
110
|
+
|
|
111
|
+
# Remove every Ok wrapping nil
|
|
112
|
+
Result.compact([Result.ok(nil), Result.ok(1), Result.err(nil)])
|
|
113
|
+
# => [Ok<1>, Err<nil>]
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Option
|
|
117
|
+
|
|
118
|
+
Build options with `Option.some`, `Option.none` or `Option.from`. None is a single shared instance, and a Some can never wrap `nil`. `Option.some(nil)` raises `ArgumentError`.
|
|
119
|
+
|
|
120
|
+
```ruby
|
|
121
|
+
Option.from(nil).map { it + 1 }.unwrap_or(0)
|
|
122
|
+
# => 0
|
|
123
|
+
|
|
124
|
+
Option.from({ a: 1 }[:a]).map { it * 10 }.unwrap!
|
|
125
|
+
# => 10
|
|
126
|
+
|
|
127
|
+
Option.none.or_else { Option.some(5) }.unwrap!
|
|
128
|
+
# => 5
|
|
129
|
+
|
|
130
|
+
# Convert to a Result
|
|
131
|
+
Option.from(nil).ok_or("missing")
|
|
132
|
+
# => Err<"missing">
|
|
133
|
+
|
|
134
|
+
# Raise your own error instead of Option::NoneError
|
|
135
|
+
Option.none.expect(KeyError.new("no key"))
|
|
136
|
+
# raises KeyError: no key
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
`Option#map` returns None when the block returns `nil`. `flat_map` and `or_else` blocks must return an `Option`, or a `DJK::Monads::ReturnError` is raised.
|
|
140
|
+
|
|
141
|
+
Other methods: `some?`, `none?`, `unwrap_or_else { }` and `to_a` (`[value]` for a Some, `[]` for None).
|
|
142
|
+
|
|
143
|
+
## Development
|
|
144
|
+
|
|
145
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then run `bundle exec rake` to run the specs and RuboCop. CI also checks formatting with `bin/stree check`. `bin/console` opens an interactive prompt with the gem loaded.
|
|
146
|
+
|
|
147
|
+
Formatting uses [Syntax Tree](https://github.com/ruby-syntax-tree/syntax_tree) (`bin/stree write`) and [RuboCop](https://rubocop.org) (`bin/rubocop -a`). A [lefthook](https://github.com/evilmartians/lefthook) pre-commit hook runs both on staged files. Run `bundle exec lefthook install` to enable it.
|
|
148
|
+
|
|
149
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `lib/djk/monads/version.rb`, then run `bundle exec rake release`. That creates a git tag for the version, pushes the commits and the tag, and pushes the `.gem` file to [rubygems.org](https://rubygems.org).
|
|
150
|
+
|
|
151
|
+
## Contributing
|
|
152
|
+
|
|
153
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/djkotowski/djk_monads. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/djkotowski/djk_monads/blob/main/CODE_OF_CONDUCT.md).
|
|
154
|
+
|
|
155
|
+
## License
|
|
156
|
+
|
|
157
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "result"
|
|
4
|
+
|
|
5
|
+
module DJK
|
|
6
|
+
module Monads
|
|
7
|
+
# The failed variant of a Result, wrapping an error.
|
|
8
|
+
#
|
|
9
|
+
# The error can be any object. Calling +unwrap!+ raises it, so an exception or a message string works best.
|
|
10
|
+
#
|
|
11
|
+
# @example
|
|
12
|
+
# Result.err("boom").unwrap_or(0)
|
|
13
|
+
# # => 0
|
|
14
|
+
class Err < Result
|
|
15
|
+
# @param error [Object] the error to wrap
|
|
16
|
+
def initialize(error)
|
|
17
|
+
super()
|
|
18
|
+
@error = error
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# @param other [Object] the object to compare against
|
|
22
|
+
# @return [Boolean] true if +other+ is an Err wrapping an error equal to this one
|
|
23
|
+
def ==(other)
|
|
24
|
+
return false unless other.is_a?(Result) && other.err?
|
|
25
|
+
|
|
26
|
+
error == other.unwrap_err!
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# @return [Array(Object)] the wrapped error in a single element array
|
|
30
|
+
def deconstruct
|
|
31
|
+
[error]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# @param _keys [Array<Symbol>, nil] ignored, every key is always returned
|
|
35
|
+
# @return [Hash{Symbol => Object}] the wrapped error under +:error+
|
|
36
|
+
def deconstruct_keys(_keys)
|
|
37
|
+
{ error: }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# @return [Boolean] always true
|
|
41
|
+
def err?
|
|
42
|
+
true
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Collapses nested results, returning the innermost one.
|
|
46
|
+
#
|
|
47
|
+
# @return [Result] the innermost result when the error is a Result, otherwise +self+
|
|
48
|
+
def flatten
|
|
49
|
+
return self unless error.is_a?(Result)
|
|
50
|
+
|
|
51
|
+
error.flatten
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Does nothing, since there is no value to chain from.
|
|
55
|
+
#
|
|
56
|
+
# @return [Err] +self+, without calling the block
|
|
57
|
+
def flat_map(&)
|
|
58
|
+
self
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Chains another result producing operation onto the wrapped error.
|
|
62
|
+
#
|
|
63
|
+
# @yieldparam error [Object] the wrapped error
|
|
64
|
+
# @yieldreturn [Result] must return a Result
|
|
65
|
+
# @raise [ReturnError] when the block does not return a Result
|
|
66
|
+
# @return [Result] the result returned by the block
|
|
67
|
+
def flat_map_err
|
|
68
|
+
yield(error).tap { raise ReturnError, "block must return a Result" unless it.is_a?(Result) }
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# @return [String] the inspected error wrapped in <tt>Err<></tt>, such as <tt>Err<"boom"></tt>
|
|
72
|
+
def inspect
|
|
73
|
+
"Err<#{error.inspect}>"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Does nothing, since there is no value to transform.
|
|
77
|
+
#
|
|
78
|
+
# @return [Err] +self+, without calling the block
|
|
79
|
+
def map
|
|
80
|
+
self
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Transforms the wrapped error.
|
|
84
|
+
#
|
|
85
|
+
# @yieldparam error [Object] the wrapped error
|
|
86
|
+
# @return [Err] an Err wrapping the block's return value, which is not flattened when it is itself a Result
|
|
87
|
+
def map_err
|
|
88
|
+
Result.err(yield(error))
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# @return [Boolean] always false
|
|
92
|
+
def ok?
|
|
93
|
+
false
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Runs the block with the wrapped error for its side effects.
|
|
97
|
+
#
|
|
98
|
+
# @yieldparam error [Object] the wrapped error
|
|
99
|
+
# @return [Err] +self+, ignoring the block's return value
|
|
100
|
+
def on_err
|
|
101
|
+
yield error
|
|
102
|
+
self
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Does nothing, since there is no value.
|
|
106
|
+
#
|
|
107
|
+
# @return [Err] +self+, without calling the block
|
|
108
|
+
def on_ok
|
|
109
|
+
self
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# @return [Hash{Symbol => Object}] the +:err+ variant along with the wrapped error
|
|
113
|
+
def to_h
|
|
114
|
+
{ variant: :err, error: }
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Always raises the wrapped error, since there is no value to unwrap.
|
|
118
|
+
#
|
|
119
|
+
# An exception is raised as is, and a string is raised as a RuntimeError with that message. Any other object
|
|
120
|
+
# makes +raise+ fail with a TypeError.
|
|
121
|
+
#
|
|
122
|
+
# @raise [Exception] the wrapped error
|
|
123
|
+
def unwrap!
|
|
124
|
+
raise error
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# @return [Object] the wrapped error
|
|
128
|
+
def unwrap_err!
|
|
129
|
+
error
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# @param default [Object] the value to return
|
|
133
|
+
# @return [Object] +default+
|
|
134
|
+
def unwrap_or(default)
|
|
135
|
+
default
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# @yieldparam error [Object] the wrapped error
|
|
139
|
+
# @return [Object] the block's return value
|
|
140
|
+
def unwrap_or_else
|
|
141
|
+
yield error
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
private
|
|
145
|
+
|
|
146
|
+
attr_reader :error
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "result"
|
|
4
|
+
|
|
5
|
+
module DJK
|
|
6
|
+
module Monads
|
|
7
|
+
# The successful variant of a Result, wrapping a value.
|
|
8
|
+
#
|
|
9
|
+
# @example
|
|
10
|
+
# Result.ok(1).unwrap!
|
|
11
|
+
# # => 1
|
|
12
|
+
class Ok < Result
|
|
13
|
+
# @param value [Object] the value to wrap
|
|
14
|
+
def initialize(value)
|
|
15
|
+
super()
|
|
16
|
+
@value = value
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# @param other [Object] the object to compare against
|
|
20
|
+
# @return [Boolean] true if +other+ is an Ok wrapping a value equal to this one
|
|
21
|
+
def ==(other)
|
|
22
|
+
return false unless other.is_a?(Result) && other.ok?
|
|
23
|
+
|
|
24
|
+
value == other.unwrap!
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# @return [Array(Object)] the wrapped value in a single element array
|
|
28
|
+
def deconstruct
|
|
29
|
+
[value]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# @param _keys [Array<Symbol>, nil] ignored, every key is always returned
|
|
33
|
+
# @return [Hash{Symbol => Object}] the wrapped value under +:value+
|
|
34
|
+
def deconstruct_keys(_keys)
|
|
35
|
+
{ value: }
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# @return [Boolean] always false
|
|
39
|
+
def err?
|
|
40
|
+
false
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Collapses nested results, returning the innermost one.
|
|
44
|
+
#
|
|
45
|
+
# @return [Result] the innermost result when the value is a Result, otherwise +self+
|
|
46
|
+
def flatten
|
|
47
|
+
return self unless value.is_a?(Result)
|
|
48
|
+
|
|
49
|
+
value.flatten
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Chains another result producing operation onto the wrapped value.
|
|
53
|
+
#
|
|
54
|
+
# @yieldparam value [Object] the wrapped value
|
|
55
|
+
# @yieldreturn [Result] must return a Result
|
|
56
|
+
# @raise [ReturnError] when the block does not return a Result
|
|
57
|
+
# @return [Result] the result returned by the block
|
|
58
|
+
def flat_map
|
|
59
|
+
yield(value).tap { raise ReturnError, "block must return a Result" unless it.is_a?(Result) }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Does nothing, since there is no error to chain from.
|
|
63
|
+
#
|
|
64
|
+
# @return [Ok] +self+, without calling the block
|
|
65
|
+
def flat_map_err
|
|
66
|
+
self
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# @return [String] the inspected value wrapped in <tt>Ok<></tt>, such as <tt>Ok<1></tt>
|
|
70
|
+
def inspect
|
|
71
|
+
"Ok<#{value.inspect}>"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Transforms the wrapped value.
|
|
75
|
+
#
|
|
76
|
+
# @yieldparam value [Object] the wrapped value
|
|
77
|
+
# @return [Ok] an Ok wrapping the block's return value, which is not flattened when it is itself a Result
|
|
78
|
+
def map
|
|
79
|
+
Result.ok(yield(value))
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Does nothing, since there is no error to transform.
|
|
83
|
+
#
|
|
84
|
+
# @return [Ok] +self+, without calling the block
|
|
85
|
+
def map_err
|
|
86
|
+
self
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# @return [Boolean] always true
|
|
90
|
+
def ok?
|
|
91
|
+
true
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Does nothing, since there is no error.
|
|
95
|
+
#
|
|
96
|
+
# @return [Ok] +self+, without calling the block
|
|
97
|
+
def on_err(&)
|
|
98
|
+
self
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Runs the block with the wrapped value for its side effects.
|
|
102
|
+
#
|
|
103
|
+
# @yieldparam value [Object] the wrapped value
|
|
104
|
+
# @return [Ok] +self+, ignoring the block's return value
|
|
105
|
+
def on_ok
|
|
106
|
+
yield(value)
|
|
107
|
+
self
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# @return [Hash{Symbol => Object}] the +:ok+ variant along with the wrapped value
|
|
111
|
+
def to_h
|
|
112
|
+
{ variant: :ok, value: }
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# @return [Object] the wrapped value
|
|
116
|
+
def unwrap!
|
|
117
|
+
value
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Always raises, since there is no error to unwrap.
|
|
121
|
+
#
|
|
122
|
+
# @raise [ReturnError] always
|
|
123
|
+
def unwrap_err!
|
|
124
|
+
raise ReturnError, "cannot unwrap_err! on Ok"
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# @param _default [Object] ignored
|
|
128
|
+
# @return [Object] the wrapped value
|
|
129
|
+
def unwrap_or(_default)
|
|
130
|
+
value
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# @return [Object] the wrapped value, without calling the block
|
|
134
|
+
def unwrap_or_else(&)
|
|
135
|
+
value
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
private
|
|
139
|
+
|
|
140
|
+
attr_reader :value
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "singleton"
|
|
4
|
+
|
|
5
|
+
module DJK
|
|
6
|
+
module Monads
|
|
7
|
+
# An optional value that is either present (Some) or absent (None).
|
|
8
|
+
#
|
|
9
|
+
# Build options with Option.some, Option.none, or Option.from. None is a single shared instance, and a Some can
|
|
10
|
+
# never wrap +nil+.
|
|
11
|
+
#
|
|
12
|
+
# @example
|
|
13
|
+
# Option.from(1).map { it + 1 }.unwrap_or(0)
|
|
14
|
+
# # => 2
|
|
15
|
+
#
|
|
16
|
+
# Option.from(nil).map { it + 1 }.unwrap_or(0)
|
|
17
|
+
# # => 0
|
|
18
|
+
class Option
|
|
19
|
+
include Singleton
|
|
20
|
+
|
|
21
|
+
# Raised by +unwrap!+ when called on None.
|
|
22
|
+
class NoneError < StandardError
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# @api private
|
|
26
|
+
# @param value [Object, nil] the value to wrap, or +nil+ for None
|
|
27
|
+
def initialize(value: nil)
|
|
28
|
+
@value = value
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# @param other [Object] the object to compare against
|
|
32
|
+
# @return [Boolean] true if both are None, or both are a Some wrapping equal values
|
|
33
|
+
def ==(other)
|
|
34
|
+
return false unless other.is_a?(Option)
|
|
35
|
+
return false if some? && other.none?
|
|
36
|
+
return true if none? && other.none?
|
|
37
|
+
|
|
38
|
+
value == other.unwrap!
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Returns the wrapped value, raising +error+ instead of a NoneError when the option is None.
|
|
42
|
+
#
|
|
43
|
+
# @param error [Exception, String] the error to raise for None
|
|
44
|
+
# @raise [Exception] +error+ when the option is None
|
|
45
|
+
# @return [Object] the wrapped value
|
|
46
|
+
def expect(error)
|
|
47
|
+
unwrap!
|
|
48
|
+
rescue NoneError
|
|
49
|
+
raise error
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Chains another option producing operation onto a Some, passing through None untouched.
|
|
53
|
+
#
|
|
54
|
+
# @yieldparam value [Object] the wrapped value, only for a Some
|
|
55
|
+
# @yieldreturn [Option] must return an Option
|
|
56
|
+
# @raise [ReturnError] when the block does not return an Option
|
|
57
|
+
# @return [Option] the option returned by the block, or +self+ when the receiver is None
|
|
58
|
+
def flat_map
|
|
59
|
+
return self if none?
|
|
60
|
+
|
|
61
|
+
yield(value).tap { raise ReturnError, "block must return an Option" unless it.is_a?(Option) }
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Transforms the value of a Some, passing through None untouched.
|
|
65
|
+
#
|
|
66
|
+
# @yieldparam value [Object] the wrapped value, only for a Some
|
|
67
|
+
# @return [Option] a Some wrapping the block's return value, None when the block returns +nil+, or +self+ when
|
|
68
|
+
# the receiver is None
|
|
69
|
+
def map
|
|
70
|
+
return self if none?
|
|
71
|
+
|
|
72
|
+
Option.from(yield(value))
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Converts the option into a Result.
|
|
76
|
+
#
|
|
77
|
+
# @param error [Object] the error to wrap when the option is None
|
|
78
|
+
# @return [Ok, Err] an Ok wrapping the value of a Some, or an Err wrapping +error+ for None
|
|
79
|
+
def ok_or(error)
|
|
80
|
+
some? ? Result.ok(value) : Result.err(error)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Falls back to another option when the receiver is None.
|
|
84
|
+
#
|
|
85
|
+
# @yieldreturn [Option] must return an Option, only called for None
|
|
86
|
+
# @raise [ReturnError] when the block does not return an Option
|
|
87
|
+
# @return [Option] +self+ for a Some, or the option returned by the block for None
|
|
88
|
+
def or_else
|
|
89
|
+
some? ? self : yield.tap { raise ReturnError, "block must return an Option" unless it.is_a?(Option) }
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# @return [Boolean] true if the option is None
|
|
93
|
+
def none?
|
|
94
|
+
value.nil?
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# @return [Boolean] true if the option is a Some
|
|
98
|
+
def some?
|
|
99
|
+
!none?
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Returns the wrapped value, raising when the option is None.
|
|
103
|
+
#
|
|
104
|
+
# @raise [NoneError] when the option is None
|
|
105
|
+
# @return [Object] the wrapped value
|
|
106
|
+
def unwrap!
|
|
107
|
+
raise NoneError, "unwrap! called on None" if none?
|
|
108
|
+
|
|
109
|
+
value
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Returns the wrapped value, falling back to +default+ when the option is None.
|
|
113
|
+
#
|
|
114
|
+
# @param default [Object] the value to return for None
|
|
115
|
+
# @return [Object] the wrapped value or +default+
|
|
116
|
+
def unwrap_or(default)
|
|
117
|
+
none? ? default : value
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Returns the wrapped value, falling back to the block's return value when the option is None.
|
|
121
|
+
#
|
|
122
|
+
# @yieldreturn [Object] the value to return, only called for None
|
|
123
|
+
# @return [Object] the wrapped value or the block's return value
|
|
124
|
+
def unwrap_or_else
|
|
125
|
+
none? ? yield : value
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# @return [Array] the wrapped value in a single element array for a Some, or an empty array for None
|
|
129
|
+
def to_a
|
|
130
|
+
some? ? [value] : []
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Wraps a value that may be +nil+.
|
|
134
|
+
#
|
|
135
|
+
# @param value [Object, nil] the value to wrap
|
|
136
|
+
# @return [Option] None when +value+ is +nil+, otherwise a Some wrapping +value+
|
|
137
|
+
def self.from(value)
|
|
138
|
+
value.nil? ? none : some(value)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# @return [Option] the shared None instance
|
|
142
|
+
def self.none = instance
|
|
143
|
+
|
|
144
|
+
# Wraps a value that must not be +nil+.
|
|
145
|
+
#
|
|
146
|
+
# @param value [Object] the value to wrap
|
|
147
|
+
# @raise [ArgumentError] when +value+ is +nil+
|
|
148
|
+
# @return [Option] a Some wrapping +value+
|
|
149
|
+
def self.some(value)
|
|
150
|
+
raise ArgumentError, "value cannot be nil" if value.nil?
|
|
151
|
+
|
|
152
|
+
new(value:)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
private
|
|
156
|
+
|
|
157
|
+
attr_reader :value
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DJK
|
|
4
|
+
module Monads
|
|
5
|
+
# The outcome of an operation that can either succeed with a value (Ok) or fail with an error (Err).
|
|
6
|
+
#
|
|
7
|
+
# Build results with Result.ok, Result.err, or Result.from rather than instantiating the subclasses directly.
|
|
8
|
+
#
|
|
9
|
+
# @abstract Subclassed by Ok and Err, which implement every instance method.
|
|
10
|
+
# @example
|
|
11
|
+
# Result.ok(1).map { it + 1 }
|
|
12
|
+
# # => Ok<2>
|
|
13
|
+
#
|
|
14
|
+
# case Result.from { Integer("oops") }
|
|
15
|
+
# in Ok[value] then value
|
|
16
|
+
# in Err[error] then error.message
|
|
17
|
+
# end
|
|
18
|
+
# # => "invalid value for Integer(): \"oops\""
|
|
19
|
+
class Result
|
|
20
|
+
# Compares this result with another object.
|
|
21
|
+
#
|
|
22
|
+
# @abstract
|
|
23
|
+
# @param other [Object] the object to compare against
|
|
24
|
+
# @return [Boolean] true if +other+ is a Result of the same variant wrapping an equal value
|
|
25
|
+
def ==(other)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Destructures the result for array patterns, exposing the wrapped value or error.
|
|
29
|
+
#
|
|
30
|
+
# @abstract
|
|
31
|
+
# @return [Array] a single element array holding the value (Ok) or the error (Err)
|
|
32
|
+
def deconstruct
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Destructures the result for hash patterns.
|
|
36
|
+
#
|
|
37
|
+
# @abstract
|
|
38
|
+
# @param keys [Array<Symbol>, nil] the keys requested by the pattern
|
|
39
|
+
# @return [Hash] the wrapped value or error keyed by variant
|
|
40
|
+
def deconstruct_keys(keys)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# @abstract
|
|
44
|
+
# @return [Boolean] true if the result is an Err
|
|
45
|
+
def err?
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Collapses a result wrapping another result into a single result.
|
|
49
|
+
#
|
|
50
|
+
# @abstract
|
|
51
|
+
# @return [Result] the innermost result, or +self+ when nothing is nested
|
|
52
|
+
def flatten
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Chains another result producing operation onto an Ok, passing through an Err untouched.
|
|
56
|
+
#
|
|
57
|
+
# @abstract
|
|
58
|
+
# @yieldparam value [Object] the wrapped value, only for an Ok
|
|
59
|
+
# @yieldreturn [Result] must return a Result
|
|
60
|
+
# @return [Result] the result returned by the block, or +self+ when the receiver is an Err
|
|
61
|
+
def flat_map
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Chains another result producing operation onto an Err, passing through an Ok untouched.
|
|
65
|
+
#
|
|
66
|
+
# @abstract
|
|
67
|
+
# @yieldparam error [Object] the wrapped error, only for an Err
|
|
68
|
+
# @yieldreturn [Result] must return a Result
|
|
69
|
+
# @return [Result] the result returned by the block, or +self+ when the receiver is an Ok
|
|
70
|
+
def flat_map_err
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# @abstract
|
|
74
|
+
# @return [String] a human readable representation of the result
|
|
75
|
+
def inspect
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Transforms the value of an Ok, passing through an Err untouched.
|
|
79
|
+
#
|
|
80
|
+
# @abstract
|
|
81
|
+
# @yieldparam value [Object] the wrapped value, only for an Ok
|
|
82
|
+
# @return [Result] an Ok wrapping the block's return value, or +self+ when the receiver is an Err
|
|
83
|
+
def map
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Transforms the error of an Err, passing through an Ok untouched.
|
|
87
|
+
#
|
|
88
|
+
# @abstract
|
|
89
|
+
# @yieldparam error [Object] the wrapped error, only for an Err
|
|
90
|
+
# @return [Result] an Err wrapping the block's return value, or +self+ when the receiver is an Ok
|
|
91
|
+
def map_err
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# @abstract
|
|
95
|
+
# @return [Boolean] true if the result is an Ok
|
|
96
|
+
def ok?
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Runs the block for its side effects when the result is an Err.
|
|
100
|
+
#
|
|
101
|
+
# @abstract
|
|
102
|
+
# @yieldparam error [Object] the wrapped error, only for an Err
|
|
103
|
+
# @return [Result] +self+
|
|
104
|
+
def on_err
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Runs the block for its side effects when the result is an Ok.
|
|
108
|
+
#
|
|
109
|
+
# @abstract
|
|
110
|
+
# @yieldparam value [Object] the wrapped value, only for an Ok
|
|
111
|
+
# @return [Result] +self+
|
|
112
|
+
def on_ok
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# @abstract
|
|
116
|
+
# @return [Hash] the result's variant along with its value or error
|
|
117
|
+
def to_h
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Returns the wrapped value, raising when the result is an Err.
|
|
121
|
+
#
|
|
122
|
+
# @abstract
|
|
123
|
+
# @raise [Object] the wrapped error when the result is an Err
|
|
124
|
+
# @return [Object] the wrapped value
|
|
125
|
+
def unwrap!
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Returns the wrapped error, raising when the result is an Ok.
|
|
129
|
+
#
|
|
130
|
+
# @abstract
|
|
131
|
+
# @raise [ReturnError] when the result is an Ok
|
|
132
|
+
# @return [Object] the wrapped error
|
|
133
|
+
def unwrap_err!
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Returns the wrapped value, falling back to +default+ when the result is an Err.
|
|
137
|
+
#
|
|
138
|
+
# @abstract
|
|
139
|
+
# @param default [Object] the value to return for an Err
|
|
140
|
+
# @return [Object] the wrapped value or +default+
|
|
141
|
+
def unwrap_or(default)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Returns the wrapped value, falling back to the block's return value when the result is an Err.
|
|
145
|
+
#
|
|
146
|
+
# @abstract
|
|
147
|
+
# @yieldparam error [Object] the wrapped error, only for an Err
|
|
148
|
+
# @return [Object] the wrapped value or the block's return value
|
|
149
|
+
def unwrap_or_else
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Implicit hash conversion, which lets a result be splatted into a hash with <tt>**result</tt>.
|
|
153
|
+
#
|
|
154
|
+
# @return [Hash] the same hash as +to_h+
|
|
155
|
+
def to_hash = to_h
|
|
156
|
+
|
|
157
|
+
# @return [String] the same string as +inspect+
|
|
158
|
+
def to_s = inspect
|
|
159
|
+
|
|
160
|
+
# Removes all instances of <tt>Ok<nil></tt> from +results+, preserving the order of the remaining results.
|
|
161
|
+
#
|
|
162
|
+
# @param results [Array<Result>] the results to compact
|
|
163
|
+
# @return [Array<Result>] +results+ without any Ok wrapping +nil+
|
|
164
|
+
def self.compact(results)
|
|
165
|
+
results.reject { it.ok? && it.unwrap!.nil? }
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Creates a new Err.
|
|
169
|
+
#
|
|
170
|
+
# @param error [Object] the error to wrap
|
|
171
|
+
# @return [Err] an Err wrapping +error+
|
|
172
|
+
def self.err(error)
|
|
173
|
+
Err.new(error)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Runs the provided block, wrapping the returned value in Ok. If an exception is raised, returns an Err with
|
|
177
|
+
# that exception instead.
|
|
178
|
+
#
|
|
179
|
+
# Only StandardError and its subclasses are rescued, so exceptions such as NotImplementedError still propagate.
|
|
180
|
+
#
|
|
181
|
+
# @yieldreturn [Object] the value to wrap in an Ok
|
|
182
|
+
# @return [Ok, Err] an Ok wrapping the block's return value, or an Err wrapping the exception it raised
|
|
183
|
+
def self.from
|
|
184
|
+
Result.ok(yield)
|
|
185
|
+
rescue StandardError => e
|
|
186
|
+
Result.err(e)
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# Creates a new Ok.
|
|
190
|
+
#
|
|
191
|
+
# @param value [Object] the value to wrap
|
|
192
|
+
# @return [Ok] an Ok wrapping +value+
|
|
193
|
+
def self.ok(value)
|
|
194
|
+
Ok.new(value)
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# Splits results into their unwrapped values and unwrapped errors, preserving their order.
|
|
198
|
+
#
|
|
199
|
+
# @param results [Array<Result>] the results to split
|
|
200
|
+
# @raise [ArgumentError] when any element of +results+ is not a Result
|
|
201
|
+
# @return [Array(Array, Array)] the unwrapped values of every Ok, followed by the unwrapped errors of every Err
|
|
202
|
+
# @example
|
|
203
|
+
# Result.partition([Result.ok(1), Result.err("boom"), Result.ok(2)])
|
|
204
|
+
# # => [[1, 2], ["boom"]]
|
|
205
|
+
def self.partition(results)
|
|
206
|
+
raise ArgumentError, "results must all be Result" unless results.all?(Result)
|
|
207
|
+
|
|
208
|
+
results.each_with_object([[], []]) do |result, (ok, err)|
|
|
209
|
+
result.on_ok { |value| ok << value }.on_err { |error| err << error }
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# Maps each value in +values+ through a block, collecting the unwrapped values into an <tt>Ok<Array></tt>.
|
|
214
|
+
# Short-circuits on the first Err.
|
|
215
|
+
#
|
|
216
|
+
# @param values [Enumerable] the values to map to results
|
|
217
|
+
# @yieldparam value [Object] each element of +values+
|
|
218
|
+
# @yieldreturn [Result] must return a Result
|
|
219
|
+
# @return [Ok<Array>, Err] all unwrapped values if all block calls returned Ok or the first Err encountered
|
|
220
|
+
# @example
|
|
221
|
+
# Result.traverse([1, 2, 3]) { |n| Result.ok(n * 2) }
|
|
222
|
+
# # => Ok<[2, 4, 6]>
|
|
223
|
+
#
|
|
224
|
+
# Result.traverse([1, 2, 3]) { |n| n == 2 ? Result.err("bad") : Result.ok(n) }
|
|
225
|
+
# # => Err<"bad">
|
|
226
|
+
def self.traverse(values)
|
|
227
|
+
values.reduce(Result.ok([])) do |result, value|
|
|
228
|
+
result.flat_map do |outputs|
|
|
229
|
+
yield(value)
|
|
230
|
+
.tap { raise ReturnError, "block must return a Result" unless it.is_a?(Result) }
|
|
231
|
+
.map { |output| [*outputs, output] }
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
end
|
data/lib/djk/monads.rb
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "monads/version"
|
|
4
|
+
require_relative "monads/return_error"
|
|
5
|
+
require_relative "monads/result"
|
|
6
|
+
require_relative "monads/ok"
|
|
7
|
+
require_relative "monads/err"
|
|
8
|
+
require_relative "monads/option"
|
|
9
|
+
|
|
10
|
+
module DJK
|
|
11
|
+
# Rust-style Result and Option monads.
|
|
12
|
+
module Monads
|
|
13
|
+
class Error < StandardError
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
ALIASES = %i[ReturnError Err Ok Option Result].freeze
|
|
17
|
+
private_constant :ALIASES
|
|
18
|
+
|
|
19
|
+
# Defines ReturnError, Err, Ok, Option, and Result as top level constants, so they can be used without the
|
|
20
|
+
# DJK::Monads namespace.
|
|
21
|
+
#
|
|
22
|
+
# A constant that is already defined at the top level is left untouched, and a warning naming it is printed to
|
|
23
|
+
# stderr. Constants that already point to the matching DJK::Monads class, such as from an earlier call, are skipped
|
|
24
|
+
# without a warning.
|
|
25
|
+
#
|
|
26
|
+
# @return [Array<Symbol>] the names of the constants that were defined by this call
|
|
27
|
+
# @example
|
|
28
|
+
# DJK::Monads.apply_aliases!
|
|
29
|
+
# # => [:ReturnError, :Err, :Ok, :Option, :Result]
|
|
30
|
+
#
|
|
31
|
+
# Result.ok(1)
|
|
32
|
+
# # => Ok<1>
|
|
33
|
+
def self.apply_aliases!
|
|
34
|
+
defined, undefined = ALIASES.partition { Object.const_defined?(it, false) }
|
|
35
|
+
conflicts = defined.reject { Object.const_get(it, false).equal?(const_get(it)) }
|
|
36
|
+
unless conflicts.empty?
|
|
37
|
+
warn "DJK::Monads.apply_aliases! skipped already defined constants: #{conflicts.join(", ")}", uplevel: 1
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
undefined.each { Object.const_set(it, const_get(it)) }
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
data/lib/djk.rb
ADDED
metadata
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: djk_monads
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Dan Kotowski
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies: []
|
|
12
|
+
description: A collection of Rust-like monads for Ruby that I'm using in my personal
|
|
13
|
+
projects.
|
|
14
|
+
email:
|
|
15
|
+
- dan@dankotowski.dev
|
|
16
|
+
executables: []
|
|
17
|
+
extensions: []
|
|
18
|
+
extra_rdoc_files: []
|
|
19
|
+
files:
|
|
20
|
+
- CHANGELOG.md
|
|
21
|
+
- CODE_OF_CONDUCT.md
|
|
22
|
+
- LICENSE.txt
|
|
23
|
+
- README.md
|
|
24
|
+
- lib/djk.rb
|
|
25
|
+
- lib/djk/monads.rb
|
|
26
|
+
- lib/djk/monads/err.rb
|
|
27
|
+
- lib/djk/monads/ok.rb
|
|
28
|
+
- lib/djk/monads/option.rb
|
|
29
|
+
- lib/djk/monads/result.rb
|
|
30
|
+
- lib/djk/monads/return_error.rb
|
|
31
|
+
- lib/djk/monads/version.rb
|
|
32
|
+
homepage: https://github.com/djkotowski/djk_monads
|
|
33
|
+
licenses:
|
|
34
|
+
- MIT
|
|
35
|
+
metadata:
|
|
36
|
+
allowed_push_host: https://rubygems.org
|
|
37
|
+
homepage_uri: https://github.com/djkotowski/djk_monads
|
|
38
|
+
changelog_uri: https://github.com/djkotowski/djk_monads/blob/main/CHANGELOG.md
|
|
39
|
+
rubygems_mfa_required: 'true'
|
|
40
|
+
rdoc_options: []
|
|
41
|
+
require_paths:
|
|
42
|
+
- lib
|
|
43
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - ">="
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: 4.0.0
|
|
48
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
49
|
+
requirements:
|
|
50
|
+
- - ">="
|
|
51
|
+
- !ruby/object:Gem::Version
|
|
52
|
+
version: '0'
|
|
53
|
+
requirements: []
|
|
54
|
+
rubygems_version: 4.0.21
|
|
55
|
+
specification_version: 4
|
|
56
|
+
summary: Rust-style monads for Ruby
|
|
57
|
+
test_files: []
|