immutable-ruby 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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