0

The following code fails to compile with error:

type must be Tuple(Thing::Ish, Slice(UInt8)), not Tuple(Array(Array(Thing::Ish) | UInt8) | UInt8, Slice(UInt8))

These two types seem equivalent to me... and adding an .as(Ish) in the right place works... what am I missing? Why do these types not unify?

module Thing
    alias Ish = UInt8 | Array(Ish)

    def self.decode(bytes : Bytes) : {Ish, Bytes}
        case bytes[0]
            when 0x00..0x17
                {bytes[0], bytes + 1}
            when 0x18
                MultiItemDecoder.new(0x80, ->(x: Bytes) { Thing.decode(x) }).decode(bytes)
            else
                raise "unknown"
        end
    end

    class MultiItemDecoder(T)
        def initialize(@base : UInt8, @item_decoder : Bytes -> {T, Bytes})
        end

        def decode(bytes): {Array(T), Bytes}
            decode_some(bytes + 1, bytes[0])
        end

        def decode_some(bytes, n)
            items = n.times.map do
                item, bytes = @item_decoder.call(bytes)
                item
            end
            {items.to_a, bytes}
        end
    end
end
singpolyma
  • 10,999
  • 5
  • 47
  • 71

2 Answers2

1

This works:

module Thing
  alias Ish = UInt8 | Array(Ish)

  def self.decode(bytes : Bytes) : {Ish, Bytes}
    case bytes[0]
    when 0x00..0x17
      {bytes[0], bytes + 1}
    when 0x18
      MultiItemDecoder.new(0x80, ->(x : Bytes) { Thing.decode(x) }).decode(bytes)
    else
      raise "unknown"
    end
  end

  class MultiItemDecoder(T)
    def initialize(@base : UInt8, @item_decoder : Bytes -> {T, Bytes})
    end

    def decode(bytes) : {Ish, Bytes}
      decode_some(bytes + 1, bytes[0])
    end

    def decode_some(bytes, n)
      items = n.times.map do
        item, bytes = @item_decoder.call(bytes)
        item.as(Ish)
      end
      {items.to_a.as(Ish), bytes}
    end
  end
end

Thing.decode(Bytes[1, 2, 3])

The thing is that Array(Array(Ish)) is not an Array(Ish), because for that it must be Array(Array(Ish) | UInt8) (note that it's an array of a union).

This all boils down to how things are represented in memory.

My advice is to avoid using recursive aliases. They are not intuitive and we might eventually remove them from the language.

asterite
  • 2,906
  • 1
  • 14
  • 14
0

Without seeing code to make this actually complile and get the error, self.decode wants to return a {Ish, Bytes}, which it does in when 0x00..0x17. But in 0x18 it is going to return an {Array(Ish), Bytes}. This will expand to {Array(UInt8 | Array(Ish)), Bytes} (recursively)

Samual
  • 188
  • 1
  • 7
  • Yes, that is precisely the nature of the question. Why is `Array(UInt8 | Array(Ish))` not unifying with `Ish` since `Array(UInt8 | Array(Ish))` === `Array(Ish)` < Ish ? – singpolyma May 07 '19 at 23:49
  • I think this is just a problem with type inference not finding this due to the use of the generic. It is just a good use case for `.as(Ish)` as type inference is done at compile time, apparently not checking for aliases for generics. It's true that it will never fail – Samual May 08 '19 at 19:05