0

So I have various hashes which do not always have the same key/value pairs in them. What I want to do is to be able to merge the hashes, but to add an empty key/value pairs if they don't already exist in that hash, but do in others. It's hard to explain but this might explain it better:

t1 = Merger.new({"a" => "1"})
puts t1.merge({:b => 2})
# => {"a" => "1", :b => 2}

t2 = Merger.new({"a" => "1", :b => 2})
puts t2.merge({:c => "3"})
# => {"a" => "1", :b => 2, :c => "3"}

t3 = Merger.new({"a" => "1", "b" => 2})
puts t3.merge
# => {"a" => "1", "b" => 2}

t4 = Merger.new
puts t4.merge({:a => 1})
# => {:a => 1}

t5 = Merger.new
puts t4.merge
# => {}

Merger class implementation:

class Merger
  alias AnyHash = Hash(Symbol | String, Int32 | String) |
                  Hash(Symbol, Int32 | String) |
                  Hash(String, Int32 | String) |
                  Hash(String, String) |
                  Hash(String, Int32) |
                  Hash(Symbol, String) |
                  Hash(Symbol, Int32)

  def initialize(params : AnyHash? = nil)
    @params = params
  end

  def merge(other = {} of String => String)
    @params.try do |params|
      other = params.merge(other)
    end
    return other
  end
end

https://play.crystal-lang.org/#/r/3oeh

Literally, I should create union type with all possible combinations of key/value pairs. Otherwise, a compile-time error is given.

Is there a more elegant way to make it work?

Anton Maminov
  • 463
  • 1
  • 6
  • 11

1 Answers1

6

Hash#merge already figures out the correct generic type arguments for you.

The only issue is with storing that in an instance variable, if I understand you correctly. This can be done making Merger a generic class where the type is automatically inferred from the constructor argument:

class Merger(T)
  def self.new
    new({} of String => String)
  end

  def initialize(@params : T)
  end

  def merge(other = {} of String => String)
    @params.merge(other)
  end
end
Johannes Müller
  • 5,581
  • 1
  • 11
  • 25
  • It works fine for 1 argument. But I can't make it work with few arguments. For example: `Merger.new(true, {:a => 1}).merge({"b" => "2"})` Where the first argument required and must be specified on initializing, – Anton Maminov Mar 07 '18 at 10:59