Jump to page: 1 2
Thread overview
Can a D library have some types determined by the client program?
Mar 08
cc
Mar 10
cc
March 07

In a source library written in D, is it possible to have some objects, variables, pointers etc which are determined by the program using the library?

An example of where this would be useful is in the library I am currently writing. I have a class called Map, which holds an array of objects of the Tile class. A program using my library can inherit the Map class if they want to add more functions and variables, but what if they want to add more to the Tile class? Even if they make a derived class of Tile, they can't override the use of the base Tile class in the Map class.

In cases like this, it would be useful to allow the client program to determine the type for some objects. For it to work with types defined in the client software code, the client software will also need a way to give it access to a module of the client software.

I know that D has something called compile-time function evaluation, but I don't know the full capabilities.

March 08
There are two ways to do this.

1. Use templates. https://tour.dlang.org/tour/en/basics/templates
2. Use a factory function. https://tour.dlang.org/tour/en/basics/delegates

```d
class Map(ATile : Tile) {
	ATile[] tiles;
}
```

Or:

```d
class Map {
	Tile[] tiles;
	Tile delegate(string params) factory;

	this() {
        factory = (string params) {
			return new Tile;
		};

		foreach(i; 0 .. 10) {
			tiles ~= factory("");
		}
	}
}
```

The factory delegate is a very rough way to do it, there are other ways to describe it including an overridable method.

The design pattern: https://en.wikipedia.org/wiki/Factory_method_pattern
March 08
On Thursday, 7 March 2024 at 22:18:40 UTC, Richard (Rikki) Andrew Cattermole wrote:
> There are two ways to do this.
>
> 1. Use templates. https://tour.dlang.org/tour/en/basics/templates
> 2. Use a factory function. https://tour.dlang.org/tour/en/basics/delegates
>
> ```d
> class Map(ATile : Tile) {
> 	ATile[] tiles;
> }
> ```

Thank you. Is this first example you gave the template? Is the syntax `(ATile : Tile)` saying that ATile must be a derived class of Tile? If this isn't worse in any way than your second example, then I don't know why I wouldn't choose this one.

I suppose that if I do this, then the derived class `Mission` would be declared like `class Mission : Map(GridTile)`, right?

When I tried adding that parameter to the Map class, on attempting to build it it complained that references to Map in other classes were incomplete, as they didn't include a parameter. I suppose I must make my other classes templates too.

Something strange that I just realized is that (without doing any of the changes you suggested to me), I have a reference to a Map object as one of the class variables in Unit, yet it has allowed me to place a Mission object in it's place. It no longer allows me if I change it to a pointer. Why is it sometimes possible to put a derived class in a place meant for the class it inherits from?


March 08
On 08/03/2024 4:09 PM, Liam McGillivray wrote:
> On Thursday, 7 March 2024 at 22:18:40 UTC, Richard (Rikki) Andrew Cattermole wrote:
>> There are two ways to do this.
>>
>> 1. Use templates. https://tour.dlang.org/tour/en/basics/templates
>> 2. Use a factory function. https://tour.dlang.org/tour/en/basics/delegates
>>
>> ```d
>> class Map(ATile : Tile) {
>>     ATile[] tiles;
>> }
>> ```
> 
> Thank you. Is this first example you gave the template? Is the syntax `(ATile : Tile)` saying that ATile must be a derived class of Tile? If this isn't worse in any way than your second example, then I don't know why I wouldn't choose this one.

Typically in D we use templates quite heavily, but what you are wanting is probably more familiar to you via the OOP method with a factory of some kind.

> I suppose that if I do this, then the derived class `Mission` would be declared like `class Mission : Map(GridTile)`, right?

``class Mission : Map!GridTile`` but right idea.

> When I tried adding that parameter to the Map class, on attempting to build it it complained that references to Map in other classes were incomplete, as they didn't include a parameter. I suppose I must make my other classes templates too.
> 
> Something strange that I just realized is that (without doing any of the changes you suggested to me), I have a reference to a Map object as one of the class variables in Unit, yet it has allowed me to place a Mission object in it's place. It no longer allows me if I change it to a pointer. Why is it sometimes possible to put a derived class in a place meant for the class it inherits from?

That should always be allowed.
March 08
On Friday, 8 March 2024 at 03:19:59 UTC, Richard (Rikki) Andrew Cattermole wrote:
>
> On 08/03/2024 4:09 PM, Liam McGillivray wrote:
>> 
>> Thank you. Is this first example you gave the template? Is the syntax `(ATile : Tile)` saying that ATile must be a derived class of Tile? If this isn't worse in any way than your second example, then I don't know why I wouldn't choose this one.
>
> Typically in D we use templates quite heavily, but what you are wanting is probably more familiar to you via the OOP method with a factory of some kind.
Nope, but thank you. I am not a very experienced programmer. The most complex thing I've ever done previous to this was my work on [Condorcet](https://github.com/julien-boudry/Condorcet) which is in PHP. I might have encountered something about factory methods, but I don't remember. I have some C++ experience, but I haven't been very successful with it. What I have so far in the game I'm making is the most complex program I have ever written.

>> I suppose that if I do this, then the derived class `Mission` would be declared like `class Mission : Map(GridTile)`, right?
>
> ``class Mission : Map!GridTile`` but right idea.
>
>> When I tried adding that parameter to the Map class, on attempting to build it it complained that references to Map in other classes were incomplete, as they didn't include a parameter. I suppose I must make my other classes templates too.
I have an update on this, after taking another go at it.

A problem I have is that the 3 classes Map, Tile, and Unit reference each-other. If I turn Map into a template, than it complains about the member variable of Unit declared as `Map map;` without arguments. I change this line to `Map!TileType map;` but this requires that Unit is also turned into a template. After changing `class Unit` to `class Unit (TileType), it complains about the line `Unit* occupant;` in Tile. I try turning Tile into a template with the `TileType` parameter, which means that the class inheriting Tile will need to use itself as a parameter. Surprisingly, I have done this and it hasn't taken issue (so far). But now that I have turned Tile into a template, it complains about the declaration of Map being `class Map (TileType : Tile)`, as `Tile` is no longer a class but a template.

Here are some of the build errors:
```
    Starting Performing "debug" build using /usr/bin/dmd for x86_64.
  Up-to-date bindbc-freetype 1.1.1: target for configuration [staticBC] is up to date.
  Up-to-date fluid 0.6.3: target for configuration [default] is up to date.
    Building open_emblem_raylib ~master: building configuration [application]
../source/map.d(185,13): Error: template class `unit.Unit(TileType)` is used as a type without instantiation; to instantiate it use `Unit!(arguments)`
../source/map.d(21,19): Error: template class `unit.Unit(TileType)` is used as a type without instantiation; to instantiate it use `Unit!(arguments)`
../source/map.d(22,27): Error: template class `unit.Unit(TileType)` is used as a type without instantiation; to instantiate it use `Unit!(arguments)`
../source/map.d(125,11): Error: template class `tile.Tile(TileType)` is used as a type without instantiation; to instantiate it use `Tile!(arguments)`
../source/map.d(129,14): Error: template class `tile.Tile(TileType)` is used as a type without instantiation; to instantiate it use `Tile!(arguments)`
../source/map.d(133,11): Error: template class `unit.Unit(TileType)` is used as a type without instantiation; to instantiate it use `Unit!(arguments)`
../source/unit.d(12,12): Error: template instance `map.Map!(VisibleTile)` error instantiating
../source/tile.d(19,9):        instantiated from here: `Unit!(VisibleTile)`
source/vtile.d(4,21):        instantiated from here: `Tile!(VisibleTile)`
```

Will I somehow manage to solve this problem?

Given that it's possible to declare an object inside a class, and then fill it with an object of a derived class, I would have thought it would be possible to use a template as a type, and then fill it with an object of a class derived from that template.
March 08

On Friday, 8 March 2024 at 06:03:51 UTC, Liam McGillivray wrote:

>

A problem I have is that the 3 classes Map, Tile, and Unit reference each-other. If I turn Map into a template, than it complains about the member variable of Unit declared as Map map; without arguments. I change this line to Map!TileType map; but this requires that Unit is also turned into a template.

If you don't want Unit to be a template, you can just have Map derive from a basic interface or abstract class. You can also have every relevant class share similar templates, you just need to remember to supply the template arguments everywhere. You'll need to think about how much interoperation you want between these classes. Does Unit really need to know what TileType map is using, or can it just trust that when it asks Map to move, Map will handle everything related to tile types? Generally it's best practice to have as much of the inner workings isolated as possible and just provide methods to access functionality. To ease some of the template argument!spaghetti, you could insert aliases into the classes via fully qualified symbol names.
Another alternative, if everything is defined on one file, you could wrap everything in a single template, but I don't usually favor this strategy.

//version=Interfaced;
version=AllTemplated;
//version=AllTemplatedAliases;
//version=OneTemplate;

version(Interfaced) {
	interface IMap {
		Unit addNewUnit();
	}
	class Map(TileType) : IMap {
		Unit[] units;
		Unit addNewUnit() {
			auto unit = new Unit(this);
			units ~= unit;
			return unit;
		}
	}
	class Unit {
		IMap map;
		private this(IMap map) {
			this.map = map;
		}
	}
	void main() {
		auto map = new Map!uint;
		auto unit = map.addNewUnit;
	}

} else version(AllTemplated) {
	class Map(TileType) {
		Unit!TileType[] units;
		auto addNewUnit() {
			auto unit = new Unit!TileType(this);
			units ~= unit;
			return unit;
		}
	}
	class Unit(TileType) {
		Map!TileType map;
		private this(Map!TileType map) {
			this.map = map;
		}
	}
	void main() {
		auto map = new Map!uint;
		auto unit = map.addNewUnit;
	}

} else version(AllTemplatedAliases) {
	class Map(TileType) {
		alias Unit = mymodule.Unit!TileType;
		Unit[] units;
		auto addNewUnit() {
			auto unit = new Unit(this);
			units ~= unit;
			return unit;
		}
	}
	class Unit(TileType) {
		alias Map = mymodule.Map!TileType;
		Map map;
		private this(Map map) {
			this.map = map;
		}
	}
	void main() {
		auto map = new Map!uint;
		auto unit = map.addNewUnit;
	}

} else version(OneTemplate) {
	template Map(TileType) {
		class Map {
			Unit[] units;
			auto addNewUnit() {
				auto unit = new Unit(this);
				units ~= unit;
				return unit;
			}
		}
		class Unit {
			Map map;
			private this(Map map) {
				this.map = map;
			}
		}
	}
	void main() {
		auto map = new Map!uint;
		auto unit = map.addNewUnit;
	}
}

If a given class doesn't really need to know what the template parameters are to the other class it's interacting with, I would avoid defining too many template types everywhere and just use interfaces or abstract parent classes.

>

After changing class Unit to class Unit (TileType), it complains about the line Unit* occupant;` in Tile.

Are you sure you need a pointer here? Class objects in D are already reference-type by default.

March 08

On Friday, 8 March 2024 at 16:54:48 UTC, cc wrote:

>

If you don't want Unit to be a template, you can just have Map derive from a basic interface or abstract class. You can also have every relevant class share similar templates, you just need to remember to supply the template arguments everywhere.

Right. Interfaces. I haven't used this feature yet, but I had read about them, thinking I would likely use them. I forgot about them at the time of making this thread. I will try making Map & Tile an interface, and maybe Unit as well.

Can an interface and a template have the same name, similar to function overloading? In that case, Map!Tile would refer to the template, while Map would be the interface. Maybe I'll have figured this out myself by the time someone replies.

>

You'll need to think about how much interoperation you want between these classes. Does Unit really need to know what TileType map is using, or can it just trust that when it asks Map to move, Map will handle everything related to tile types? Generally it's best practice to have as much of the inner workings isolated as possible and just provide methods to access functionality.

I don't think Tile and Unit will need to know the details of what kind of Tile & Unit derivative the map is using.

As an aside on this topic, I wonder if my Map class and it's derivative Mission are doing too many things. I don't know if there is enough of a problem with one "master" class dominating the program that it's worth splitting it up despite the more complex programming.

> >

After changing class Unit to class Unit (TileType), it complains about the line Unit* occupant; in Tile.

Are you sure you need a pointer here? Class objects in D are already reference-type by default.

I'm pretty sure I do, as references can't be null. As an approximation of what I'm doing, think of Map as a chess board, Tile as a square on the board, and Unit as a chess piece. Not every tile on the chess board is occupied by a piece.

If you want, you can see the program I have so far on my GitHub repository. Right now the master branch is the most up-to-date, but I might soon make a new branch to try out templates and interfaces.

March 09

Update on two things:

One is that I now better understand what it means that D objects are "reference by default". This means that references can be null if they are declared with a class. In my commits last night, I have changed many pointers into references. I think my time will be smoother from now on, spending far less time trying to debug segfaults.

Secondly, I found out that interfaces can't have variables. What!? That's crazy! Why wouldn't they? They totally should. Doesn't this mean that I will need to use getter and setter functions instead of direct access when using interfaces? I don't like this.

March 10

I have made a new branch of my project called "templates-interfaces" which reworks some things, and turns the Map class into an interface and template. It is now functioning like the master branch, but I think the code should now be (arguably) easier to follow. At least that's true for the Raylib front-end, though maybe a little less so for the library.

Here it is:
https://github.com/LiamM32/Open_Emblem/tree/templates-interfaces

I will probably merge it into master soon.

March 10

On Saturday, 9 March 2024 at 22:03:34 UTC, Liam McGillivray wrote:

>

Secondly, I found out that interfaces can't have variables. What!? That's crazy! Why wouldn't they? They totally should. Doesn't this mean that I will need to use getter and setter functions instead of direct access when using interfaces? I don't like this.

An interface just defines an interface, a set of method signatures that a class can respond to, and must implement. If you want storage and functionality, you can define a base class. A derived class can inherit from either one base class or one or more interfaces. Or a combination, but multiple inheritance is not a well-liked idea.

class Map {
	int someVar = 3;
	void someFunc() {
		// default behavior
		writeln("hello");
	}
}

class CustomMap!TierType : Map {
	override void someFunc() {
		// new behavior
		writefln("good day %s %s", someVar, TierType.stringof);
	}
}

void main() {
	auto map = new Map;
	map.someFunc() // hello
	map = new CustomMap!uint;
	map.someFunc() // good day 3 uint
}

In the last line of this code, the variable map is still of type Map (chosen by auto when it received new Map, but the actual class object it references is of type CustomMap!uint, hence calling a virtual function like someFunc calls the derived version instead of the original. Note that functions that take template arguments (not included here) don't necessarily follow this behavior; mixing templated methods and inheritance has some pitfalls.

« First   ‹ Prev
1 2