Comparable return type conflict?

Hi all,
I’m getting stuck on a very stupid thing - but I’m not really finding a way around it.
Suppose I have this very simple class

class Container(T)

  @comparison_function : (T,T) -> Int32

  # in future, pass a comparison function to determine how to manage
  #def initialize(&comparison_function : (T, T) -> Int32)
  #   @comparison_function = comparison_function
  #end

  def initialize
    {% raise "TypeError: #{@type.stringify} only accepts comparable entities or a sorting function." unless T <= Comparable %}
    @comparison_function = ->(a : T, b : T) { a <=> b }
  end
end

Basically, you create a Container and then the comparison function is set automatically to the <=> operator defined in container.

Except that it doesn’t compile when I try to do something like

Container(String).new
Container(Float64).new

Compile error is:
Error: instance variable '@comparison_function' of Container(String) must be Proc(String, String, (Int32 | Nil)), not Proc(String, String, Int32)

But if I remove the nullable in the definition of the comparison function I get:
Error: instance variable '@comparison_function' of Container(Float64) must be Proc(Float64, Float64, Int32), not Proc(Float64, Float64, (Int32 | Nil))

Which makes kind of sense looking at the source code (comparison with NaN should return nil) but it actually freezes me in the loop. :confused:

Any suggestions on how to proceed?
The only way around it that I can think of is changing the implementation from
@comparison_function = ->(a : T, b : T) { a <=> b }
to
@comparison_function = ->(a : T, b : T) { a <=> b || Int32::Max}

But it’s really a meh-ing solution…
Any better options?

The way it’s handled for array sorting (which is implemented in Slice since Array converts itself to a slice to be sorted) is to raise if the result of the comparison is nil, which removes it from the return type union. Your default block could be implemented similarly.

1 Like