digitalmars.D.learn - Can a D library have some types determined by the client program?
- Liam McGillivray (17/17) Mar 07 In a source library written in D, is it possible to have some
- Richard (Rikki) Andrew Cattermole (26/26) Mar 07 There are two ways to do this.
- Liam McGillivray (19/29) Mar 07 Thank you. Is this first example you gave the template? Is the
- Richard (Rikki) Andrew Cattermole (6/37) Mar 07 Typically in D we use templates quite heavily, but what you are wanting
- Liam McGillivray (63/80) Mar 07 Nope, but thank you. I am not a very experienced programmer. The
- cc (112/120) Mar 08 If you don't want Unit to be a template, you can just have Map
- Liam McGillivray (24/39) Mar 08 Right. Interfaces. I haven't used this feature yet, but I had
- Liam McGillivray (12/12) Mar 09 Update on two things:
- Liam McGillivray (9/9) Mar 09 I have made a new branch of my project called
- Liam McGillivray (17/19) Mar 10 I have put up a merge request for these changes I have made to
- cc (36/41) Mar 10 An interface just defines an interface, a set of method
- Liam McGillivray (24/27) Mar 25 Thank you for teaching me how to do this. This is where I first
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.
Mar 07
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
Mar 07
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?
Mar 07
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: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.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?``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.
Mar 07
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: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.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 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.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.
Mar 07
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. ```d //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.
Mar 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.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](https://github.com/LiamM32/Open_Emblem). 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.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.
Mar 08
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.
Mar 09
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.
Mar 09
On Sunday, 10 March 2024 at 04:39:33 UTC, Liam McGillivray wrote:https://github.com/LiamM32/Open_Emblem/tree/templates-interfaces I will probably merge it into master soon.I have put up a merge request for these changes I have made to the library and the Raylib front-end. I would be interested in having my code looked at by someone more experienced than me (which would be everyone who has replied here and most of those reading). https://github.com/LiamM32/Open_Emblem/pull/2 You can take a look in `source/map.d` where the former `Map` class has been replaced with the `Map` interface and the `MapTemp` template. You may also look in `oe-raylib/source/mission.d`, and compare how sprites were handled before vs after. Of course, you can also compile and run it yourself (assuming I didn't accidentally leave out a file). Other commentary on my code would also be appreciated. I don't know if there's a forum section appropriate for sharing personal projects, but I may post there too.
Mar 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. ```d 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.
Mar 10
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/templatesThank you for teaching me how to do this. This is where I first learned to use templates in D, and I have been using them since for functions, and as of yesterday, my first mixin template. That being said, I'm pleased to announce the return of the `Map` class. As of yesterday's commit, the class template `class MapTemp(TileType:Tile, UnitType:Unit)` and `interface Map` have now been replaced with a single `Map` class as they were before. At the time I had asked about this, I had not yet discovered that an array of objects can be filled with objects of a derived type by just using the regular cast syntax. Now that I know this, I've decided to just create the objects under the `VisibleTile` and `VisibleUnit` classes, place them in the `Map` object's arrays (cast to `Tile` and `Unit`), and then cast them back whenever I need to access anything specific to the derived classes. Now I no longer have to deal with the limitations of interfaces. Things should be easier now. The straw that broke the camels back was when I made my first mixin template, which adds functionality to a class for manipulating arrays of `Unit` objects. The compiler wasn't allowing it in the `Map` class because the array that I was trying to give it access to was of `UnitType`, not specifically the base `Unit` class. I'm happy to have it back.
Mar 25