errgonomic 0.9.2 → 0.9.3
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 +12 -0
- data/README.md +1 -1
- data/lib/errgonomic/core_ext/enumerable.rb +2 -2
- data/lib/errgonomic/option.rb +57 -3
- data/lib/errgonomic/result.rb +27 -8
- data/lib/errgonomic/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 58963c6e1c1cfcf52d8fe4b065dd986acda616f4e55001d7d2bc13cbeab1eb28
|
|
4
|
+
data.tar.gz: 9d6f93fdd27c94525f2f2a2dc53010eb4f99760dae7514eaf4f035f5c9947869
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2adf6eadd64c94629ba265ea9747d943314616f8c26943ad075ea5399bc86c4747e433946273989640b668e0841c337cdc6f3ecc873fed9006aa82eb439510d9
|
|
7
|
+
data.tar.gz: 475c6a42553b89700d4de4d0d7257bcbb71388c61d8b12da69a51ce4aa30a75f7074757a498bb20d5074539748b8e799bf8c78303ef3dc8ac33a74306d615aad
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.9.3] - 2026-09-10
|
|
4
|
+
|
|
5
|
+
This release removes the public `value` slot from `Some`, `Ok` and `Err`, and freezes every instance as it is constructed, so an Option or a Result is the value the README already said it was.
|
|
6
|
+
|
|
7
|
+
### Upgrading from 0.9.2
|
|
8
|
+
|
|
9
|
+
`value` and `value=` are gone from `Some`, `Ok` and `Err`, and every instance is frozen as it is constructed. A read of `.value` becomes `unwrap_or(fallback)`, `expect!(message)`, `map`, `and_then` or a pattern, each of which names the other branch; a write of `.value=` becomes a new `Some(v)` assigned where the old one lived. A call to either now raises `Errgonomic::UnwrappedAccessError`, which is a `NoMethodError`, naming the combinators.
|
|
10
|
+
|
|
11
|
+
### Changes
|
|
12
|
+
|
|
13
|
+
- [Behavior change] `Some`, `Ok` and `Err` no longer expose `value` or `value=`, and every Option and Result is frozen as it is constructed, whether by `Some`, `None`, `Ok`, `Err`, `new` or a combinator; `clone` keeps it frozen. A copy that skips construction, from `dup`, `Marshal.load`, a YAML load or ActiveSupport's `deep_dup`, is not frozen; with no writer, it changes only through `instance_variable_set`. The reader reached the inner value with no `None` branch, the writer mutated a wrapper through an alias and moved a Hash key out from under its own bucket, and the README already said an Option is a value rather than a slot. The reader is protected, for the sibling reads equality, ordering and `zip` need; a call from outside gets the combinator teaching `Errgonomic::UnwrappedAccessError` gives any other miss
|
|
14
|
+
|
|
3
15
|
## [0.9.2] - 2026-09-10
|
|
4
16
|
|
|
5
17
|
This release makes `Option#and`, `#xor`, `#zip` and `#zip_with` check their operand the way `or` already did.
|
data/README.md
CHANGED
|
@@ -144,7 +144,7 @@ Writers unwrap under that integration, which changes what a truthiness slip cost
|
|
|
144
144
|
|
|
145
145
|
The remaining present-side helpers are soft-deprecated on Options in favor of the combinators. They unwrap, where on any other object they return the receiver: `Some(v).present_or_raise!(msg)`, `present_or(default)` and `present_or_else { }` all yield `v`, and `None` raises, substitutes, or computes. Each prints a one-line stderr nudge naming the combinator to use instead (`expect!`, `unwrap_or`, `unwrap_or_else`), once per process per method rather than once per call, so a hot path does not flood the log. The blank side (`blank_or*`) raises `UnwrappedAccessError` outright: an Option's blankness is its discriminant, so test it with `none?`.
|
|
146
146
|
|
|
147
|
-
Four of Rust's methods are deliberately absent: `take`, `replace`, `insert` and `get_or_insert`. Every one of them writes through an `&mut Option`, and an Option here is a value rather than a slot: `Some(1)` is something you pass around and compare, not a cell whose contents you swap out from under another reference. Build the Option you want and assign it where the old one lived.
|
|
147
|
+
Four of Rust's methods are deliberately absent: `take`, `replace`, `insert` and `get_or_insert`. Every one of them writes through an `&mut Option`, and an Option here is a value rather than a slot: `Some(1)` is something you pass around and compare, not a cell whose contents you swap out from under another reference. Build the Option you want and assign it where the old one lived. The same rule is why a `Some`, an `Ok` and an `Err` have no `value` reader or writer, and why every instance is frozen as it is constructed. A copy that skips construction, from `dup`, `Marshal.load`, a YAML load or ActiveSupport's `deep_dup`, is not frozen; with no writer, it changes only through `instance_variable_set`. A reader would reach the inner value with no `None` branch, and a writer would move a Hash key out from under its own bucket. Reach in with `unwrap_or`, `expect!`, `map`, `and_then` or a pattern, each of which names what happens on the other branch.
|
|
148
148
|
|
|
149
149
|
Equality is between Options only: `Some(5) == Some(5)`, but `Some(5) == 5` and `None() == nil` are `false`. That is quiet, never an error, matching how every Ruby object compares across types. Rust rejects `Some(5) == 5` at compile time; Ruby cannot, so guard the idiom in review and tests: compare against a wrapped value (`opt == Some(5)`) or test the inner value (`opt.some_and? { |v| v == 5 }`). `Errgonomic.strict_equality = true` turns that guard into an error, which is what a test suite wants; see [Pedantic runtime checks](#pedantic-runtime-checks).
|
|
150
150
|
|
|
@@ -46,7 +46,7 @@ module Enumerable
|
|
|
46
46
|
end
|
|
47
47
|
return None() if member.none?
|
|
48
48
|
|
|
49
|
-
values <<
|
|
49
|
+
member.tap_some { |value| values << value }
|
|
50
50
|
end
|
|
51
51
|
Some(values)
|
|
52
52
|
end
|
|
@@ -87,7 +87,7 @@ module Enumerable
|
|
|
87
87
|
end
|
|
88
88
|
return member if member.err?
|
|
89
89
|
|
|
90
|
-
values <<
|
|
90
|
+
member.tap_ok { |value| values << value }
|
|
91
91
|
end
|
|
92
92
|
Ok(values)
|
|
93
93
|
end
|
data/lib/errgonomic/option.rb
CHANGED
|
@@ -178,7 +178,7 @@ module Errgonomic
|
|
|
178
178
|
# measurement = Errgonomic::Option::Some.new(1)
|
|
179
179
|
# case measurement
|
|
180
180
|
# in Errgonomic::Option::Some, value
|
|
181
|
-
# "Measurement is #{
|
|
181
|
+
# "Measurement is #{value}"
|
|
182
182
|
# in Errgonomic::Option::None
|
|
183
183
|
# "Measurement is not available"
|
|
184
184
|
# else
|
|
@@ -851,11 +851,47 @@ module Errgonomic
|
|
|
851
851
|
|
|
852
852
|
# Represent a value
|
|
853
853
|
class Some < Any
|
|
854
|
-
|
|
855
|
-
|
|
854
|
+
# A Some is a value, not a slot: nothing outside reads the inner value
|
|
855
|
+
# without handling the None branch, and nothing swaps it out from under
|
|
856
|
+
# another reference or a Hash key.
|
|
857
|
+
#
|
|
858
|
+
# @example the inner value is reached through a combinator, never a reader
|
|
859
|
+
# begin
|
|
860
|
+
# Some(1).value
|
|
861
|
+
# rescue NoMethodError => e
|
|
862
|
+
# e.class
|
|
863
|
+
# end # => Errgonomic::UnwrappedAccessError
|
|
864
|
+
# Some(1).respond_to?(:value) # => false
|
|
865
|
+
#
|
|
866
|
+
# @example a Some cannot be mutated through an alias
|
|
867
|
+
# a = Some(1)
|
|
868
|
+
# b = a
|
|
869
|
+
# begin
|
|
870
|
+
# b.value = 99
|
|
871
|
+
# rescue NoMethodError => e
|
|
872
|
+
# e.class
|
|
873
|
+
# end # => Errgonomic::UnwrappedAccessError
|
|
874
|
+
# a # => Some(1)
|
|
875
|
+
# Some(1).frozen? # => true
|
|
876
|
+
# begin
|
|
877
|
+
# Some(1).instance_variable_set(:@value, 2)
|
|
878
|
+
# rescue FrozenError => e
|
|
879
|
+
# e.class
|
|
880
|
+
# end # => FrozenError
|
|
881
|
+
#
|
|
882
|
+
# @example a Some keeps its place as a Hash key
|
|
883
|
+
# k = Some(1)
|
|
884
|
+
# h = { k => :v }
|
|
885
|
+
# begin
|
|
886
|
+
# k.value = 2
|
|
887
|
+
# rescue NoMethodError
|
|
888
|
+
# nil
|
|
889
|
+
# end
|
|
890
|
+
# h[k] # => :v
|
|
856
891
|
def initialize(value)
|
|
857
892
|
super()
|
|
858
893
|
@value = value
|
|
894
|
+
freeze
|
|
859
895
|
end
|
|
860
896
|
|
|
861
897
|
def some?
|
|
@@ -877,10 +913,28 @@ module Errgonomic
|
|
|
877
913
|
def inspect
|
|
878
914
|
"Some(#{value.inspect})"
|
|
879
915
|
end
|
|
916
|
+
|
|
917
|
+
protected
|
|
918
|
+
|
|
919
|
+
# Sibling instances read each other's value for equality, ordering and
|
|
920
|
+
# zip; nothing else does.
|
|
921
|
+
attr_reader :value
|
|
880
922
|
end
|
|
881
923
|
|
|
882
924
|
# Represent the absence of a value.
|
|
883
925
|
class None < Any
|
|
926
|
+
# @example a None has no value to read, and says so the same way a Some does
|
|
927
|
+
# begin
|
|
928
|
+
# None().value
|
|
929
|
+
# rescue NoMethodError => e
|
|
930
|
+
# e.class
|
|
931
|
+
# end # => Errgonomic::UnwrappedAccessError
|
|
932
|
+
# None().frozen? # => true
|
|
933
|
+
def initialize
|
|
934
|
+
super
|
|
935
|
+
freeze
|
|
936
|
+
end
|
|
937
|
+
|
|
884
938
|
def some?
|
|
885
939
|
false
|
|
886
940
|
end
|
data/lib/errgonomic/result.rb
CHANGED
|
@@ -8,10 +8,27 @@ module Errgonomic
|
|
|
8
8
|
class Any
|
|
9
9
|
include Comparable
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
# A Result is a value, not a slot: the inner value is reached through a
|
|
12
|
+
# combinator that handles the other variant, and nothing swaps it out
|
|
13
|
+
# from under another reference.
|
|
14
|
+
#
|
|
15
|
+
# @example
|
|
16
|
+
# begin
|
|
17
|
+
# Ok(1).value
|
|
18
|
+
# rescue NoMethodError => e
|
|
19
|
+
# e.class
|
|
20
|
+
# end # => Errgonomic::UnwrappedAccessError
|
|
21
|
+
# begin
|
|
22
|
+
# Err(:x).value = :y
|
|
23
|
+
# rescue NoMethodError => e
|
|
24
|
+
# e.class
|
|
25
|
+
# end # => Errgonomic::UnwrappedAccessError
|
|
26
|
+
# Ok(1).respond_to?(:value) # => false
|
|
27
|
+
# Ok(1).frozen? # => true
|
|
28
|
+
# Err().frozen? # => true
|
|
13
29
|
def initialize(value)
|
|
14
30
|
@value = value
|
|
31
|
+
freeze
|
|
15
32
|
end
|
|
16
33
|
|
|
17
34
|
# Results order like Rust's: Ok sorts before any Err, and same variants
|
|
@@ -477,6 +494,12 @@ module Errgonomic
|
|
|
477
494
|
[self, value]
|
|
478
495
|
end
|
|
479
496
|
|
|
497
|
+
protected
|
|
498
|
+
|
|
499
|
+
# Sibling instances read each other's value for equality and ordering;
|
|
500
|
+
# nothing else does.
|
|
501
|
+
attr_reader :value
|
|
502
|
+
|
|
480
503
|
private
|
|
481
504
|
|
|
482
505
|
def to_s_refusal
|
|
@@ -512,8 +535,6 @@ module Errgonomic
|
|
|
512
535
|
|
|
513
536
|
# The Ok variant.
|
|
514
537
|
class Ok < Any
|
|
515
|
-
attr_accessor :value
|
|
516
|
-
|
|
517
538
|
# Ok is always ok
|
|
518
539
|
#
|
|
519
540
|
# @example
|
|
@@ -544,13 +565,11 @@ module Errgonomic
|
|
|
544
565
|
class Err < Any
|
|
545
566
|
class Arbitrary; end
|
|
546
567
|
|
|
547
|
-
attr_accessor :value
|
|
548
|
-
|
|
549
568
|
# Err may be constructed without a value, if you want.
|
|
550
569
|
#
|
|
551
570
|
# @example
|
|
552
|
-
# Err(:y).
|
|
553
|
-
# Err().
|
|
571
|
+
# Err(:y).unwrap_err! # => :y
|
|
572
|
+
# Err().unwrap_err! # => Arbitrary
|
|
554
573
|
def initialize(value = Arbitrary)
|
|
555
574
|
super(value)
|
|
556
575
|
end
|
data/lib/errgonomic/version.rb
CHANGED