Crystal: "Object as generic type argument" workaround?

Created on 30 May 2019  路  5Comments  路  Source: crystal-lang/crystal

I am new to Crystal. I am using my Java experiences to learn this language, and stumbled upon something.

In Java, we can write something like this

class MyClass<T> {
    final <R> MyClass<R> myMethod(Function<T, R> someFunc) {
        // some stuff here
    }
}

which allows one to return an instance of MyClass that can have a different generic type than that of the instance, processed through someFunc. It is inferred like this:

MyClass<SomeClassA> mc = new MyClass<SomeClassB>.someMethod(b -> castToA(b));

in Crystal, we can't have something like this:

class MyClass(T)
     def myMethod(someFunc : Proc(T, R)) : MyClass(R)
         # some stuff here
     end
end

I checked out the Crystal API to check if there is a base class for all kinds of types, classes and structs, and found Object. However, using it as a generic type nor using it like Proc(T, Object) throws an error.

Is there a workaround for this? Take note that the method must not return or explicitly define an specific type for the generic argument

question

Most helpful comment

All 5 comments

I think just omitting the argument type and return type will get you where you want.

For example:

class MyClass(T)
  def initialize(@value : T)
  end

  def myMethod(someFunc)
    MyClass.new(someFunc.call(@value))
  end
end

c1 = MyClass.new(1)
c2 = c1.myMethod(->(x : Int32) { x.to_s })

p! c2, typeof(c1), typeof(c2)

Or if you want type signatures:

class MyClass(T)
  def initialize(@value : T)
  end

  def myMethod(someFunc : Proc(T, R)) : MyClass(R) forall R
    MyClass(R).new(someFunc.call(@value))
  end
end

c1 = MyClass(Int32).new(1)
c2 = c1.myMethod(->(x : Int32) { x.to_s })

p! c2, typeof(c1), typeof(c2)

Maybe you are missing forall R? (which is equivalent to Java's <R> in the type signature)

Then for methods that just receive a proc it's usual to just pass a block:

class MyClass(T)
  def initialize(@value : T)
  end

  def myMethod(&block : T -> R) : MyClass(R) forall R
    MyClass(R).new(yield @value)
  end
end

c1 = MyClass(Int32).new(1)
c2 = c1.myMethod { |x| x.to_s }

p! c2, typeof(c1), typeof(c2)

And as usual there's no need for so many type annotations:

class MyClass(T)
  def initialize(@value : T)
  end

  def myMethod
    MyClass.new(yield @value)
  end
end

c1 = MyClass.new(1)
c2 = c1.myMethod { |x| x.to_s }

p! c2, typeof(c1), typeof(c2)

Thanks for the quick reply!

I liked the example with type signatures, it is closer to what I need

I never knew about the forall keyword. Is there a guide for it in the official docs?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Papierkorb picture Papierkorb  路  3Comments

nabeelomer picture nabeelomer  路  3Comments

grosser picture grosser  路  3Comments

ArthurZ picture ArthurZ  路  3Comments

asterite picture asterite  路  3Comments