kaitai_struct
kaitai_struct copied to clipboard
Best way to represent inheritance?
My use-case is basically a stream of serialized instances of classes from the hierarchy, each instance prefixed by type code.
As far as I understand, Kaitai doesn't support struct inheritance (yet?), so it's not really possible for types to "inherit" some common fields from a common ancestor type.
What's the best way of "emulating" it?
I see several options:
-
Include "base class" as a kind of
base
subfield into types representing children classes and use generic TLV. +: No code duplication here, great readability. -: But then with the generated code I'll have to do stuff likeMyObject.base.base.base.field_1
, and what's even worse - the access level will vary depending on a type's position in the hierarchy. -
Include all fields of all base classes into each type representing a child class and use generic TLV. +: No indirection problems, mediocre readability -: Code duplication sucks in case I have to edit something manually (and I'll probably have to)
-
Include all fields of all classes from the hierarchy into one type and make them conditional on the type code. +: No code duplication, no indirection problems. -: Readability in case of deep and wide hierarchy sucks.
-
(variation of 1): put types representing child subclasses as a "TLV tail" into base class. Pros and cons are basically the same, except there'll be an additional level of indirection in .ksy code itself, because each child class will have to refer to (grand-grand-grand)parent's
type
field.
Maybe there's some other solution I'm not familiar with? I'm not really concerned about writing the .ksy code (it'll be autogenerated), only with reading, editing and using it.
struct inheritance
Inheritance is considered harmful. The things that are usually achieved by inheritance should better be achieved by composition and autodelegation. We should not pursue inheritance IMHO. But autodelegation and interfaces (#314) seems to be what we need.
Proposal: delegate
key.
types:
blah:
delegate:
i_a: # the key is the a name of interface. Can be empty for automatically implicitly derived interfaces.
- a value is either a field or an instance.
- b # multiple delegation for the same interface is possible, in this case it populates the fields that have not been populated by the items standing in the list before the current item.
Thanks for your reply!
Inheritance is considered harmful. The things that are usually achieved by inheritance should better be achieved by composition and autodelegation.
"Inheritance is considered harmful" is more of an ideological dogma than a practical standpoint. Inheritance won't be as largely adopted solution as it is now if it didn't solve many everyday problems in a clean and easy-to-understand-and-reason-about way. Of course it comes with it's own bunch of hassles and complications, but it should be up to the programmer to decide what to employ in each particular case. Good framework will always provide as many options as possible for users to choose from.
So, lets leave holy wars to CS theoretists =) We're talking about a real practical problem here.
Note that I didn't mention "inheriting" behaviour; I'm only interested in sharing the structure of the data, basically a form of a "code reuse". I understand that this problem can be solved with composition and (auto)delegation. I already tried to go this way in Rust with option #1
and "Deref polymorphism", and while it removes the .base.base.base.base
boilerplate, this option doesn't really sits well with me. IMO "data inheritance" or "flat inclusion" / "extension" would apply here in much more clean and simple manner:
types:
blah:
seq:
- id: common_fields_1
type: base_type_1
extend: true # KS compiler should put all fields from common_fields_1 in place of this field
- id: common_fields_2
type: base_type_2
extend: true # KS compiler should put all fields from common_fields_2 in place of this field
- id: specific_field_1
type: u4
- id: specific_field_2
type: u1
But let's return to our muttons. While you're more than welcome to look at my question as a statring point for a brainstorm of new ideas, my main purpose here was not to make a feature request, but looking for a practical solution for today (not tomorrow, not next month, not next KS release). I'd be very much obligued if discussion would steer that way. :)
#88 is also related to the question raised here.
@KOLANICH:
#88 is also related to the question raised here.
Yes, that immediately occurred to me as well. But it's not implemented, so let's not pollute this question.
@Jasuf:
- Include "base class" as a kind of
base
subfield into types representing children classes and use generic TLV. +: No code duplication here, great readability.
In my opinion, any way that involves code duplication (which are basically all) is not an option, because you wrote that you'll have to read and probably edit the code.
-: But then with the generated code I'll have to do stuff like
MyObject.base.base.base.field_1
, and what's even worse - the access level will vary depending on a type's position in the hierarchy.
I think you can at least contain that inconvenient access in the .ksy
spec which will be autogenerated (as you mentioned) - like this:
seq:
- id: base
type: base_type
instances:
foo:
value: base.foo
bar:
value: base.bar
types:
base_type:
seq:
- id: foo
type: u1
- id: bar
size: 4
That's probably the option I would choose.
4. (variation of 1): put types representing child subclasses as a "TLV tail" into base class. Pros and cons are basically the same, except there'll be an additional level of indirection in .ksy code itself, because each child class will have to refer to (grand-grand-grand)parent's
type
field.
It sounds like I've used exactly this approach in bmp.ksy
, so it can be useful in some cases too.
I think you can at least contain that inconvenient access in the
.ksy
spec which will be autogenerated (as you mentioned) - like this:
Wow, I completely forgot about instances
here! Thanks @generalmimon ! That would require some backtracking in generation tool and would look a bit messy in .ksy file, but so far I think that's the best choice.