Specialization Tree


#1

I can achieve a linear inheritance with specialization, but not a tree. I need siblings as well as children.

I’m wondering if there’s something I’m missing or any solutions using Rust features.

EDIT: Deleted Explanation. My original understanding was wrong.


Here is trying to add a branch and obviously getting a conflict.

impl<C:Element + Button + Clear> MouseUp for C { }

Thanks :smiley: This is an important topic for me, and I know inheritance is one of the goals of specialization in the long run, and Servo has been pushing for stuff like this for native use with the DOM!


#2

(From someone who knows very little about the feature.)
Don’t branch.

First idea to make it run; Making a linear supertrait list.
https://play.rust-lang.org/?gist=c0052bbe2ac7ff38e9634404d100ff86&version=nightly
Very ugly and hacky, hard to reason of being a good approach.

More reasonable second idea try; Remove the default
https://play.rust-lang.org/?gist=1063ab19ad09c7d3b85e0a6eb9883e80&version=nightly
(names should be picked to be more appropriate if used for real.)


#3

This comment on the specialization tracking issue shows how to implement a diamond pattern like the one you have. It shows impls for:

  • any T,
  • T: Clone,
  • T: Default,
  • T: Clone + Default.

You could adapt this into impls for:

  • Controller: Element + Button
  • Controller: Element + Button + Submit
  • Controller: Element + Button + Clear
  • Controller: Element + Button + Submit + Clear

#4

Oh wow, that could be a very strong case for Rust’s paradigm! I need some time to wrap my head around this, but I sincerely appreciate your help! I remember reading some of that tracking issue. It’s been very interesting to learn about Rust thus far.

@dtolnay Would you ever consider putting some official-ish documentation out there in the rust-sphere? Hopefully it will be stable for documentation soon.

This is music to my ears. Thank you :smiley:


#5

(Skimmed too much to tell if not been covered already.)
The comment example looks like c++ voodoo to me;

impl<T> Trait for T {
    default fn print(&self) {
        self.if_not_clone();
    }
}

T isn’t constrained so how does self get to call if_not_clone(). There could be more than one trait with the same function. Regular functions give;

error[E0599]: no method named if_not_clone found for type &T in the current scope


#6

The trait IfNotClone is in scope and has blanket implementation, so if_not_clone() is accessible for every type.


#7

This system still uses linear specialization, it just adds a pattern of delegation that sort of makes it able to go horizontal instead of just vertical, if that makes sense. I’m a little confused by how to use it in my case, since it may prove to be too complicated as it scales beyond a diamond shape.

So it still uses the additive specialization, but you can kind of turn it on its side. (I’ll explain with letters instead of Clone and Default)

You can get 0, A, B, or A +B.

Basically, he splits up A and B, and adds 1 constraint to each to get all 4 combinations.
So the specialization is like

0 and 0 + A.
B and B + A.

    A + B
         \
 A + 0     B
   \
      0

So like red75prime said, that T is 0, the blanket statement, default, nothing, whatever you want to call it.

I’ve found that you can keep adding sideways constraints this way, or vertical constraints, but it creates an unusual hierarchy of possibilities that I haven’t mapped out yet, but would most likely be unorthodox to reason with in practice. It seems it would have overlapping cases where it could go either vertical or horizontal. I haven’t gotten very far with that yet, and have no idea what I’m doing.

I also have at least another idea in the works that I may be able to use to get specialization branching that also uses delegation. Still haven’t worked on it yet.


#8

(To my prior post: Now that I have tried it you can add the trait for if_not_clone() to the clause without throwing off code into branching.)

My reasoning seems to allow adding more traits. (Without going too far to complete expansion of example.)
I renamed the example trait to closer match option.
So going from the 2 (Clone+Default) to 3 (+Eq) seems like it would be adding 4 more traits. (If wanting full coverage.)
Going to 4 would take 8 more traits. To 5, 16 extra.

trait TraitOptionClone { fn print_option_clone_none(&self); } // was IfNotClone
trait TraitSomeClone: Clone { fn print_some_clone(&self); } // was IfClone

trait TraitSomeCloneOptionEq: TraitSomeClone { fn print_some_clone_option_eq_none(&self); }
trait TraitSomeCloneAndEq: Eq { fn print_some_clone_and_eq(&self); }

trait TraitNoCloneOptionEq: TraitOptionClone { fn print_option_clone_and_eq_none(&self); }
trait TraitNoCloneSomeEq: Eq { fn print_no_clone_some_eq(&self); }

#9

I was able to get things like:
A
A + B
A + C
A + D

just by doing this, just adding more constraints incrementally.

Those are not all the possibilities that you make with the way it is set up. Without changing anything, you can also set rules for like A + B + C and stuff like that, but I removed those in the example.

It seems like there may be a system that uses his technique that would work for branching if you could create two axes – basically this just uses 1 axis – there’s one specialization that you keep adding 1 constraint to to get more possibilities.

The problem is, it seems you need 2 axes, 1 for children and 1 for siblings, but with this system I’m trying to squeeze 2 axes out of just 1. So by just wanting to add A + D, you also get things like A + C + D, but at the same time, not things like C + D. (or something like that)

So if I want to build a tree, it becomes quite complicated and unconventional, since you get weird combinations of what can inherit what, and letters can be either a sibling or a child, but you only have 1 axis to build from.

Still, his idea proves that sibling nodes are possible, so I think there’s some intelligent way to scale a robust inheritance out of it – I just don’t know how to wrap my head around that right now.


#10

I thought I’d leave something I got working here. If you just need inheritance (children and siblings) for methods you can do it easily, but have to bypass the trait system – and use a hashmap. Some trade-offs there, but very similar.

Example Case:

//controller/element/form/clear
fn  
    inherit_clear_1(C: &mut Controller) {
    inherit_button1(C);

    C.Events.insert(String::from("Event_MouseUp"), Event_MouseUp_Clear_1);
}
    fn Event_MouseUp_Clear_1(node: &Node) {
        println!("Event_MouseUp_Clear_1");
    }

This is for if you want to use composition, like generalizing events, (or if you don’t - like in the example) but still want to be able to inherit from what you compose. Doing so with data is the other half of the battle and it’s another story – Rust really isn’t designed for this, lol. Some notes on that in the link.