www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.announce - record: C# like records for D

reply Dylan Graham <dylan.graham2000 gmail.com> writes:
[DUB](https://code.dlang.org/packages/record)
[Github](https://github.com/hmmdyl/record)



1](https://docs.microsoft.com/en-us/dotnet/csharp/fundamentals/types/records)
https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/tutorials/records).

Future steps are going to be adding a record struct; default 

of construction or duplication, the init lambda for the field is 
called, and the field can be set that once.

Example:

```D
import drecord;

alias MyRecord = record!(
     get!(int, "x"), /// x is an int, can only be set during 
construction
     get_set!(float, "y"), /// y is a float, can be get or set 
whenever
     property!("getDoubleOfX", (r) => r.x * 2), /// a property 
that returns the double of x
     property!("getMultipleOfX", (r, m) => r.x * m, int), /// that 
takes an argument and multiples x by that value
     property!("printY", (r) => writeln(r.y)), /// prints y
     property!("resetY", (r) => r.y = 0) /// resets y to 0f
);

auto r = new MyRecord(12, 4.5f); /// sets x, y

writeln(r); // { x = 12, y = 4.5f }
writeln(r.toHash); // 376

writeln(r.x); // 12
writeln(r.getDoubleOfX); // 24
writeln(r.getMultipleOfX(4)); // 48
r.printY; // 4.5
r.resetY;
writeln(r.y); // 0
r.y = 13f;
r.printY; // 13

/// Duplicate r, and set x to 17 (we can only do this in ctor, or 
during duplication)

auto q = r.duplicate!("x")(17);
writeln(q); // {x = 17, y = 0}
writeln(q == r); // false
writeln(q is r); // false

auto b = r.duplicate; // duplicate, don't change any fields
writeln(b == r); // true
writeln(b is r); // false
```
Jul 14
next sibling parent Dylan Graham <dylan.graham2000 gmail.com> writes:
On Wednesday, 14 July 2021 at 23:16:05 UTC, Dylan Graham wrote:
 [DUB](https://code.dlang.org/packages/record)
 [Github](https://github.com/hmmdyl/record)
record now has support for custom default initialisers. Example: ```D import drecord; alias DefaultRecord = record!( // The third parameter is a lambda which provides default initialisation get!(int, "x", () => 4), // x is set to 4 by default get_set(Object, "o", () => new Object) // o is set to a new Object by default ); auto r = new DefaultRecord; // run the default initialisers writeln(r); // {x = 4, o = object.Object} auto q = DefaultRecord.create!"x"(9); // run default initialisers, then set x to 9 writeln(r); // {x = 9, o = object.Object} ```
Jul 15
prev sibling next sibling parent Dylan Graham <dylan.graham2000 gmail.com> writes:
On Wednesday, 14 July 2021 at 23:16:05 UTC, Dylan Graham wrote:

 construction or duplication, the init lambda for the field is 
 called, and the field can be set that once.
This has been implemented with `get_compute`. Example: ```D alias MyRecord = record!( get!(int, "x", () => 20), // get_compute lets you compute a field after the rest have been initialised get_compute!(float, "y", (rec) => rec.x * 2f) ); auto r = new MyRecord; writeln(r); // {x = 20, y = 40f} r = new MyRecord(10); writeln(r); // {x = 10, y = 20f} r = MyRecord.create!"x"(5); writeln(r); // {x = 5, y = 10f} auto q = r.duplicate!"x"(2); writeln(q); // {x = 2, y = 4f} ```
Jul 15
prev sibling next sibling parent reply Dylan Graham <dylan.graham2000 gmail.com> writes:
On Wednesday, 14 July 2021 at 23:16:05 UTC, Dylan Graham wrote:
 [DUB](https://code.dlang.org/packages/record)
 [Github](https://github.com/hmmdyl/record)
Found and squashed some critical bugs. Thanks to Adam and Rikki for the help. Before, record would throw a compilation error due when passed types declared outside of drecord or its imports. Example: ```D module myapp; class A{} auto MyRecord = record!(get!(A, "a")); // would throw an error as it could not find A ``` This was due to improper usage of `.stringof` and mixins. This has been corrected and replaced with idiomatic D. The string generation functions for the constructor and opEquals have been removed and replaced in accordance to above.
Jul 16
parent Dylan Graham <dylan.graham2000 gmail.com> writes:
On Friday, 16 July 2021 at 13:14:22 UTC, Dylan Graham wrote:
 On Wednesday, 14 July 2021 at 23:16:05 UTC, Dylan Graham wrote:
 [DUB](https://code.dlang.org/packages/record)
 [Github](https://github.com/hmmdyl/record)
```D module myapp; class A{} auto MyRecord = record!(get!(A, "a")); // would throw an error as it could not find A ```
That should read ```D [...] alias MyRecord = record!(get!(A, "a")); // would throw an error ```
Jul 16
prev sibling parent reply vit <vit vit.vit> writes:
On Wednesday, 14 July 2021 at 23:16:05 UTC, Dylan Graham wrote:
 [DUB](https://code.dlang.org/packages/record)
 [Github](https://github.com/hmmdyl/record)



 1](https://docs.microsoft.com/en-us/dotnet/csharp/fundamentals/types/records)
https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/tutorials/records).

 Future steps are going to be adding a record struct; default 

 of construction or duplication, the init lambda for the field 
 is called, and the field can be set that once.
What adventage has record over normal immutable/const class?
Jul 16
parent reply Dylan Graham <dylan.graham2000 gmail.com> writes:
On Friday, 16 July 2021 at 13:54:36 UTC, vit wrote:
 On Wednesday, 14 July 2021 at 23:16:05 UTC, Dylan Graham wrote:
 [DUB](https://code.dlang.org/packages/record)
 [Github](https://github.com/hmmdyl/record)

 This is record. It aims to implement records similar to what 

 1](https://docs.microsoft.com/en-us/dotnet/csharp/fundamentals/types/records)
https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/tutorials/records).

 Future steps are going to be adding a record struct; default 

 end of construction or duplication, the init lambda for the 
 field is called, and the field can be set that once.
What adventage has record over normal immutable/const class?
In terms of mutability, none. The duplicate method, however, lets you copy and mutate (once at duplication) a record without impacting the integrity of the original. You can do this with an immutable class, but then you have to roll your own. Really, it's more a convenience / less typing sort of feature, as it implements all the common boilerplate for you, which should help with consistency of code.
Jul 16
parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 7/16/21 10:52 AM, Dylan Graham wrote:
 On Friday, 16 July 2021 at 13:54:36 UTC, vit wrote:
 What adventage has record over normal immutable/const class?
In terms of mutability, none. The duplicate method, however, lets you copy and mutate (once at duplication) a record without impacting the integrity of the original. You can do this with an immutable class, but then you have to roll your own. Really, it's more a convenience / less typing sort of feature, as it implements all the common boilerplate for you, which should help with consistency of code.
What about a UFCS `duplicate` method? but D has pretty good tools when it comes to boilerplate. I would possibly suggest that instead of a record template that accepts directives using inline lambdas, etc, just accept a model type and use udas to adjust the record type. i.e.: ```d struct RecModel { get_set float y; get int x; auto getDoubleOfX() { return x * 2; } ... // etc } alias MyRecord = record!RecModel; ``` I use this kind of thing to great success in my SQL database system. -Steve
Jul 16
parent reply Dylan Graham <dylan.graham2000 gmail.com> writes:
On Friday, 16 July 2021 at 19:37:53 UTC, Steven Schveighoffer 
wrote:
 On 7/16/21 10:52 AM, Dylan Graham wrote:
 On Friday, 16 July 2021 at 13:54:36 UTC, vit wrote:
 What adventage has record over normal immutable/const class?
In terms of mutability, none. The duplicate method, however, lets you copy and mutate (once at duplication) a record without impacting the integrity of the original. You can do this with an immutable class, but then you have to roll your own. Really, it's more a convenience / less typing sort of feature, as it implements all the common boilerplate for you, which should help with consistency of code.
What about a UFCS `duplicate` method? I'm not doubting there are good reasons to define types this boilerplate. I would possibly suggest that instead of a record template that accepts directives using inline lambdas, etc, just accept a model type and use udas to adjust the record type. i.e.: ```d struct RecModel { get_set float y; get int x; auto getDoubleOfX() { return x * 2; } ... // etc } alias MyRecord = record!RecModel; ``` I use this kind of thing to great success in my SQL database system. -Steve
That is a good idea, and to be honest I haven't looked at it that way. So the record is a separate type from its model? Ie: `class MyRecord {}` based off `struct RecordModel {}`? Or did I misunderstand? With regards to things like properties and methods, do you have RecModel inlined in the class and then forward the calls to it? Ie: ```D struct RecModel { get int x; auto doubleOfX() { return x; } } class Rec { private RecModel instance; property auto x() { return instance.x; } auto doubleOfX() { return instance.doubleOfX; } } ``` I do like the lambda directives as it, in my mind at least, enforces the idea that the record/class must be a simple data type (ie no crazy methods and such). I do have to admit this was more an exercise in metaprogramming, and since I did manage to make something I figured I'd share it.
Jul 16
parent Steven Schveighoffer <schveiguy gmail.com> writes:
On 7/16/21 4:11 PM, Dylan Graham wrote:
 On Friday, 16 July 2021 at 19:37:53 UTC, Steven Schveighoffer wrote:
 I would possibly suggest that instead of a record template that 
 accepts directives using inline lambdas, etc, just accept a model type 
 and use udas to adjust the record type.
 
 That is a good idea, and to be honest I haven't looked at it that way. 
 So the record is a separate type from its model? Ie: `class MyRecord {}` 
 based off `struct RecordModel {}`? Or did I misunderstand?
 
 With regards to things like properties and methods, do you have RecModel 
 inlined in the class and then forward the calls to it? Ie:
 
 ```D
 struct RecModel {
       get int x;
      auto doubleOfX() { return x; }
 }
 
 class Rec {
      private RecModel instance;
 
       property auto x() { return instance.x; }
      auto doubleOfX() { return instance.doubleOfX; }
 }
 ```
Yeah, something like that. Though there are multiple ways to go about it. One is to write a string that represents what you want it to do, and then mixin that thing. The way I do it is using `opDispatch` which is super-powerful for forwarding calls like that. I'm assuming you are already doing something like this anyway! It's just, what are the instructions? I really can't wait to reveal the sql library I've been nursing along with my web project. I don't want to post it yet here, because it will just garner more questions. I can point you at some early ideas I had on this kind of model-based programming, in a Boston meetup I did a talk for a number of years ago: https://www.youtube.com/watch?v=ZxzczSDaobw
 I do like the lambda directives as it, in my mind at least, enforces the 
 idea that the record/class must be a simple data type (ie no crazy 
 methods and such).
I'm not sure what you think is a "crazy" method, but lambdas can do whatever a method can do. I like using models vs. template directives because you get to use the actual language to enforce your "model" is sane, and to make readable instructions for the metaprogramming processor. Like in one of your methods, you have: ```d property!("getMultipleOfX", (r, m) => r.x * m, int) ``` What is that `int` for? It's not clear from the usage, I have to go look it up. Most likely, it's the return type, but it's not nearly as clear as: ```d int getMultipleOfX(int m) { return x * m; } ``` Plus, I can debug my model without having to debug the metaprogramming stuff.
 
 I do have to admit this was more an exercise in metaprogramming, and 
 since I did manage to make something I figured I'd share it.
It's cool, I love using metaprogramming to write code for me. It's without a doubt the best reason to use D. Learning how to use to avoid writing boilerplate is life changing! -Steve
Jul 16