Implementing sub-types

43 views
Skip to first unread message

Matthew Browne

unread,
Dec 5, 2016, 9:10:26 AM12/5/16
to object-co...@googlegroups.com
I'm curious to know if anyone here has developed or researched new ways
of subtyping data objects and Contexts. In the trygve language there is
a carefully limited form of inheritance for classes, but there was still
wide disagreement the last time we discussed it as to whether a new DCI
language should include inheritance at all. Also, a DCI data model can
include both simple data classes and Contexts (e.g. the Account context
in the money transfer example). So it seems we need a way of subtyping /
extending contexts and I'm sure I'm not alone in being wary of using
inheritance for that. I don't know whether the answer is forwarding,
prototypes, or something else, but this seems to be an area in need of
attention.

Cheers,
Matt

Egon Elbre

unread,
Dec 5, 2016, 11:00:04 AM12/5/16
to object-composition
Can you give some real-world examples for it?

I've found that solutions that do not use inheritance tend to be easier to analyse and understand. But, occasionally they tend to be more verbose.

+ Egon

Matthew Browne

unread,
Dec 5, 2016, 11:24:22 AM12/5/16
to object-co...@googlegroups.com

Sure...for Contexts, I was thinking of something like Cope's money transfer example where CheckingAccount, SavingsAccount, and InvestmentAccount are subtypes of Account. If Account were implemented as a DCI Context wrapping a collection of ledgers like it is in other versions such as Rune's, then how would one create the subtypes?

I was thinking more in terms of overall strategies for managing subtyping rather than solving a particular problem in a real project, but I can think of other examples if needed. I'm not currently facing this as a real-world problem, just anticipating that it's something I or others might encounter in the future.

(Actually there was a more esoteric case I encountered on a real project where I thought Context extension might be helpful, but that was a special case involving code management for 2 very similar systems with a very similar use case that had slight differences in each system. But that's not what I was thinking of when I raised this question...and I'm less certain of whether subtyping was really needed there or if there was a better solution.)

--
You received this message because you are subscribed to the Google Groups "object-composition" group.
To unsubscribe from this group and stop receiving emails from it, send an email to object-composit...@googlegroups.com.
To post to this group, send email to object-co...@googlegroups.com.
Visit this group at https://groups.google.com/group/object-composition.
For more options, visit https://groups.google.com/d/optout.

rune funch

unread,
Dec 5, 2016, 11:37:16 AM12/5/16
to object-co...@googlegroups.com
The discussion on whether subtyping is needed or not has been one of those in favour presenting "real world examples" and those against showing how to do that in question without subtypes. I have yet to encounter any situation where I'd need subtyping on a context level. So a real world problem that shows why it would be needed would probably be a good idea

To unsubscribe from this group and stop receiving emails from it, send an email to object-composition+unsub...@googlegroups.com.
To post to this group, send email to object-composition@googlegroups.com.

--
You received this message because you are subscribed to the Google Groups "object-composition" group.
To unsubscribe from this group and stop receiving emails from it, send an email to object-composition+unsub...@googlegroups.com.
To post to this group, send email to object-composition@googlegroups.com.

Egon Elbre

unread,
Dec 5, 2016, 11:37:21 AM12/5/16
to object-composition
On Monday, 5 December 2016 18:24:22 UTC+2, Matthew Browne wrote:

Sure...for Contexts, I was thinking of something like Cope's money transfer example where CheckingAccount, SavingsAccount, and InvestmentAccount are subtypes of Account. If Account were implemented as a DCI Context wrapping a collection of ledgers like it is in other versions such as Rune's, then how would one create the subtypes?


I would consider two possible solutions:

1. private member "Ledger"
2. private role "Ledger", which is played by some Ledger implementation.

To ensure that specific Account can be varied -- use interface Account or role Account in the parent context.

Of course depending on the situation, there can be other solutions. Where one might be embedding/composing multiple classes together.

Matthew Browne

unread,
Dec 5, 2016, 12:01:54 PM12/5/16
to object-co...@googlegroups.com

To ensure that specific Account can be varied -- use interface Account or role Account in the parent context.

Hmm, perhaps that's all that's needed for Contexts. (It still leaves the question of whether there should be a separate construct for simple data objects - i.e. do we need classes - and whether inheritance should be available for those, but let's focus on contexts for the moment.)

I just tried out creating a Context in trygve that implements an interface and it works. (I either forgot about that or didn't realize that feature had been added - thanks Cope!)

To implement a type hierarchy that goes more than 2 levels deep, I suppose a Context could just implement all the relevant interfaces, e.g.:

context MyContext implements ParentInterface, SomeInterface

...so that you could do this:

MyContext c = new MyContext();
c instanceof ParentInterface; //true
c instanceof SomeInterface; //true


(There doesn't seem to be an instanceof operator in trygve, but the above is just intended to show the idea.)

Does that sound like a good approach?

Thanks,
Matt

James O Coplien

unread,
Dec 5, 2016, 12:24:57 PM12/5/16
to object-co...@googlegroups.com
My feeling is that inheritance of Contexts would split the use case among too many modules. One could no longer understand use cases locally.

Contexts currently  implement abstraction boundaries (boundaries beyond which I have no business knowing how the implementor did things). One Context can create an instance of another and, if the Contexts within such a chain are well-designed, it provides another layer of compression at the system level.

If you were to inherit one Context from another, then Contexts would no longer really implement an abstraction boundary, since parts of two or more Contexts will be combined in a single object. Objects should not hide some of their internals from other of their internals, in my opinion. The Context would no longer be a recursion on the concept of a computer.

Indeed one should probably instead ask why inheritance is still allows between classes, it’s because they’re pretty simple, and it ends being useful without being confusing, if tastefully used. Classes should reflect local structure that basically remember pretty simple, low-level information. If there is more processing involved then Contexts should be used instead, and they can help partition complex scenarios into multiple system operations.

Were we to do away with classes and replace them with Contexts, we’d probably need to introduce Context inheritance to restore the compressing expressiveness of classes, but that violates the “abstraction boundary rule.” So I think we’re stuck with both.

If someone comes up with a good example I can certainly try building a version of trygve with Context inheritance. Or maybe someone else wants to take a shot at it.

Matthew Browne

unread,
Dec 5, 2016, 12:49:39 PM12/5/16
to object-co...@googlegroups.com

Yes, inheritance (or any kind of implicit extension) of Contexts is a slippery slope - thanks for the explanation. While I can see potential uses for it in the data model (e.g. the Account example), it could be risky to allow it for typical use case Contexts (e.g. the MoneyTransfer context), and I'm not sure how you would allow the former while preventing the use of the latter.

On the other hand, I also think Rune (over the course of past discussions) has made a compelling case for doing away with classes and using contexts to define all objects with the exception of immutable data objects.



On 12/5/16 12:24 PM, James O Coplien wrote:

So I think we’re stuck with both.

Well, there are alternatives to inheritance that would perhaps be suitable for Contexts in ways that inheritance is not...like maybe embeding/composition as Egon suggested as a possible alternative...or some way to make implementing multiple interfaces for related types more convenient (maybe not quite as convenient as "Car extends Vehicle" but close). The hard part would be to keep it "useful without being confusing, if tastefully used". Something to think about...

Egon Elbre

unread,
Dec 5, 2016, 2:06:59 PM12/5/16
to object-composition


On Monday, 5 December 2016 19:01:54 UTC+2, Matthew Browne wrote:

To ensure that specific Account can be varied -- use interface Account or role Account in the parent context.
Hmm, perhaps that's all that's needed for Contexts. (It still leaves the question of whether there should be a separate construct for simple data objects - i.e. do we need classes - and whether inheritance should be available for those, but let's focus on contexts for the moment.)

I just tried out creating a Context in trygve that implements an interface and it works. (I either forgot about that or didn't realize that feature had been added - thanks Cope!)

To implement a type hierarchy that goes more than 2 levels deep, I suppose a Context could just implement all the relevant interfaces, e.g.:

context MyContext implements ParentInterface, SomeInterface

...so that you could do this:

MyContext c = new MyContext();
c instanceof ParentInterface; //true
c instanceof SomeInterface; //true


(There doesn't seem to be an instanceof operator in trygve, but the above is just intended to show the idea.)

Does that sound like a good approach?

I would prefer implicit interfaces, but this version would work as well, just a little bit more annoying.

Matthew Browne

unread,
Dec 5, 2016, 2:19:16 PM12/5/16
to object-co...@googlegroups.com

Yes, I was just going to add...we already have good ways of implementing abstraction boundaries, as Cope pointed out. The only thing really lacking in terms of the end result is that you don't automatically get parent types and sub-types like you do when extending classes. I think your suggestion of using interfaces is a fine solution; the main reason to consider alternatives would be concerns about syntax or convenience.

This would probably be a bigger concern for a new language that did away with classes entirely. In terms of the approach of the trygve language, the current features seem sufficient to get the job done, although some form of implicit interfaces might make it a bit easier. Maybe it would help if one interface could inherit from another?

Matthew Browne

unread,
Dec 5, 2016, 2:26:44 PM12/5/16
to object-co...@googlegroups.com

On 12/5/16 2:19 PM, Matthew Browne wrote:

In terms of the approach of the trygve language, the current features seem sufficient to get the job done, although some form of implicit interfaces might make it a bit easier. Maybe it would help if one interface could inherit from another?

I should have made it clearer that these are two separate, orthogonal ideas.

Egon Elbre

unread,
Dec 5, 2016, 2:36:50 PM12/5/16
to object-composition


On Monday, 5 December 2016 21:19:16 UTC+2, Matthew Browne wrote:

Yes, I was just going to add...we already have good ways of implementing abstraction boundaries, as Cope pointed out. The only thing really lacking in terms of the end result is that you don't automatically get parent types and sub-types like you do when extending classes. I think your suggestion of using interfaces is a fine solution; the main reason to consider alternatives would be concerns about syntax or convenience.

This would probably be a bigger concern for a new language that did away with classes entirely. In terms of the approach of the trygve language, the current features seem sufficient to get the job done, although some form of implicit interfaces might make it a bit easier. Maybe it would help if one interface could inherit from another?


See how Go implements them, I think it got interfaces right.

It uses embedding to contain other interfaces:

type SourceAccount interface {
Withdraw(amount int)
}

type DestinationAccount interface {
Deposit(amount int)
}

type Account interface {
SourceAccount
DestinationAccount

Print()
}

To implement all these interfaces you would need to do:

type MockAccount struct { ... }
func (acc *MockAccount) Withdraw(amount int) { ... }
func (acc *MockAccount) Deposit(amount int) { ... }
func (acc *MockAccount) Print() { ... }

Matthew Browne

unread,
Dec 5, 2016, 2:44:58 PM12/5/16
to object-co...@googlegroups.com
On 12/5/16 2:36 PM, Egon Elbre wrote:

To implement all these interfaces you would need to do:

type MockAccount struct { ... }
func (acc *MockAccount) Withdraw(amount int) { ... }
func (acc *MockAccount) Deposit(amount int) { ... }
func (acc *MockAccount) Print() { ... }
I don't quite follow...I'm guessing the idea is that MockAccount implements the Account interface, but I don't get that from the code (or would that be part of the "..."?).

Matthew Browne

unread,
Dec 5, 2016, 2:47:58 PM12/5/16
to object-co...@googlegroups.com

Never mind, I just remembered that in Go, as long as a type implements all the methods in an interface, then it's automatically considered to be of that interface type (without needing to explicitly specify that it implements the interface with an "implements" keyword or anything like that). Kind of like the duck typing of role players in trygve. Did I get that right?

Egon Elbre

unread,
Dec 5, 2016, 2:55:13 PM12/5/16
to object-composition
On Monday, 5 December 2016 21:47:58 UTC+2, Matthew Browne wrote:

Never mind, I just remembered that in Go, as long as a type implements all the methods in an interface, then it's automatically considered to be of that interface type (without needing to explicitly specify that it implements the interface with an "implements" keyword or anything like that). Kind of like the duck typing of role players in trygve. Did I get that right?


Yes.

Also, when you do want to make it explicit you can declare a local variable with the appropriate type and assignment, e.g.

var _ Account = &MockAccount{} // the underscore is an unnamed variable

This sometimes helps to get better error-messages... or catch the errors nearer the implementation.

James O Coplien

unread,
Dec 6, 2016, 2:55:25 AM12/6/16
to object-co...@googlegroups.com

On 5 Dec 2016, at 20.55, Egon Elbre <egon...@gmail.com> wrote:

This sometimes helps to get better error-messages... or catch the errors nearer the implementation.

People keep forgetting how important error messages are.

And too many is as bad as too few. But stumbling, with cascading error messages far from the point of sin, makes error messages almost useless. So this is a good insight.

Matthew Browne

unread,
Dec 6, 2016, 10:15:07 AM12/6/16
to object-co...@googlegroups.com

I realized that I was thinking of something different in terms of "implicit interfaces" than what Egon meant. Egon's usage is the more common and logical use of the term, so let me describe what I was imagining, which is something different (I was thinking about how implicit/explicit the implementation of the interface would be...I'll avoid the misuse of the term "implicit interfaces" in the future).

The below pseudocode is just one of many possible ways to facilitate subtyping of Contexts:

interface AccountInterface {
    public void increaseBalance(Currency amt);
    public void decreaseBalance(Currency amt);
    public Currency getBalance();
}

context GeneralLedgerAccount implements AccountInterface {
    ...
}

context SavingsAccount subtypeOf GeneralLedgerAccount {
    public void increaseBalance: Account.increaseBalance;
    public void decreaseBalance: Account.decreaseBalance;
    public Currency availableBalance: Account.availableBalance;
   
    public SavingsAccount() {
        Account = new GeneralLedgerAccount();
    }
   
    role Account {
        public void increaseBalance(Currency amt);
        public void decreaseBalance(Currency amt);
        public Currency availableBalance();
    }
    requires {
        void increaseBalance(Currency amt);
        void decreaseBalance(Currency amt);
        Currency availableBalance();
    }
   
    ...
}

The idea here is that the programmer can tell the compiler to create forwarding methods automatically (first 3 lines of SavingsAccount forward calls to increaseBalance, decreaseBalance, and availableBalance to the Account role player). And instances of SavingsAccount would also be instances of AccountInterface and GeneralLegerAccount.

Note: The AccountInterface is used to stay in keeping with Cope's example where Account is an abstract type; that's why I called the concrete implementation GeneralLedgerAccount, with the idea being that SavingsAccount and CheckingAccount are both subtypes of GeneralLedgerAccount. I don't have much domain knowledge of banking so I don't know if this structure makes sense, but hopefully it's sufficient for discussion purposes.

The above is still a little verbose and we could go one step further, e.g.:

context SavingsAccount subtypeOf GeneralLedgerAccount {
    expose Account {increaseBalance, decreaseBalance, availableBalance);
    ...
}

As I mentioned earlier, I'm thinking at this point mainly of convenience and reducing the amount of boilerplate code required to achieve this sort of thing. The semantics would need refinement and careful consideration of course, but what do you all think?

Since this feature wouldn't actually allow you to accomplish anything that you can't already accomplish with more verbose code in trygve, I'm unsure whether or not it would be worth implementing in trygve since it's a research language. But I do think that at least for a production language, facilitating the subtyping of contexts with something similar to the above would be useful.

Egon Elbre

unread,
Dec 6, 2016, 1:11:34 PM12/6/16
to object-composition
This is how you would do it in Go:

type Account interface {
IncreaseBalance(amount Currency)
DecreaseBalance(amount Currency)
AvailableBalance() Currency
}

type SavingsAccount struct {
Account
...
}

+ Egon

Matthew Browne

unread,
Dec 6, 2016, 1:20:24 PM12/6/16
to object-co...@googlegroups.com
On 12/6/16 1:11 PM, Egon Elbre wrote:
This is how you would do it in Go:

type Account interface {
IncreaseBalance(amount Currency)
DecreaseBalance(amount Currency)
AvailableBalance() Currency
}

type SavingsAccount struct {
Account
...
}

That's nicely concise. But in Go, if you wanted to call IncreaseBalance() on a SavingsAccount, I think you would have to do this:

mySavingsAccount.Account.IncreaseBalance(someAmount);

Is that right? I would rather be able to just call mySavingsAccount.IncreaseBalance(someAmount) - it maintains the abstraction boundary better (i.e. it doesn't require knowledge of the internal structure of a SavingsAccount object).

Egon Elbre

unread,
Dec 6, 2016, 1:34:36 PM12/6/16
to object-composition
You can use either:

acc := &SavingsAccount{...}
acc.IncreaseBalance(10)
acc.Account.IncreaseBalance(10)

+ Egon

Matthew Browne

unread,
Dec 6, 2016, 1:47:26 PM12/6/16
to object-co...@googlegroups.com
On 12/6/16 1:34 PM, Egon Elbre wrote:
You can use either:

acc := &SavingsAccount{...}
acc.IncreaseBalance(10)
acc.Account.IncreaseBalance(10)
Ah, thanks for the clarification. This brings up the question of name conflicts, but I just found the answer to how Go handles that in the manual:
Second, if the same name appears at the same nesting level, it is usually an error; it would be erroneous to embed log.Logger if the Job struct contained another field or method called Logger. However, if the duplicate name is never mentioned in the program outside the type definition, it is OK. This qualification provides some protection against changes made to types embedded from outside; there is no problem if a field is added that conflicts with another field in another subtype if neither field is ever used.
And there are also alternative ways that name conflicts could be handled (e.g. the way name conflicts are handled in the implementation of traits in some languages).

In any case, whether going with the more implicit approach of Go or a somewhat more explicit approach, I think we have some good ideas here for creating subtypes of Contexts without introducing the problems of traditional inheritance.
Reply all
Reply to author
Forward
0 new messages