immutable-ruby 0.0.1 → 0.0.2

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.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/lib/immutable/core_ext/struct.rb +9 -0
  3. data/lib/immutable/enumerable.rb +9 -0
  4. data/lib/immutable/hash.rb +104 -4
  5. data/lib/immutable/list.rb +13 -13
  6. data/lib/immutable/nested.rb +3 -0
  7. data/lib/immutable/vector.rb +21 -11
  8. data/lib/immutable/version.rb +1 -1
  9. data/spec/lib/immutable/hash/dig_spec.rb +34 -0
  10. data/spec/lib/immutable/hash/fetch_values_spec.rb +22 -0
  11. data/spec/lib/immutable/hash/put_spec.rb +9 -0
  12. data/spec/lib/immutable/hash/subset_spec.rb +42 -0
  13. data/spec/lib/immutable/hash/superset_spec.rb +42 -0
  14. data/spec/lib/immutable/hash/to_proc_spec.rb +39 -0
  15. data/spec/lib/immutable/hash/values_at_spec.rb +26 -6
  16. data/spec/lib/immutable/list/all_spec.rb +1 -1
  17. data/spec/lib/immutable/list/any_spec.rb +1 -1
  18. data/spec/lib/immutable/list/at_spec.rb +1 -1
  19. data/spec/lib/immutable/list/construction_spec.rb +1 -1
  20. data/spec/lib/immutable/list/count_spec.rb +1 -1
  21. data/spec/lib/immutable/list/each_slice_spec.rb +1 -1
  22. data/spec/lib/immutable/list/each_spec.rb +1 -1
  23. data/spec/lib/immutable/list/empty_spec.rb +1 -1
  24. data/spec/lib/immutable/list/eql_spec.rb +1 -1
  25. data/spec/lib/immutable/list/find_index_spec.rb +1 -1
  26. data/spec/lib/immutable/list/find_spec.rb +1 -1
  27. data/spec/lib/immutable/list/group_by_spec.rb +1 -1
  28. data/spec/lib/immutable/list/hash_spec.rb +1 -1
  29. data/spec/lib/immutable/list/include_spec.rb +1 -1
  30. data/spec/lib/immutable/list/index_spec.rb +6 -2
  31. data/spec/lib/immutable/list/indices_spec.rb +1 -1
  32. data/spec/lib/immutable/list/inspect_spec.rb +1 -1
  33. data/spec/lib/immutable/list/join_spec.rb +1 -1
  34. data/spec/lib/immutable/list/last_spec.rb +1 -1
  35. data/spec/lib/immutable/list/maximum_spec.rb +1 -1
  36. data/spec/lib/immutable/list/minimum_spec.rb +1 -1
  37. data/spec/lib/immutable/list/multithreading_spec.rb +4 -4
  38. data/spec/lib/immutable/list/none_spec.rb +1 -1
  39. data/spec/lib/immutable/list/one_spec.rb +1 -1
  40. data/spec/lib/immutable/list/product_spec.rb +1 -1
  41. data/spec/lib/immutable/list/reduce_spec.rb +1 -1
  42. data/spec/lib/immutable/list/reverse_spec.rb +1 -1
  43. data/spec/lib/immutable/list/size_spec.rb +1 -1
  44. data/spec/lib/immutable/list/sum_spec.rb +1 -1
  45. data/spec/lib/immutable/list/tail_spec.rb +1 -1
  46. data/spec/lib/immutable/list/to_a_spec.rb +1 -1
  47. data/spec/lib/immutable/list/to_ary_spec.rb +1 -1
  48. data/spec/lib/immutable/nested/construction_spec.rb +11 -5
  49. data/spec/lib/immutable/set/add_spec.rb +4 -2
  50. data/spec/lib/immutable/set/grep_spec.rb +10 -10
  51. data/spec/lib/immutable/set/grep_v_spec.rb +59 -0
  52. data/spec/lib/immutable/vector/dig_spec.rb +30 -0
  53. data/spec/spec_helper.rb +4 -0
  54. metadata +323 -306
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: de7204bc2220b5743b2d8b3344d562377bd692a9
4
- data.tar.gz: 965f9782b7d7a5b803f2ee76c3ef10d5f1203a2b
3
+ metadata.gz: a963e4f0565bc516534d1cf81f0206357b252f3a
4
+ data.tar.gz: 38a237bd2bdcef74cb66971375e1414ea36c67bd
5
5
  SHA512:
6
- metadata.gz: 3e7a5faa17dfdbbcbf86ba190a30537cf9b52c7b571cee68158fed8bfa69be547aeb8af91d9f91efd875a7b21462a1f84efce0edab1283fd132b9e8e819122c2
7
- data.tar.gz: b06f1d17c9f1159433c2db10a2a30788a02cf379f093da86b24e77ccefe04323582beef964dcf7d220e5c3e54ac7e98e40f794e87a408da07f9c2b38da59951b
6
+ metadata.gz: '040888fc5816c86d1df3333bd1e7e922096c7775e196f5bf3c9e9dedc599dc55b888cf31c1b602bcc4bfda8c06e28b436a793d4e221fca54855e69b0b2d2e523'
7
+ data.tar.gz: fb6bffbec4a70ccc00058140a563007cbf7617c55af03328c2a8385772caaf4d6e0d22e9e10664541ec04ad3f1dfe43f5b3b327268539841d6edb9c7683af1dc
@@ -0,0 +1,9 @@
1
+ class Struct
2
+ # Implement Struct#to_h for Ruby interpreters which don't have it
3
+ # (such as MRI 1.9.3 and lower)
4
+ unless method_defined?(:to_h)
5
+ def to_h
6
+ Hash[each_pair.to_a]
7
+ end
8
+ end
9
+ end
@@ -30,6 +30,15 @@ module Immutable
30
30
  result
31
31
  end
32
32
 
33
+ # Search the collection for elements which are not `#===` to `item`. Yield
34
+ # them to the optional code block if provided, and return them as a new
35
+ # collection.
36
+ def grep_v(pattern, &block)
37
+ result = select { |item| !(pattern === item) }
38
+ result = result.map(&block) if block_given?
39
+ result
40
+ end
41
+
33
42
  # Yield all integers from 0 up to, but not including, the number of items in
34
43
  # this collection. For collections which provide indexed access, these are all
35
44
  # the valid, non-negative indices into the collection.
@@ -264,6 +264,12 @@ module Immutable
264
264
  end
265
265
  end
266
266
 
267
+ # @private
268
+ # @raise NoMethodError
269
+ def []=(*)
270
+ raise NoMethodError, "Immutable::Hash doesn't support `[]='; use `put' instead"
271
+ end
272
+
267
273
  # Return a new `Hash` with a deeply nested value modified to the result of
268
274
  # the given code block. When traversing the nested `Hash`es and `Vector`s,
269
275
  # non-existing keys are created with empty `Hash` values.
@@ -570,20 +576,54 @@ module Immutable
570
576
  end
571
577
 
572
578
  # Return a {Vector} of the values which correspond to the `wanted` keys.
573
- # If any of the `wanted` keys are not present in this `Hash`, they will be skipped.
579
+ # If any of the `wanted` keys are not present in this `Hash`, `nil` will be
580
+ # placed instead, or the result of the default proc (if one is defined),
581
+ # similar to the behavior of {#get}.
574
582
  #
575
583
  # @example
576
584
  # h = Immutable::Hash["A" => 1, "B" => 2, "C" => 3]
577
- # h.values_at("B", "A", "D") # => Immutable::Vector[2, 1]
585
+ # h.values_at("B", "A", "D") # => Immutable::Vector[2, 1, nil]
578
586
  #
579
587
  # @param wanted [Array] The keys to retrieve
580
588
  # @return [Vector]
581
589
  def values_at(*wanted)
582
- array = []
583
- wanted.each { |key| array << get(key) if key?(key) }
590
+ Vector.new(wanted.map { |key| get(key) }.freeze)
591
+ end
592
+
593
+ # Return a {Vector} of the values which correspond to the `wanted` keys.
594
+ # If any of the `wanted` keys are not present in this `Hash`, raise `KeyError`
595
+ # exception.
596
+ #
597
+ # @example
598
+ # h = Immutable::Hash["A" => 1, "B" => 2, "C" => 3]
599
+ # h.fetch_values("C", "A") # => Immutable::Vector[3, 1]
600
+ # h.fetch_values("C", "Z") # => KeyError: key not found: "Z"
601
+ #
602
+ # @param wanted [Array] The keys to retrieve
603
+ # @return [Vector]
604
+ def fetch_values(*wanted)
605
+ array = wanted.map { |key| fetch(key) }
584
606
  Vector.new(array.freeze)
585
607
  end
586
608
 
609
+ # Return the value of successively indexing into a nested collection.
610
+ # If any of the keys is not present, return `nil`.
611
+ #
612
+ # @example
613
+ # h = Immutable::Hash[a: 9, b: Immutable::Hash[c: 'a', d: 4], e: nil]
614
+ # h.dig(:b, :c) # => "a"
615
+ # h.dig(:b, :f) # => nil
616
+ #
617
+ # @return [Object]
618
+ def dig(key, *rest)
619
+ value = self[key]
620
+ if rest.empty? || value.nil?
621
+ value
622
+ else
623
+ value.dig(*rest)
624
+ end
625
+ end
626
+
587
627
  # Return a new {Set} containing the keys from this `Hash`.
588
628
  #
589
629
  # @example
@@ -734,6 +774,50 @@ module Immutable
734
774
  self.eql?(other) || (other.respond_to?(:to_hash) && to_hash.eql?(other.to_hash))
735
775
  end
736
776
 
777
+ # Return true if this `Hash` is a proper superset of `other`, which means
778
+ # all `other`'s keys are contained in this `Hash` with identical
779
+ # values, and the two hashes are not identical.
780
+ #
781
+ # @param other [Immutable::Hash] The object to compare with
782
+ # @return [Boolean]
783
+ def >(other)
784
+ self != other && self >= other
785
+ end
786
+
787
+ # Return true if this `Hash` is a superset of `other`, which means all
788
+ # `other`'s keys are contained in this `Hash` with identical values.
789
+ #
790
+ # @param other [Immutable::Hash] The object to compare with
791
+ # @return [Boolean]
792
+ def >=(other)
793
+ other.each do |key, value|
794
+ if self[key] != value
795
+ return false
796
+ end
797
+ end
798
+ true
799
+ end
800
+
801
+ # Return true if this `Hash` is a proper subset of `other`, which means all
802
+ # its keys are contained in `other` with the identical values, and the two
803
+ # hashes are not identical.
804
+ #
805
+ # @param other [Immutable::Hash] The object to compare with
806
+ # @return [Boolean]
807
+ def <(other)
808
+ other > self
809
+ end
810
+
811
+ # Return true if this `Hash` is a subset of `other`, which means all its
812
+ # keys are contained in `other` with the identical values, and the two
813
+ # hashes are not identical.
814
+ #
815
+ # @param other [Immutable::Hash] The object to compare with
816
+ # @return [Boolean]
817
+ def <=(other)
818
+ other >= self
819
+ end
820
+
737
821
  # See `Object#hash`.
738
822
  # @return [Integer]
739
823
  def hash
@@ -801,6 +885,22 @@ module Immutable
801
885
  end
802
886
  alias :to_h :to_hash
803
887
 
888
+ # Return a `Proc` which accepts a key as an argument and returns the value.
889
+ # The `Proc` behaves like {#get} (when the key is missing, it returns nil or
890
+ # the result of the default proc).
891
+ #
892
+ # @example
893
+ # h = Immutable::Hash["A" => 1, "B" => 2, "C" => 3]
894
+ # h.to_proc.call("B")
895
+ # # => 2
896
+ # ["A", "C", "X"].map(&h) # The & is short for .to_proc in Ruby
897
+ # # => [1, 3, nil]
898
+ #
899
+ # @return [Proc]
900
+ def to_proc
901
+ lambda { |key| get(key) }
902
+ end
903
+
804
904
  # @return [::Hash]
805
905
  # @private
806
906
  def marshal_dump
@@ -1,6 +1,6 @@
1
1
  require "thread"
2
2
  require "set"
3
- require "concurrent/atomics"
3
+ require "concurrent"
4
4
 
5
5
  require "immutable/undefined"
6
6
  require "immutable/enumerable"
@@ -1311,23 +1311,23 @@ module Immutable
1311
1311
  def initialize(&block)
1312
1312
  @head = block # doubles as storage for block while yet unrealized
1313
1313
  @tail = nil
1314
- @atomic = Concurrent::Atomic.new(0) # haven't yet run block
1314
+ @atomic = Concurrent::Atom.new(0) # haven't yet run block
1315
1315
  @size = nil
1316
1316
  end
1317
1317
 
1318
1318
  def head
1319
- realize if @atomic.get != 2
1319
+ realize if @atomic.value != 2
1320
1320
  @head
1321
1321
  end
1322
1322
  alias :first :head
1323
1323
 
1324
1324
  def tail
1325
- realize if @atomic.get != 2
1325
+ realize if @atomic.value != 2
1326
1326
  @tail
1327
1327
  end
1328
1328
 
1329
1329
  def empty?
1330
- realize if @atomic.get != 2
1330
+ realize if @atomic.value != 2
1331
1331
  @size == 0
1332
1332
  end
1333
1333
 
@@ -1348,7 +1348,7 @@ module Immutable
1348
1348
  def realize
1349
1349
  while true
1350
1350
  # try to "claim" the right to run the block which realizes target
1351
- if @atomic.compare_and_swap(0,1) # full memory barrier here
1351
+ if @atomic.compare_and_set(0,1) # full memory barrier here
1352
1352
  begin
1353
1353
  list = @head.call
1354
1354
  if list.empty?
@@ -1357,22 +1357,22 @@ module Immutable
1357
1357
  @head, @tail = list.head, list.tail
1358
1358
  end
1359
1359
  rescue
1360
- @atomic.set(0)
1360
+ @atomic.reset(0)
1361
1361
  MUTEX.synchronize { QUEUE.broadcast }
1362
1362
  raise
1363
1363
  end
1364
- @atomic.set(2)
1364
+ @atomic.reset(2)
1365
1365
  MUTEX.synchronize { QUEUE.broadcast }
1366
1366
  return
1367
1367
  end
1368
1368
  # we failed to "claim" it, another thread must be running it
1369
- if @atomic.get == 1 # another thread is running the block
1369
+ if @atomic.value == 1 # another thread is running the block
1370
1370
  MUTEX.synchronize do
1371
1371
  # check value of @atomic again, in case another thread already changed it
1372
1372
  # *and* went past the call to QUEUE.broadcast before we got here
1373
- QUEUE.wait(MUTEX) if @atomic.get == 1
1373
+ QUEUE.wait(MUTEX) if @atomic.value == 1
1374
1374
  end
1375
- elsif @atomic.get == 2 # another thread finished the block
1375
+ elsif @atomic.value == 2 # another thread finished the block
1376
1376
  return
1377
1377
  end
1378
1378
  end
@@ -1591,5 +1591,5 @@ module Immutable
1591
1591
  true
1592
1592
  end
1593
1593
  end
1594
- end
1595
- end.freeze
1594
+ end.freeze
1595
+ end
@@ -5,6 +5,7 @@ require "immutable/vector"
5
5
  require "immutable/sorted_set"
6
6
  require "immutable/list"
7
7
  require "immutable/deque"
8
+ require "immutable/core_ext/struct"
8
9
 
9
10
  module Immutable
10
11
  class << self
@@ -30,6 +31,8 @@ module Immutable
30
31
  when ::Array
31
32
  res = obj.map { |element| from(element) }
32
33
  Immutable::Vector.new(res)
34
+ when ::Struct
35
+ from(obj.to_h)
33
36
  when ::SortedSet
34
37
  # This clause must go before ::Set clause, since ::SortedSet is a ::Set.
35
38
  res = obj.map { |element| from(element) }
@@ -279,6 +279,24 @@ module Immutable
279
279
  end
280
280
  end
281
281
 
282
+ # Return the value of successively indexing into a nested collection.
283
+ # If any of the keys is not present, return `nil`.
284
+ #
285
+ # @example
286
+ # v = Immutable::Vector[9, Immutable::Hash[c: 'a', d: 4]]
287
+ # v.dig(1, :c) # => "a"
288
+ # v.dig(1, :f) # => nil
289
+ #
290
+ # @return [Object]
291
+ def dig(key, *rest)
292
+ value = self[key]
293
+ if rest.empty? || value.nil?
294
+ value
295
+ else
296
+ value.dig(*rest)
297
+ end
298
+ end
299
+
282
300
  # Return specific objects from the `Vector`. All overloads return `nil` if
283
301
  # the starting index is out of range.
284
302
  #
@@ -536,17 +554,9 @@ module Immutable
536
554
  # @return [Vector]
537
555
  def uniq(&block)
538
556
  array = self.to_a
539
- if block_given?
540
- if array.frozen?
541
- self.class.new(array.uniq(&block).freeze)
542
- elsif array.uniq!(&block) # returns nil if no changes were made
543
- self.class.new(array.freeze)
544
- else
545
- self
546
- end
547
- elsif array.frozen?
548
- self.class.new(array.uniq.freeze)
549
- elsif array.uniq! # returns nil if no changes were made
557
+ if array.frozen?
558
+ self.class.new(array.uniq(&block).freeze)
559
+ elsif array.uniq!(&block) # returns nil if no changes were made
550
560
  self.class.new(array.freeze)
551
561
  else
552
562
  self
@@ -1,5 +1,5 @@
1
1
  module Immutable
2
2
  # Current released gem version. Note that master will often have the same
3
3
  # value as a release gem but with different code.
4
- VERSION = "0.0.1"
4
+ VERSION = "0.0.2"
5
5
  end
@@ -0,0 +1,34 @@
1
+ require "spec_helper"
2
+ require "immutable/hash"
3
+
4
+ describe Immutable::Hash do
5
+ describe "#dig" do
6
+ let(:h) { H[:a => 9, :b => H[:c => 'a', :d => 4], :e => nil] }
7
+ it "returns the value with one argument to dig" do
8
+ expect(h.dig(:a)).to eq(9)
9
+ end
10
+
11
+ it "returns the value in nested hashes" do
12
+ expect(h.dig(:b, :c)).to eq('a')
13
+ end
14
+
15
+ it "returns nil if the key is not present" do
16
+ expect(h.dig(:f, :foo)).to eq(nil)
17
+ end
18
+
19
+ it "returns nil if you dig out the end of the hash" do
20
+ expect(h.dig(:f, :foo, :bar)).to eq(nil)
21
+ end
22
+
23
+ # This is a bit different from Ruby's Hash; it raises TypeError for
24
+ # objects which don't respond to #dig
25
+ it "raises a NoMethodError if a value does not support #dig" do
26
+ expect { h.dig(:a, :foo) }.to raise_error(NoMethodError)
27
+ end
28
+
29
+ it "returns the correct value when there is a default proc" do
30
+ default_hash = H.new { |k, v| "#{k}-default" }
31
+ expect(default_hash.dig(:a)).to eq("a-default")
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,22 @@
1
+ require "spec_helper"
2
+ require "immutable/hash"
3
+
4
+ describe Immutable::Hash do
5
+ describe "#fetch_values" do
6
+ context "when the all the requested keys exist" do
7
+ it "returns a vector of values for the given keys" do
8
+ h = H[:a => 9, :b => 'a', :c => -10, :d => nil]
9
+ h.fetch_values.should be_kind_of(Immutable::Vector)
10
+ h.fetch_values.should eql(V.empty)
11
+ h.fetch_values(:a, :d, :b).should be_kind_of(Immutable::Vector)
12
+ h.fetch_values(:a, :d, :b).should eql(V[9, nil, 'a'])
13
+ end
14
+ end
15
+
16
+ context "when the key does not exist" do
17
+ it "raises a KeyError" do
18
+ -> { H["A" => "aye", "C" => "Cee"].fetch_values("A", "B") }.should raise_error(KeyError)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -1,6 +1,15 @@
1
1
  require "spec_helper"
2
2
 
3
3
  describe Immutable::Hash do
4
+ describe "#[]=" do
5
+ let(:hash) { H["A" => "aye", "B" => "bee", "C" => "see"] }
6
+
7
+ it 'raises error pointing to #put' do
8
+ expect { hash[:A] = 'aye' }
9
+ .to raise_error(NoMethodError, /Immutable::Hash.*`put'/)
10
+ end
11
+ end
12
+
4
13
  describe "#put" do
5
14
  let(:hash) { H["A" => "aye", "B" => "bee", "C" => "see"] }
6
15
 
@@ -0,0 +1,42 @@
1
+ require "spec_helper"
2
+ require "immutable/hash"
3
+
4
+ describe Immutable::Hash do
5
+ describe "#<=" do
6
+ [
7
+ [{}, {}, true],
8
+ [{"A" => 1}, {}, false],
9
+ [{}, {"A" => 1}, true],
10
+ [{"A" => 1}, {"A" => 1}, true],
11
+ [{"A" => 1}, {"A" => 2}, false],
12
+ [{"B" => 2}, {"A" => 1, "B" => 2, "C" => 3}, true],
13
+ [{"A" => 1, "B" => 2, "C" => 3}, {"B" => 2}, false],
14
+ [{"B" => 0}, {"A" => 1, "B" => 2, "C" => 3}, false],
15
+ ].each do |a, b, expected|
16
+ describe "for #{a.inspect} and #{b.inspect}" do
17
+ it "returns #{expected}" do
18
+ expect(H[a] <= H[b]).to eq(expected)
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ describe "#<" do
25
+ [
26
+ [{}, {}, false],
27
+ [{"A" => 1}, {}, false],
28
+ [{}, {"A" => 1}, true],
29
+ [{"A" => 1}, {"A" => 1}, false],
30
+ [{"A" => 1}, {"A" => 2}, false],
31
+ [{"B" => 2}, {"A" => 1, "B" => 2, "C" => 3}, true],
32
+ [{"A" => 1, "B" => 2, "C" => 3}, {"B" => 2}, false],
33
+ [{"B" => 0}, {"A" => 1, "B" => 2, "C" => 3}, false],
34
+ ].each do |a, b, expected|
35
+ describe "for #{a.inspect} and #{b.inspect}" do
36
+ it "returns #{expected}" do
37
+ expect(H[a] < H[b]).to eq(expected)
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end