I am building an API with a fairly complex data structure inside. I
have worked round in circles thinking of the best representation. The
system has 50 or more different kinds of Axiom with different
structure. So I did this:
struct SubClass{sup:Class, sub:Class}
struct SubProperty{sup:Property, sub:Property}
//... 50 more
Now this causes two problems. First, if I want to store all of these,
I need a struct with lots of fields.
struct Ontology {
subclass:HashSet<SubClass>,
subproperty:Hashset<SubProperty>
}
And, secondly, although the types are different, they also contain
commonality. One solution would be to add a trait. This could address
the second question. Now I can add the commonality like so:
trait Annotated {
fn annotations() -> Annotation
}
struct SubClass{sup:Class, sub:Class, annotations:HashSet<Annotation>}
impl Annotated for SubClass {
fn annotations() {
annotations
}
}
struct SubProperty{sup:Property, sub:Property, annotations:HashSet<Annotation>}
impl Annotated for SubProperty {
fn annotations() {
annotations
}
}
// 50 more...
But now I have a lot of boilerplate. And it doesn't solve the first
problem of storing all of these in another sturct.
Next solution was to put the commonality into a struct and the
diversity into a enum.
struct Axiom{
annotation: HashSet<Annotation>
body: AxiomBody
}
enum AxiomBody {
SubClass{sup:Class, sub:Class},
SubProperty{sup:Property, sub:Property}
// 50 more...
}
This reduces the boilerplate. And I can store all the Axioms into a
single data structure HashSet<Axiom>
. But now my data structure is
inefficent. If I want to get all of the SubClass
I have to do
something like this:
axioms.iter().filter(|ax| if let AxiomBody::SubClass(_) = ax {true})
Now there is a way to solve this which is to store the variants
independently. I thought about a data structure like this
HashMap<Discriminant,Axiom>
which seems like a nice solution. But it isn't because
mem::discriminant
needs an instance. So I can't get to the
SubClass
axioms without creating an SubClass
axiom instance.
So, I add another enum without values.
struct Axiom{
annotation: HashSet<Annotation>
body: AxiomBody
kind: AxiomKind
}
enum AxiomKind {
SubClass, SubProperty // 50 more...
}
enum AxiomBody {
SubClass{sup:Class, sub:Class},
SubProperty{sup:Property, sub:Property}
// 50 more...
}
Now I can store my Axioms in a data structure HashMap<AxiomKind, Axiom>
-- I just need to be careful putting data into the map, since
there is no way to enforce the relationship between key and the
value. This also contains a problem for the downstream user since even
when I return (say) all the instances of AxiomBody::SubClass
, we
still have to use if let
to access sup
and sub
because
AxiomBody::SubClass
isn't a type -- I cannot directly return
instances of it.
So, I am stuck here and coming to the conclusion that what I need is
to macro the whole thing. So something like this:
axioms!{
struct SubClass{sup:Class, sub:Class}
struct SubProperty{sup:Property, sub: Property}
}
which will transform into:
enum AxiomKind {
SubClass,
SubProperty
// 50 more...
}
struct SubClass
{
sup:Class, sub:Class,
annotated: HashSet<Annotation>,
kind: AxiomKind
}
impl Annotated for SubClass {
fn annotation(&self) {self.annotated}
}
impl Kinded for SubClass {
fn kinded(&self) {self.kind}
}
// 50 more ...
I am slightly nervous about this -- I'm a lisp programmer and tend to
overuse macros. But I can't see anyway around a lot of boilerplate.
Am I missing something?
If anyone gets this far, thank you for taking this time with a long
post!