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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2b6ae05f6601a1d5470b1cb9cd9266e3937b91fd2ea2bab8eeac469959b24e90
4
- data.tar.gz: 5b3095f01409a6275b500df7d08e0c8b23b50062924d61ada8a4a6c98e0b69fe
3
+ metadata.gz: 58963c6e1c1cfcf52d8fe4b065dd986acda616f4e55001d7d2bc13cbeab1eb28
4
+ data.tar.gz: 9d6f93fdd27c94525f2f2a2dc53010eb4f99760dae7514eaf4f035f5c9947869
5
5
  SHA512:
6
- metadata.gz: a74d4cdf58805801e853a59329b076ca43c553da3a670fc56762fea5d755da898c6a6951e2c697fcb91b28930382d2f0ff6e3e2a85038ad33a5a3a0d4567c6c3
7
- data.tar.gz: d636451a6addbfc3e2af62974e659fe7de490dd7b4176f3cf2f53c6f93dd6d620244f14e99a684817a160a4715c02ed4b5d0b003bded913f5962e64987d674cc
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 << member.value
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 << member.value
90
+ member.tap_ok { |value| values << value }
91
91
  end
92
92
  Ok(values)
93
93
  end
@@ -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 #{measurement.value}"
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
- attr_accessor :value
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
@@ -8,10 +8,27 @@ module Errgonomic
8
8
  class Any
9
9
  include Comparable
10
10
 
11
- attr_reader :value
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).value # => :y
553
- # Err().value # => Arbitrary
571
+ # Err(:y).unwrap_err! # => :y
572
+ # Err().unwrap_err! # => Arbitrary
554
573
  def initialize(value = Arbitrary)
555
574
  super(value)
556
575
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Errgonomic
4
- VERSION = '0.9.2'
4
+ VERSION = '0.9.3'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: errgonomic
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.2
4
+ version: 0.9.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nick Zadrozny