Search code examples
crystal-lang

Hash of (Array of Tuples) causing compile/runtime error "index out of bounds for tuple {(String, String -> Void)}"


I am trying to make a Hash of Arrays with tuples in them. This is causing a weird issue which I suspect is a compiler bug, but I am not 100% sure.

The syntax I am using:

class SomeClass
  @@weird_object = Hash(String, Array(Tuple(String, String -> ))).new {|h, k| h[k] = [] of Tuple(String, String -> ) }

  #...
  def self.some_method
    @@weird_object.each do |tuple|
      pp tuple[0]
      pp tuple[1] #=> This causes compile error "index out of bounds for tuple {(String, String -> Void)}"
    end

    @@weird_object.each do |the_sting, callback|
      #this causes a "Nil trace:" for the callback parameter
    end
#...
end

It looks like this has become a tuple of a (String, String -> void) object, and not String, String -> void. This error shows up when I run crystal spec but not when I run crystal build ./src/main.cr.

Is this a compiler/runtime error, or have I messed up the syntax?


Crystal 0.8.0 [e363b63] (Sat Sep 19 12:00:17 UTC 2015)


Solution

  • Your actual code has:

    Hash(String, Array({String, String -> })).new
    

    This tuple is the same as

    {(String, String ->)}
    

    So you need parens to disambiguate

    Hash(String, Array({String, (String -> )})).new
    

    Your actual code also has a problem with Void appearing everywhere, I'm not sure why, but a few casts can workaround it. Here's the full diff to make it work

    diff --git a/src/untangle/aggregator.cr b/src/untangle/aggregator.cr
    index 8c49681..7553eb2 100644
    --- a/src/untangle/aggregator.cr
    +++ b/src/untangle/aggregator.cr
    @@ -3,7 +3,7 @@ class Aggregator
        @@subscribers_all = Array(String, String ->).new
        @@responders = Hash(String, (String ->)).new
        #@@reroutes = {} of String => Proc(String)
    -   @@reroutes = Hash(String, Array({String, String -> })).new {|h, k| h[k] = Array({String, String -> }).new }
    +   @@reroutes = Hash(String, Array({String, (String -> )})).new {|h, k| h[k] = Array({String, (String -> )}).new }
    
        def self.subscribe (message_type, &callback : String ->)
            @@subscribers[message_type] << callback
    @@ -35,11 +35,11 @@ class Aggregator
                spawn do
                    message_type = ch.receive
                    data = ch.receive
    -               callback.call(message_type, data)
    +               callback.call(message_type, data as String)
                end
    
                ch.send(message_type)
    -           ch.send(data)
    +           ch.send(data as String)
            end
    
            @@subscribers[message_type].each do |callback|
    @@ -48,14 +48,14 @@ class Aggregator
                spawn do
                    callback.call(ch.receive)
                end
    -           ch.send(data)
    +           ch.send(data as String)
            end
    
            @@reroutes[message_type].each do |tuple| #|to_type, callback|
                # puts "!!!!!!!!!!!!!!!!!!!"
                # pp tuple
                # puts "!!!!!!!!!!!!!!!!!!!"
    -           Aggregator.publish(tuple[0], tuple[1].call(data))
    +           Aggregator.publish(tuple[0], tuple[1].call(data as String))
            end
        end
        def self.request(message_type, arguments)
    

    Please also give http://crystal-lang.org/docs/conventions/coding_style.html a read if you plan adoption of your library in the Crystal community.