This should work:
module Moo
end
class Foo
include Moo
end
class Bar
include Moo
end
a = [] of Moo.class
a << Foo
a << Bar
While I really like the expressibility this adds, "[] of Moo.class" looks a quite weird at first glance, as I'd assume that returns Module, meaning that a would be a Array of arbitrary modules.
Speaking of which, I can find no api docs for modules.
@yxhuvud you can interpret Moo.class to be a class that implements Moo interface, similar to what you do with Moo by itself:
ary = [] of Moo
Is interpreted as an array of Moo instances (or any object instance that implements Moo interface).
@yxhuvud re: api docs for Module: the only docs shown for class are due included/injected methods (serialization, comparison, etc).
You can find Module and Class documentation in the docs:
@luislavena But then #class for modules would do something utterly different compared to #class everywhere else. I object to the naming in the API, not the functionality of it. Moo.includer is probably what I'd would have choosen, as that also gives an obvious Moo.extender for the classes that has been extended by the module.
As for interpretations of implementing interfaces, would this allow for duck typed classes that have methods that match the API without including it which is what your suggested interpretation boils down to? I assumed that it didn't, but if it does, then I'd instead suggest a = [] of Moo.interface. Of course, the alternatives between includers and duck typed interfaces are not exclusive - both are possible at the same time.
As for Class API documentation, that exist at https://crystal-lang.org/api/0.20.3/Class.html. I guess the question can be boiled down into: Are modules first class objects that can be passed around, the way classes are? Sure, you can't include them dynamically the way you can instantiate classes, but they start getting methods and used for instantiating stuff like generics like Arrays, then they probably should be, and also have their own API docs like everything else.
@yxhuvud It already works like that, check the docs, section "Classes as restrictions"
@asterite: how about the extend case:
module Moo
end
class Foo
extend Moo
end
class Bar
extend Moo
end
a = [] of Moo.???
a << Foo
a << Bar
@yxhuvud That already works, if you do [] of Moo, because when you extend, you make the "metaclass" inherit the module.
I admit it's a bit confusing, but classes and metaclass are confusing.
I think I just got more confused, because for classes [] of Moo would mean [] is an array of instances of Moo, and I'd have expected both a and b to work:
module Moo
end
class Foo
include Moo
end
a = [] of Foo
a << Foo.new
b = [] of Moo
b << Foo.new
That is, the elements of the array have the instance methods of the class/module.
(btw, you can use triple backticks followed by "cr" to get syntax coloring for crystal)
Yes, the above snippet works :-)
When you include Moo in Foo, instances of Foo are "instances" of Moo. So it works.
When you extend Moo in Foo, the class Foo is an "instance" of Moo.
When you do [] of Foo.class it means "array of class Foo or subclasses of Foo" (but not instances, just classes). What's missing is the same concept for "array of types (not instances) that include a module".
@asterite: Glad to hear that. Then I wasn't as confused as I thought. Then I repeat my question about the extend case, ie array of types that extend from a module to use your phrasing.
What question? :-)
It works:
module Moo
end
class Foo
extend Moo
end
class Bar
extend Moo
end
a = [] of Moo
a << Foo
a << Bar
But the use case is this:
module Action
abstract def exec
end
class Jump
include Action
def exec
puts "Jumping!"
end
end
class Run
include Action
def exec
puts "Running!"
end
end
Now if you want an array of types that include Action, I can't do it. I might want to do that because I will create different instances, maybe pass them something in the constructor.
Ok, confusion gone after trying around examples. The case of arrays of instances from classes that extends from a module is missing, but perhaps that is a lot less necessary to have as it usually is the api of the generic content and not the api of the class of the generic argument that is relevant. Sorry for spamming your notification inbox :)
Related: https://forum.crystal-lang.org/t/array-co-something-inconsistency/238. Either @asterite example or [] of Moo.module should work in the end.
In the same regard, this currently does not work:
module TestInterface
end
class Test
include TestInterface
def build_tests : Array(TestInterface)
[Test.new]
end
end
It fails at compile time with Error: method must return Array(TestInterface) but it is returning Array(Test).
@pyrsmk [Test.new] of TestInterface
Return types are checks, they don't give types to expressions.
So bad, this is a strong limitation with the interface paradigm. But I can understand this is a tricky subject.
Well, actually, in this case one would expect [Test.new] to kind of match Array(TestInterface) but then there's the covariance stuff so it's not clear what should be done here.
@asterite Maybe a workaround would be to add a way to say that value must be an Array of descendants of Type. By explicitly defining what we want, it can do the trick (maybe?).
Yes, that's essentially what covariance and conteavariance annotation are for. See for example C# who has them (I think kotlin too). However, implementing something like that in Crystal is not only a huge task, it might also be impossible to do because type annotations are optional.
I understand. So a better approach would be to rethink how I'm handling my return types with arrays and interfaces. ~For example, having a SomethingCollection class/struct that accepts any SomethingInterface would do the job.~
Doesn't work either since I still rely on an Array of SomethingInterface 馃槄
What you want to do is already possible. Maybe show us some code that's not working?
Note that this issue is about arrays of module types, not module instances. Your case should work. That the return type doesn't match the array type is a separate issue.
Note that this issue is about arrays of module types, not module instances.
Yes, I noticed the difference from my case afterward 馃槄
That the return type doesn't match the array type is a separate issue.
I agree, but it's strongly linked to my case anyway IMHO. Take this NodeCollection example:
module NodeInterface
end
struct Node
include NodeInterface
end
module NodeCollectionInterface
include Enumerable(NodeInterface)
abstract def with(node : NodeInterface) : NodeCollectionInterface
end
struct NodeCollection
include NodeCollectionInterface
@nodes = [] of NodeInterface
def initialize(@nodes : Array(NodeInterface)); end
def each
@nodes.each { |node| yield node }
end
def with(node : NodeInterface) : self
self.class.new(@nodes.concat([node]))
end
end
NodeCollection.new([Node.new, Node.new]).with(Node.new)
As expected, the compile fails on the initialize. And I can't see a way of handling the input otherwise.
@pyrsmk Replace your last line with this:
NodeCollection.new([Node.new, Node.new] of NodeInterface).with(Node.new)
I'm actually not sure this issue is valid, the original snippet doesn't make sense, now that I look back at it.
Woh! I wasn't aware that I could do that... So it seems there wasn't really no issue at all... 馃槄 Thanks!
What doesn't make sense in my snippet?
The type of an array is deduced from its elements. When you write [Node.new] the type becomes Array(Node). That's not compatible with Array(NodeInterface) because you can only add Node to that array, not any kind of NodeInterface. When you write [Node.new] of NodeInterface you are saying that the array is of NodeInterface, not just Node.
This is the one thing that bites Crystal developers the most. I don't know how we can improve the situation.
That completely makes sense now that you explained it, and to be honest this is one thing I didn't remember after reading the guide.
But the explanation is right here, at the bottom of the inheritance chapter... So I feel silly.
Maybe it needs more visibility in the summary, directly at the top with other edge cases.
This is the one thing that bites Crystal developers the most. I don't know how we can improve the situation.
Interesting, I always thought this was very clear.
(I mean, it is only expected that if a person writes Smth.new, Crystal can't auto-detect this as being anything other than type Smth. If a user wants to change or extend that, they need to be specify it manually like with that of NodeInterface)