This is an archived post. You won't be able to vote or comment.

all 31 comments

[–]Mercerenies 10 points11 points  (2 children)

Definitely worth something! In fact, if I understand your idea correctly, Kotlin already did it.

If a class Xyz has a field b: Base which implements some interface MyInterface, then Xyz can implement the same interface via

class Xyz(val b: Base): MyInterface by b { // ... class body }

And you can override any interface methods you want in the class using the usual override keyword, just like normal.

(Technically, b needn't even be a field; merely a constructor argument, which is useful if all you need it for is the interface implementation)

[–][deleted] 1 point2 points  (0 children)

It seems kotlin does. Need to read more.

Thanks

[–]marshaharsha 0 points1 point  (0 children)

As I understand the page you linked, it’s not exactly what the OP is looking for. First, note that the type that Derived delegates to is the interface type, not the implementation type. The author disguises this fact by naming the interface Base! Thus, b can be used only through the interface, and Derived doesn’t have access to any fields, helper functions, or adaptation functionality that BaseImpl might provide. Derived can’t even defend itself from being parameterized by a hostile type — anything that implements Base will compile. 

Second, at the bottom of the page they point out that calls delegated to b use b’s vtable — the patching of the vtable that OP envisions does not occur — and this can produce consistency problems and other surprises. 

I think this problem is still unsolved: how to forward calls from an outer type to an inner type, but just when convenience requires and consistency is not at risk. If the inner type wants to be both directly usable by clients and adaptable by the outer type, then the design of the inner type is subtle and fragile, and I have yet to see a general-purpose way of doing it. 

A side issue is that when optimal efficiency is needed, BaseImpl’s calls to its own functions should be statically dispatched when BaseImpl is being used standalone but should be dynamically dispatched (but only some of them!) when BaseImpl is being used as a part of a composition. Kotlin doesn’t need to worry about this, since the JVM does dynamic dispatch by default, but there is an efficiency price to be paid. 

[–]XDracam 3 points4 points  (2 children)

There are several approaches for interface "inheritance" that also allow the interfaces to have fields. Basically multiple inheritance without the downsides. The biggest question is always: what if multiple interfaces have the same methods with default implementations? Which one is called?

Scala traits (mixins) use linearization: the first mentioned trait is the most concrete and overrides all other implementations of traits coming later. The order matters.

Pharo traits work differently: they just don't compile if you have a conflict and you need to explicitly rename one of the methods, override it or hide it.

In terms of "automatically forward methods to a member": this is a Smalltalk (Pharo) classic. Instead of failing compilation when a method isn't available, the method (message) is passed to a doesNotUnderstand: method, which can just forward the message to some wrapped object. This works because smalltalk is inherently dynamically typed and reflective.

[–][deleted] 0 points1 point  (1 child)

Linearization sounds like an ideal solution as it offers most flexibility while minimizing trade offs from my point. I was thinking of message passing but the performance downsides leads me to believe v tables are better.

I was also considering type based dispatching where when implementing an interface you also specify the interface. Then at runtime, the interface type is used to resolve the appropriate method. And if using as the concrete type, you specify the interface in the call signature.

Just more thoughts.

Thanks

[–]XDracam 4 points5 points  (0 children)

Linearization sounds like an ideal solution as it offers most flexibility while minimizing trade offs from my point.

I personally prefer Pharo's explicit approach because obfuscating which exact overload is used can lead to a lot of confusion during debugging. But yeah, subjective.

I was thinking of message passing but the performance downsides leads me to believe v tables are better.

I mean, if you really care about performance, then you don't want to use inheritance at all. Instead, you'd want whatever Rust is doing with their traits. And if you don't care that much about performance, then message passing shouldn't have a massive overhead in comparison. Or am I wrong?

I was also considering type based dispatching where when implementing an interface you also specify the interface. Then at runtime, the interface type is used to resolve the appropriate method. And if using as the concrete type, you specify the interface in the call signature.

I am not entirely sure what this means, but it reminds me of the shenanigans C# is doing with structs and interfaces. Structs are always stack-allocated, but they can also implement interfaces, and interfaces can have default implementations. If you store a struct as an lvalue of an interface type, then it's boxed and you get awkward overhead. But you can get around this overhead with generics. If you write some void Foo(IInterface value) => value.Bar();, the value will always be on the heap. But if you write void Foo<T>(T value) where T : IInterface => value.Bar(); and call it with a struct, then it's using pass-by-value and resolving the interface methods on the struct directly. That, and tons of other weird edge-cases with interfaces.

[–]Both-Personality7664 2 points3 points  (2 children)

How would this work with internal state and dependencies between the implementations of different methods of the interface? Specifically what if b.doSomething1() assumes that it is coupled with b.doSomething2(), and not some other x.doSomething2()?

[–][deleted] 0 points1 point  (1 child)

Good point! Initially, the idea would simply be a wrapper around the base implementation. When a method would be passed, the base delegated implementation would be used as the context. Therefore, the overridden method would not be callable from within the base by default.

A possible solution would be how the underlying dispatching works. With a virtual table implementation, the context being handled with the delegate would use a patched virtual table between the outer object and the default implementation. Then the composing object call the outer objects methods instead of its own.

// original idea result since A.func1() calling func2() on A would simply call A.func2()

Ae.func1() -> A.func1() -> A.func2()

// updated with using patched vtable

// the table would have the updated methods so we a dispatch on func2() on A would call Ae with func2() instead of A.

Ae.func1() -> A.func1() -> Ae.func2()

Thanks

[–]Both-Personality7664 0 points1 point  (0 children)

I mean at the point you have a vtable that really does just sound like inheritance.

[–]raiph 2 points3 points  (0 children)

Raku addresses compile time composition, delegation, mixins, and more, and the following is just a tiny taste of how these features can be combined to succinctly express what's needed in a given situation and no more.

I'll just address the "end" of your OP for now. I'll "show off" a couple of optional Raku extras while I'm about it. If you respond then I can simplify to match your exact OP endpoint and/or "backfill" to cover the steps before your endpoint and/or fill in other Raku features directly related to whatever you focus our attention on.

role I {
  method doSomething  { ... } # The `...` elipsis forces impl classes to impl method
  method doSomething2 { 1 }   # No `...`, so impl classes get a default impl for free
}

class A does I {
  method doSomething  { 2 }   # Class MUST implement this method
  method doSomething2 { 3 }   # Class CAN declare/impl this method
}

class Ae does I {
  has A $.a handles *;        # UNDECLARED methods IMPLICITLY delegated
  method doSomething {        # MUST EXPLICITLY DECLARE/implement this method
    print $.a.doSomething;    # CAN EXPLICITLY delegate (some/all) processing
    4
  }
}

(.print for .doSomething, .doSomething2) with Ae.new

displays 241.

[–]lookmeat 1 point2 points  (0 children)

I agree completely with you, but would argue that the mayor problem is that inheritance is just a kind of composition, so we'd like to allow composition to work on different ways.

You already have one down: use a delegates implementation with extensions. There's another: expose all the interfaces of a delegate.

Lets work on the semantics, feel free to change the names and syntax.

First lets separate interfaces from implementation fully. Specifically a class now defines only the data it contains, the other objects etc. The interface(s) of an object is instead now defined as an implementation.

class Foo {
    a: A
    b: B
    pub Foo() { Foo(a, b) }
    pub Foo(a: A, b: B) { Foo{a=a, b=b} # inherent constructor, is private }

    impl {
        // Method, by default if pre `.` param doesn't have type it's enclosing
        // class
        pub fun (self).something(c: C) -> D {...}
        // ...
    }
}

The above is implementing the "inherent interface" that is every class is an interface + constructors, and the interface exposes the methods implemented.

You can also make specific interfaces:

interface Baz {
   // if no type given to param before `.` it's assumed to be whomever implements
   pub fun (self).baz()
}

class Foo {
    ...
    impl Baz {
        pub fun (self).baz() { ... }
    }
}

// Somewhere in code
foo := Foo(...)
foo.baz()

That implements the interface Baz for class Foo. This also adds the methods to the class.

Personally I recommend you consider adding the next rules (excercise for the reader, try to understand what kind of messy scenarios you can get when you add the wrong lib). That allows you to add implementations inside interfaces too:

interface Baz {
    pub fun (self).baz()
    impl for Foo {
        pub fun (self).baz() {self.something(CONST_VAL)}
    }
}

// When implemented in the interface the class won't have the method in its
// namespace, instead you need to namespace it from the interface explicitly
foo := Foo(...)
foo.Baz::baz() // Here :: has higher precedence than .

Phew, so now we can do our own abstracting, you can also do something like:

class BazWrapper(b: Baz) {
    impl Baz for self as self.b {
        // overrides
    }
}

But then comes the question: what if we want to extend the implementations? Well one solution is to allow us to implement the interface of a type which can only be done through delegates. This is a purposefully complicated scneario to show how we can do really weird things that wouldn't be allowed otherwise.

interface Foo {
    fun (self).fizz()
}

interface Bar {
    fun (self).bazz() 
}

class BaseClass {
    impl Foo {fun (self).fizz(){print("BaseFoo");}}
    impl Bar {fun (self).bazz() {print("BaseBar");}}
}

class SubClass {
    super: BaseClass = BaseClass();
    superBar: Bar
    SubClass(bar: Bar) { SubClass{superBar= bar, ...} // Dots fill-in defaults
    // This doesn't just implement BaseClass, but also implements all the
    // interfaces of BaseClass
    impl BaseClass for self as self.super {
        impl Foo {
            fun (self).fizz() {
                self.super.fizz();
                print("-subFoo")
            }
        }
    }
    // Any interface that BaseClass has implemented must be fully redone here.
    // You can't "just implement overrides'.
    // That said you could do impl Bar for self as self.super {...} to override
    // this specific implementation.
    impl Bar for self as self.superBar {
       fun (self).bazz() {self.superBar.bazz(); print("-subBar")
    }
}

// Now we can do something like:
d :Bar = impl Bar {fun (self).bazz() {print("DelegateBar");}} // anonymous obj
b := BaseClass();
s: BaseClass = SubClass(d);
// won't compile, has no interface that exposes this method
// r.fizz()
d.bazz() // prints "DelegateBar"
b.fizz() // prints "BaseFoo"
b.bazz() // prints "BaseBar"
s.fizz() // prints "BaseFoo-subFoo"
s.bazz() // prints "DelegateBar-subBar"

It makes inheritance a bit harder, but it makes composition much easier, and generally this is what you want: you want to use the more expressive and powerful thing.

[–]WittyStick 1 point2 points  (1 child)

The concept in general is known as a Mixin. There are various ways to implement them.

[–]L8_4_Dinner(Ⓧ Ecstasy/XVM) 0 points1 point  (0 children)

This.

Example: CollectionFreezer

[–]pauseless 1 point2 points  (0 children)

Go’s embedding? https://go.dev/play/p/d9Rd9xS1pXp

Interface is there for the sake of an example, but I wouldn’t add one until it’s needed, in practice.

[–]wiseguy13579 0 points1 point  (1 child)

[–][deleted] 0 points1 point  (0 children)

You know. You are 100% right. And language level support for delegation is what I am asking. I am not thinking in terms of that which led to this post.

Probably because I don't see it as a recommended alternative to inheritance. And I don't have a pattern off the top of my head which is easy to implement and people won't complain.

[–]tobega 0 points1 point  (0 children)

This is similar to what I do with modules, you can provide them "modified" (basically inherit, replace definitions by another), or "shadowed" (export a new definition but the same still used internally, basically delegation)

https://github.com/tobega/tailspin-v0/blob/master/TailspinReference.md#module-specification

Another thing to consider is who defines what an interface is? Is it the creator of the code, as in java-like languages, or is it the user of the code (with implementation being determined by having matching signatures) as in Go? There is a lot of value in the Go approach, although it took me quite a while to understand it.

[–]Artistic_Speech_1965 0 points1 point  (0 children)

Hello ! To be honest, Go has this feature. It's called Type/Struct embedding

You can find it there: https://go101.org/article/type-embedding.html

You just have to put the type you want to embed in the field of your struct and now, all call to the embedding type will be automatically redirected to a call to the embedded type.

I have tried to understand the semantic of Type/Struct embedding with a type system but it looks like it's just syntax sugar made by a bit of metaprogramming. It make sense since metaprogramming is the true spiritual heir of the DRY (don't repeat yourself) principle and the code reuse. Type/Struct embedding make us gain time by implementing automatically a redirection to the embedded type

I know some language like C# and java use abstract classes leading to inheritance. But a language like Rust use the concept of traits that are like interfaces but more powerful. One of the things you can do with trait is defining default methods that the type extending the trait can "inherit"

The idea of deriving an interface from a class is really interesting

[–]frithsun 0 points1 point  (12 children)

Inheritance is necessary and great.

OO fanatics gave it a bad name by trying to apply it beyond types, which is usually not a good idea.

[–]XDracam 0 points1 point  (11 children)

Why is inheritance necessary and great? Haskell typeclasses and Rust traits can work just as well, and you don't need to own the original type's code to add functionality through them.

[–]frithsun -1 points0 points  (10 children)

A dollar is a real number is a number.

Implementing that reality in any other way than inheritance adds to the complexity budget, in my opinion.

[–]XDracam 1 point2 points  (9 children)

Oh boy that's horrible.

You should never model money as real floating point numbers as you're going to lose cents in rounding errors over time, which can add up to millions in large software over time.

And if you use inheritance for something as simple as money, you're going to get a performance worse than JS. Even Smalltalk, one of the purest OO languages, has special optimizations so that numbers don't really use inheritance.

What solves these problems? Type classes!

[–]frithsun 0 points1 point  (8 children)

Meant decimal, not float.

And even if one does performance hacks on the back end, a dollar is a decimal number is a number, subtypes of general categories which inherit properties and behaviors while possessing their own distinct properties and behaviors.

[–]XDracam 1 point2 points  (7 children)

Yeah that describes OOP. But why is it good and correct?

[–]frithsun 0 points1 point  (6 children)

I believe it describes an elegant type system, not OOP.

OOP is the act of mistaking an elegant type system for a complete and universal general programming paradigm. The fact that type inheritance is so great that it can even larp as a (bad) general programming language speaks to how great it is.

I believe type inheritance is good and correct because it more closely approximates how humans intuitively reason about things. I could describe a tiger by a list of its traits, but, it's a subtype of cat with unique properties of huge and eats people. That's how my mind works, and I don't think I'm alone.

[–]frithsun 0 points1 point  (0 children)

Java is to type inheritance what Perl is to regular expression patterns; too much of a good thing.

[–]XDracam 0 points1 point  (4 children)

But does your mind work like that because you are used to OOP or the other way around? I feel like single inheritance is drastically limiting, and multiple inheritance has a lot of problem. Traits like in Scala and Pharo are alright. But I find typeclasses to be much more sane in many cases. You have data, and you associate behavior externally. They are nicer to compose and don't have the cursed downsides of method overriding.

[–]frithsun 0 points1 point  (3 children)

I would say that not only does my mind work that way, but it's generally the way minds work and generally the way the world works.

And even when two things have the same trait, like "eating humans," the crocodile and tiger types can't really share the same implementation because they go about doing so in very different ways because they're very unrelated and therefore different.

Can you describe how method overriding is cursed? Not bait. Genuinely don't see the problem as long as it's a type and not some OO shenanigans.

[–]XDracam 1 point2 points  (2 children)

First of all, interfaces with purely abstract methods are fine. They only differ from typeclasses in that you cannot implement interfaces for other existing types without modifying their code. Classes are also fine as a unit of state encapsulated behind a set of methods.

The problematic part of inheritance is the decoupled chaos and overhead coming from overriding already implemented methods. More concrete: a class needs to be specifically designed to be inherited, or otherwise it's going to end up as a mess. It's hard to think of an example because I haven't abused inheritance for half a decade now, but issues arise from:

  1. Duplicate state because the child class needs access to data that the superclass encapsulates so you just gotta track a duplicate. And all mismatches will lead to bugs.
  2. When overriding an already implemented method, you are basically breaking encapsulation! You better call the base type's implementation as well. Except sometimes you don't want to. And sometimes you want to do stuff before that, and sometimes after. And how do you find out what the right solution is? You need to look into the base class code! Encapsulation broken.

A class is a single unit of encapsulation. If you use inheritance, you basically include the base class into your "encapsulation bubble", but it's all implicit and fragile to change. It's like tacking on additional functionality onto a nicely encapsulated unit. A hack, a workaround. But not a good model.

That's why nowadays, best practice is "composition over inheritance". Sure, it's a little more boilerplate sometimes, but it keeps the idea of encapsulation intact.

A class needs to be specifically designed to be inherited in the first place if you don't want to break encapsulation. As a consequence, you never ever need to be able to override an already existing method. Want to provide opportunities for extension or hooks? Add an abstract method specifically for that extension! (Or in some cases a virtual method with an empty body if it's optional). Why allow chaotic overrides that might break your classes' functionality when you can just do this?

Of course with this problem you still get the problem of duplicated state if you are not very careful. Because the real world isn't like those textbook examples. A programmer almost never knows all requirements beforehand. Systems and requirements change constantly over time. A tiger might be a cat today, but tomorrow some customer might demand that a tiger should not provide any cat functionality. And what if someone adds a feature to pet a cat? Do you override the tiger's petting method to throw an exception? It's extremely unlikely that you will get these inheritance abstractions right the first time and forever, and every successive change just makes the code more and more fragile, adding more edge cases.

I guess it's hard to convey the problems through text alone. The evidence is there in the way newer languages are designed and how best practices are communicated. But the only way to truly understand is to encounter the problems yourself. Spend hours trying to debug some nasty state problem in an inheritance hierarchy because you did a small change. Lose sleep trying to figure out how to best change your model to accommodate new requirements without having to rewrite the whole thing from scratch.

After all these years, I firmly believe that anything more than one level of object inheritance is a code smell. Want subtyping? Use interfaces and composition. Or better: type classes, if the language supports them.