Reputation: 21
I'am trying to translate a part of my framework from Python to D and being struggling with one detail: But first I have to expand the scope. The component is like an ORM. My users define classes, which instances will be persistent in a database. The user-API should be as simple as possible. For example
class Person:
identity _id,
string name,
date birthdate,
...
class Car:
indentity _id,
string ident,
int power,
Person owner
...
myCar = Car.load(ident="DEAD-BEAF")
print myCar.power
print myCar.owner.name
The load-function loads the instance data from the database. But the loading of the owner of the car should be deferred until the instance is used, because most of the application works on cars, the owners are uses rarely.
In Python I can implement this behaviour using the Descriptor-Protocol. I have a set of "field"-classes, which are descriptors. For example
class StringField(object):
def __get__(self,obj,objtype):
# get the value from obj
def __set__(self,obj,value):
# set value in obj
The EntityClass has an appropriate metaclass, which wires the needed connections. The user defines in Python:
class Person(EntityClass):
name = StringField()
birthdate = DateField()
...
class Car(EntityClass):
ident = StringField()
power = IntField()
owner = RelationField(Person)
...
and uses the classes:
myCar = Car.load(ident="DEAD-BEAF")
print myCar.power (#1)
print myCar.owner.name (#2)
Under the hood the call to myCar.power is expanded to
Car.power.__get__(myCar,Car)
If I load a car from the database, I only load the owner-Id. If one uses the owner
theowner = myCar.owner
I can load deferred the Person instance from the database
class RelationField(object):
def __get__(self,obj,objtype):
if not instance in obj.cache:
load instance
add instance to obj.cache
return instance from obj.cache
Translating the ORM to D I have tried different implementations. For simple basetypes it is very simple to use User Defined Attributes (UDA) in conjuction with templates and the unified call syntax:
struct Persistent {};
save(T)(ref T obj)
{
...
}
T load(T)(...)
class Person
{
@Persistent string name;
@Persistent Date birthday;
...
}
class Car
{
@Persistent string ident;
@Persistent int power;
@Persistent Person owner; //???
...
}
auto myCar = load!Car(...);
writeln(myCar.power);
writeln(myCar.owner.name)
This API is as simple as the Python-API, but I have no idea how to implement the deferred loading of owner. What I need is to replace the owner-member by a property function, but I do not known how to do this using compile time meta programming. So how gan this be done? Or is there an idiomatic way to do?
Upvotes: 2
Views: 184
Reputation: 3442
You can use opDispatch. The property name is available compile time, so you can handle them with a generic code, or you can also create specialisations, like the code below does, using if
after the template
line.
struct Person {
int id;
string name;
}
Person[int] person_by_id;
struct Car {
int id;
int power;
int owner_id;
// https://dlang.org/spec/operatoroverloading.html#dispatch
template opDispatch(string property)
if (property == "owner")
{
@property ref Person opDispatch() {
return person_by_id[owner_id];
}
}
}
void main() {
auto p = Person(1, "Joe");
person_by_id[p.id] = p;
auto c = Car(123, 900, 1);
assert(c.owner.name == "Joe");
c.owner.name = "Average Joe";
assert(person_by_id[1].name == "Average Joe");
}
The opDispatch
template can be generated by a mixin template so the user should write something like this:
struct Car {
int id;
int power;
int owner_id;
mixin addMyHandlers!Car;
}
Here the addMyHandler
mixin template should generate the opDispatch
function above, using introspection of the passed-in struct. If you prefer to keep the struct nice and clean, you can generate functions outside the struct, and take advantage of the Uniform Function Call Syntax (UFCS):
struct Car {
int id;
int power;
int owner_id;
}
@property ref Person owner(const ref Car car) {
return person_by_id[car.owner_id];
}
This owner
function can also be generated using a mixin template, using introspection on Car
to investigate what to generate. (Similarly to your python code.)
Upvotes: 2