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
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?
Yes, the last section here explains forall: https://crystal-lang.org/reference/syntax_and_semantics/type_restrictions.html.
Most helpful comment
Yes, the last section here explains
forall: https://crystal-lang.org/reference/syntax_and_semantics/type_restrictions.html.