How to access methods in elements of Array<T> inside a class implementing a generic interface<T>

I have a class CollectionParentBase that implements an Interface CollectionParentI<T> and which defines a toString() method. I then have an array of objects of type T, all of which have CollectionParentBase as an ancestor. In the toString() of CollectionParentBase I try to go through all children and concatenate the result of their toString(). However, that doesn’t work. The compiler says that CollectionParentBase.T has no field toString.

Here’s all the code involved.

class CollectionParentBase<T, U> extends CollectionBase implements CollectionParentI<T, U>
{

	public var parent: T;

	@:isVar public var children (get, null): Array<U>;
	/**
	* Returns the children of this CollectionBase.
  **/
	public function get_children (): Array<U>
	{
		if (children == null) children = new Array<U>();
		return children;
	}

	private function new (name: String)
	{
		super(name);
	}

	public function toString(): String
	{
		var ret: String = "";
		for (child in children)
		{
			//var childC: CollectionParentI<U> = cast(child, CollectionParentI<U>);
			//The above cast fails saying that the argument should be dynamic.
			//If I try to remove the <U>, then it fails, of course, saying that there's insufficient type parameters.
			ret += child.toString(); //no field toString
		}
		return ret;
	}

}

interface CollectionParentI<T, U>
{

	public var parent (default, default): T;

	public var children (get, null): Array<U>;

	public function toString(): String;

}

I tried using @:generic but I can’t seem to get my head around how all that works. I think the manual could use more examples, or links to the Haxe Code Cookbook (which in turn could use more examples :slight_smile: ). Which brings me to…

Why the need for the explicit toString() call anyway? That should happen automatically when concatenating strings. Or does that also not work for you in this case?

Btw, minor code style thing: set_ and get_ methods are usually declared private, since you normally don’t want to call them directly / expose them as the public API of something (a private setter doesn’t mean that set access to the property is private).

1 Like

Thanks! :). Regarding toString(), for some reason I tried to do that with an object some time ago and it didn’t work. It was probably because I didn’t override it in the class, because now, once I removed the toString now, it worked :smiley:

I’m still curious about the answer: how could I achieve calling a function in U, as a member of Array<U>? All objects in that children Array are inheriting from CollectionParentBase, which in turn implements the Interface defining toString. What am I doing wrong?

Maybe I’m missing some basic stuff due to the thousand things swimming in my head and/or the hectic program I have today and/or being tired after several 6-hours nights (thanks to the joy of Haxing :smiley: ) . Or I’m missing some advanced stuff due to being rusty :slight_smile:

I simplified the code somewhat, bundling the two interfaces into the same one. Also, I actually realized that I do need to create a custom toString method which recursively iterates through a hierarchy of classes. The CollectionParentI<T, U> interface allows me to chain classes in a hierarchy, using parents which own arrays of children. So, in the base class CollectionParentBase I want to create a toString that recurses through all children. And I can’t do that if I can’t type the elements. I even tried the code below, trying to provide a parent as an argument of type T, but I can’t get any code completion or compiling to work.

	public function toStringFullDump(parent: T = null): String
	{
		parent.toString(); //Error:(42, 3) Null<CollectionParentBase.T> has no field toString
	}

Anybody? :slight_smile:

I think that … = null … is probably wrong in the above code-snippet. (Does it even make sense for this to be “nullable?”)

I think that I would be using interfaces here. You know that everything in your collection needs to implement an interface that provides toString() capability.

When you use things like <T> you are by design saying nothing about what the type can actually be. But in this case you do have a requirement: “an object that does not implement toString() must not be here.” Therefore, you need to say that to Haxe using interfaces and types, so that it can properly detect at compile time when you attempt to add something to a collection which the associated code won’t be able to use.

A fully-dynamic language like JavaScript can figure out these things at runtime (usually just spitting out a silent error-message if they’re not there), but Haxe doesn’t work that way. Haxe can catch your mistake and keep it from happening in the first place.

1 Like

As far as getting code completion to work in IntelliJ IDEA (assuming you’re still using that), there is an option to use the compiler’s completion functionality. You can find it in the menu File → Project Structure → SDKs (left panel) → (middle panel) → Use Compiler (checkbox). This uses the compiler for completions in addition to the Haxe plugin’s internal completion engine (which doesn’t know as much about typing and knows nothing about macros).

1 Like

I think, what you are looking for is “Constraints” concept: Constraints - Haxe - The Cross-platform Toolkit

class CollectionParentBase<T, U:{function toString():String;}> extends ...

Example: Try Haxe !

3 Likes

@sundialservices Yes, I wanted that to be nullable so that if it is passed as null then a recursive search would start from the very first parent. The code is not complete as I was struggling to even get it to compile :slight_smile:

Regarding your interfaces comment: check CollectionParentI in my original post. All the types that are coming in there are an Interface that implements toString() :slight_smile: Which is why I sort of “wanted” this to work… somehow.

@ebishton I’m still using IJ, especially which such superstar support :wink: . I tried doing what you suggested, but I think IJ doesn’t like DropBox. It sometimes has a file access issue, due to DropBox trying to sync the file. It never happens if I don’t have the “use compiler” checked. I guess the compiler flag wants a sort of file access that is a bit too demanding when DropBox operates on a file. It works fine if I shut down DropBox. However, after applying the changes suggested by Aleksandr, I get no code completion for that function. See the try.haxe sample at the end of my post @ line 63. Unfortunately try.haxe can’t run my code, but it runs fine on my machine when compiled into a Main.hx

@RealyUniqueName Aleksandr you saved my sorry arse mate :smiley:. Thank you for that AMAZING piece of information. I knew Haxe can do this :smiley: Constraints! Wow, awesome. I like this language more and more :smiley: . I wonder how C# or C++ would handle what I just tried to do. I’m bookmarking this topic to check that when I have more time.

I applied your teachings here, also testing out to see if I can also make it work when passing a type parameter into the toStringDump function. Sure enough, it works! :wink: Well,works on my machine, because it breaks try.haxe :smiley:

P.S.: the use of parent in toStringDump is purely an example. I currently do not intend to use the parent at all as a parameter in that function.

Yes, @ReallyUniqueName nailed it. Flood it with up-votes.

1 Like

The compiler vs. DropBox issue is a new one to me. If you’d like to file a bug for that, feel free! It’s likely a combination of factors contributing to the issue (some of which are under the plugin’s control and most of which are not).

As far as code completion for the example you have above, it is likely because the plugin isn’t quite smart enough to do structural subtyping. Combine that with a number of known bugs when trying to resolve chained generics (classes with generics subclassing classes with generics), and you’re tromping on edge cases. :wink: Feel free to file a bug about that, too. I’ve already copied your code into my sample set.

1 Like