traits icon indicating copy to clipboard operation
traits copied to clipboard

Consider implementing BaseList / BaseDict / BaseSet

Open kitchoi opened this issue 3 years ago • 2 comments

Use case first: There is a use case (in pyface actually) where we would want to validate the length of a list dynamically, but the minlen and maxlen on List are static.

This prompted a more general question: should there be a stable and extensible trait type base class for TraitList, TraitDict and TraitSet that projects can subclass and extend from.

kitchoi avatar Jul 22 '20 15:07 kitchoi

Use case first: There is a use case (in pyface actually) where we would want to validate the length of a list dynamically, but the minlen and maxlen on List are static.

For this use case, I tried subclassing List (just to experiment) but then I couldn't prevent the list from getting too long or too short by mutation (e.g. append). This is probably off-topic for this issue regarding base trait types, but still a relevant context.

kitchoi avatar Jul 22 '20 16:07 kitchoi

Overall this is a good idea, and gives the start of a path out of the use of the current TraitListObject which holds references it really shouldn't to the object and trait.

Another use case to consider, from playing around with the data view in Pyface. One goal of the goals of the redesign, apart from code cleanliness was to make the container objects more "first-class" citizens of the Traits world, and to avoid the constant copying of lists which can trip-up users who expect typical Python semantics.

For the current implementation, validation methods are specified as instance-level state: if you want a list of integers then you say something like TraitList(..., validator=int_validator) or something similar. Which is fine, on the surface.

The problem comes because we want to declare the type of the items in the list declaratively in the BaseList trait. Consider the following code:

class Foo(HasTraits):
    x = List(Int)

class Bar(HasTraits):
    y = List(Union(Int, Str))

l = TraitList([1, 2, 3, 4, 5])
f = Foo(x=l)
b = Bar(y=l)

This is clearly valid, but assuming that we want to avoid copying the list, what should the validator of l be set to? There is a similar problem, but perhaps even harder to resolve, with validating minlen and maxlen dynamically based on the state of an object.

Thinking this through, probably the simplest way to deal with the situation is that we actually have multiple validators for l, and each time it gets set into or removed from a trait we modify the set, which will be a bit annoying to book-keep, but should give the desired result.

Alternatively, we could think of the validation as being bound to the type of the list, so we have concepts "AnyList" and "IntList" and "StrList" all as separate subclasses of TraitList and have some way to declare them, and so effectively instead of saying List(Int) it effectively becomes Instance(IntList) with some sort of better spelling (perhaps Instance(List(Int)), where List(...) is effectively a class factory/metaclass, so you would say l = List(Int)([1, 2, 3, 4])). Things might get a bit gorpy when you try to unravel Union(Instance(List(Int)), Instance(List(Str))) vs Instance(List(Union(Int, Str))) but if you think hard, those are actually two distinct things.

This approach would need something to identify that List(Int), List(Int()), etc. are really all the same thing so you don't end up with thousands of almost identical types.

corranwebster avatar Aug 19 '20 16:08 corranwebster