Polymorphism support (Must implement function if included an interface) #307

Closed
opened 2021-04-14 10:40:29 +09:00 by zxq9 · 28 comments
zxq9 commented 2021-04-14 10:40:29 +09:00 (Migrated from gitlab.com)

Created by: VitalJeevanjot

Greetings,

I was wondering if there is a way to implement interfaces in Sophia so we can work on standards in a more appropriate way. As discussed here https://forum.aeternity.com/t/how-to-write-interfaces-in-sophia/8236/6 .

Best,

*Created by: VitalJeevanjot* Greetings, I was wondering if there is a way to implement interfaces in Sophia so we can work on standards in a more appropriate way. As discussed here https://forum.aeternity.com/t/how-to-write-interfaces-in-sophia/8236/6 . Best,
zxq9 commented 2021-04-14 15:58:05 +09:00 (Migrated from gitlab.com)

Created by: radrow

I see it as something like

contract interface Strokable =
  entrypoint stroke : () => unit

contract Cat : Strokable =
  @impl 
  entrypoint stroke() = abort("not today")

or even

contract Cat =
  impl Strokable =
    entrypoint stroke() = abort("not today")
  entrypoint ...

or evener

contract Cat =
  entrypoint ...

impl Cat for Strokable =
  entrypoint stroke() = abort("not today")

The question is how deep do we want to dive into it:

  • Do we want to let interfaces be subinterfaces of the others (smell Java)
  • What about multiple inheritance?
  • Should the type system treat it in Rust/Haskellish way as a typeclass or in OOP manner with subtyping?
*Created by: radrow* I see it as something like ``` contract interface Strokable = entrypoint stroke : () => unit contract Cat : Strokable = @impl entrypoint stroke() = abort("not today") ``` or even ``` contract Cat = impl Strokable = entrypoint stroke() = abort("not today") entrypoint ... ``` or evener ``` contract Cat = entrypoint ... impl Cat for Strokable = entrypoint stroke() = abort("not today") ``` The question is how deep do we want to dive into it: - Do we want to let interfaces be subinterfaces of the others (smell Java) - What about multiple inheritance? - Should the type system treat it in Rust/Haskellish way as a typeclass or in OOP manner with subtyping?
zxq9 commented 2021-04-14 19:11:38 +09:00 (Migrated from gitlab.com)

Created by: VitalJeevanjot

Thank you for looking into it, My view on your questions:

-> I was not sure about this but could be helpful in the case of extended standards (Including a function that was not in the original interface, for e.g. adding safeTransfer if not in default ERC721) so should result in a cleaner code & better understanding.
-> This might be helpful in writing cleaner modules (classes) and not deriving every class before moving on (Since you asked so I assume that currently, Sophia does not support multiple inheritance).
-> Whatever you prefer I can adapt to it.

Best,

*Created by: VitalJeevanjot* Thank you for looking into it, My view on your questions: -> I was not sure about this but could be helpful in the case of extended standards (Including a function that was not in the original interface, for e.g. adding `safeTransfer` if not in default ERC721) so should result in a cleaner code & better understanding. -> This might be helpful in writing cleaner modules (classes) and not deriving every class before moving on (Since you asked so I assume that currently, Sophia does not support multiple inheritance). -> Whatever you prefer I can adapt to it. Best,
zxq9 commented 2021-04-14 19:33:06 +09:00 (Migrated from gitlab.com)

Created by: marc0olo

I personally like this kind of style better:

contract interface Strokable =
  entrypoint stroke : () => unit

contract Cat : Strokable =
  @impl 
  entrypoint stroke() = abort("not today")

whereas the compilation should fail if the implementation of stroke() is missing in the contract.

  • we should definitely provide multiple inheritance for interfaces
  • subinterfaces would be great but I don't know whether this is a bit contrary to the current state of Sophia where we don't have contract inheritance (https://gitlab.com/gajumaristas/aesophia/-/issues/198)
    • this should probably be introduced together
  • I have no preference in regards to the type system, but probably the rust/haskell way fits Sophia better
*Created by: marc0olo* I personally like this kind of style better: ``` contract interface Strokable = entrypoint stroke : () => unit contract Cat : Strokable = @impl entrypoint stroke() = abort("not today") ``` whereas the compilation should fail if the implementation of `stroke()` is missing in the contract. - we should definitely provide multiple inheritance for interfaces - subinterfaces would be great but I don't know whether this is a bit contrary to the current state of Sophia where we don't have contract inheritance (https://gitlab.com/gajumaristas/aesophia/-/issues/198) - this should probably be introduced together - I have no preference in regards to the type system, but probably the rust/haskell way fits Sophia better
arpunk commented 2021-04-16 11:35:36 +09:00 (Migrated from gitlab.com)

I might be biased, but what about:

interface Strokable =
  callback stroke : () => unit

interface Other =
  callback other : () => unit

contract Cat =
  @impl Strokable
  entrypoint stroke() = abort("not today")
  
  @impl Other
  entrypoint other() = about("probably tomorrow")

As for the questions:

Do we want to let interfaces be subinterfaces of the others (smell Java)

I think this will lead to many layered interfaces. Probably keeping it flat enforces better callback design.

What about multiple inheritance?

It would be nice if the compiler warns on missing entrypoints, specially if I plan to expand on a set of contracts/interfaces/behaviours/etc.

I might be biased, but what about: ``` interface Strokable = callback stroke : () => unit interface Other = callback other : () => unit contract Cat = @impl Strokable entrypoint stroke() = abort("not today") @impl Other entrypoint other() = about("probably tomorrow") ``` As for the questions: > Do we want to let interfaces be subinterfaces of the others (smell Java) I think this will lead to many layered interfaces. Probably keeping it flat enforces better callback design. > What about multiple inheritance? It would be nice if the compiler warns on missing entrypoints, specially if I plan to expand on a set of contracts/interfaces/behaviours/etc.
zxq9 commented 2021-04-27 18:23:45 +09:00 (Migrated from gitlab.com)

Created by: radrow

This is going to be extremely helpful with the upcoming contract factories

*Created by: radrow* This is going to be extremely helpful with the upcoming contract factories
zxq9 commented 2021-09-16 19:55:27 +09:00 (Migrated from gitlab.com)

Created by: marc0olo

is there a plan to address this issue? @hanssv @ghallak

I think it would be good to address this along with #198

*Created by: marc0olo* is there a plan to address this issue? @hanssv @ghallak I think it would be good to address this along with #198
zxq9 commented 2021-09-16 20:45:47 +09:00 (Migrated from gitlab.com)

Created by: hanssv

I don't know of any plans. It is two rather big pieces of work and I'm not sure it is the best bang for the buck but it would be nice to have for sure ;-)

*Created by: hanssv* I don't know of any plans. It is two rather big pieces of work and I'm not sure it is the best bang for the buck but it would be nice to have for sure ;-)
zxq9 commented 2021-09-16 21:02:59 +09:00 (Migrated from gitlab.com)

Created by: marc0olo

at least there were requests from the community in that regards in the past. what are more important issues in your opinion right now? just wondering :-)

would definitely be good to have these features

*Created by: marc0olo* at least there were requests from the community in that regards in the past. what are more important issues in your opinion right now? just wondering :-) would definitely be good to have these features
zxq9 commented 2021-09-21 19:15:38 +09:00 (Migrated from gitlab.com)

Created by: radrow

Note that you can hack it out by making unsafe contract coercions. Just deploy an identity contract and declare it as desired cast in your main contract. Then you can remote call it whenever you need dynamic casting.

This is dirty, but could serve as a polymorphism replacement until it is done.

*Created by: radrow* Note that you can hack it out by making unsafe contract coercions. Just deploy an identity contract and declare it as desired cast in your main contract. Then you can remote call it whenever you need dynamic casting. This is dirty, but could serve as a polymorphism replacement until it is done.
zxq9 commented 2021-09-21 19:34:31 +09:00 (Migrated from gitlab.com)

Created by: marc0olo

Note that you can hack it out by making unsafe contract coercions. Just deploy an identity contract and declare it as desired cast in your main contract. Then you can remote call it whenever you need dynamic casting.

This is dirty, but could serve as a polymorphism replacement until it is done.

That sounds interesting, but I guess we can wait for the final solution here. We should stop working around everything IMO :)

But definitely good to know if somebody wants to try it like you described.

*Created by: marc0olo* > Note that you can hack it out by making unsafe contract coercions. Just deploy an identity contract and declare it as desired cast in your main contract. Then you can remote call it whenever you need dynamic casting. > > This is dirty, but could serve as a polymorphism replacement until it is done. That sounds interesting, but I guess we can wait for the final solution here. We should stop working around everything IMO :) But definitely good to know if somebody wants to try it like you described.
ghallak commented 2021-11-10 03:06:31 +09:00 (Migrated from gitlab.com)

Proposal 1: Write the implementations along other functions, but precede implementations with @impl and declare that the contract implements the interface.

contract interface Strokable =
  entrypoint stroke : () => unit

contract Cat : Strokable =
  @impl entrypoint stroke() = abort("not today")

Please react with 👍 to this comment if you support this proposal.

**Proposal 1**: Write the implementations along other functions, but precede implementations with `@impl` and declare that the contract implements the interface. ``` contract interface Strokable = entrypoint stroke : () => unit contract Cat : Strokable = @impl entrypoint stroke() = abort("not today") ``` Please react with :+1: to this comment if you support this proposal.
ghallak commented 2021-11-10 03:09:48 +09:00 (Migrated from gitlab.com)

Proposal 1.1: If we decide to go with Proposal 1, should we use @impl or impl

@impl entrypoint stroke() = abort("not today")
impl entrypoint stroke() = abort("not today")

Please react with ❤️ to this comment if you support @impl or with 🎉 if you support impl.

**Proposal 1.1:** If we decide to go with **Proposal 1**, should we use `@impl `or `impl` ``` @impl entrypoint stroke() = abort("not today") ``` ``` impl entrypoint stroke() = abort("not today") ``` Please react with :heart: to this comment if you support `@impl` or with :tada: if you support `impl`.
ghallak commented 2021-11-10 03:14:27 +09:00 (Migrated from gitlab.com)

Proposal 1.2: If we decide to go with Proposal 1, should we use contract Cat is Strokable or contract Cat : Strokable

Please react with ❤️ to this comment if you support contract Cat is Strokable or with 🎉 if you support contract Cat : Strokable.

**Proposal 1.2:** If we decide to go with **Proposal 1**, should we use `contract Cat is Strokable` or `contract Cat : Strokable` Please react with :heart: to this comment if you support `contract Cat is Strokable` or with :tada: if you support `contract Cat : Strokable`.
ghallak commented 2021-11-10 03:15:07 +09:00 (Migrated from gitlab.com)

Proposal 2: Write the implementations in a separate block inside the contract

contract interface Strokable =
  entrypoint stroke : () => unit

contract Cat =
  impl Strokable =
    entrypoint stroke() = abort("not today")
  entrypoint ...

Please react with 👍 to this comment if you support this proposal.

**Proposal 2:** Write the implementations in a separate block inside the contract ``` contract interface Strokable = entrypoint stroke : () => unit contract Cat = impl Strokable = entrypoint stroke() = abort("not today") entrypoint ... ``` Please react with :+1: to this comment if you support this proposal.
ghallak commented 2021-11-10 03:15:36 +09:00 (Migrated from gitlab.com)

Proposal 3: Write the implementations separately outside the contract

contract interface Strokable =
  entrypoint stroke : () => unit

contract Cat =
  entrypoint ...

impl Cat for Strokable =
  entrypoint stroke() = abort("not today")

Please react with 👍 to this comment if you support this proposal.

**Proposal 3:** Write the implementations separately outside the contract ``` contract interface Strokable = entrypoint stroke : () => unit contract Cat = entrypoint ... impl Cat for Strokable = entrypoint stroke() = abort("not today") ``` Please react with :+1: to this comment if you support this proposal.
zxq9 commented 2021-11-10 04:19:19 +09:00 (Migrated from gitlab.com)

Created by: UlfNorell

Why do you want special syntax for the interface entrypoints? The most natural thing (looking at Java for instance), would be to just write

contract interface Strokable =
  entrypoint stroke : () => unit

contract Cat : Strokable =
  entrypoint stroke() = abort("not today")
*Created by: UlfNorell* Why do you want special syntax for the interface entrypoints? The most natural thing (looking at Java for instance), would be to just write ``` contract interface Strokable = entrypoint stroke : () => unit contract Cat : Strokable = entrypoint stroke() = abort("not today") ```
zxq9 commented 2021-11-10 04:28:57 +09:00 (Migrated from gitlab.com)

Created by: marc0olo

Why do you want special syntax for the interface entrypoints? The most natural thing (looking at Java for instance), would be to just write

contract interface Strokable =
  entrypoint stroke : () => unit

contract Cat : Strokable =
  entrypoint stroke() = abort("not today")

actually I agree on that and I see Proposal 1 is fitting this mostly but additionally has the @impl annotation. actually I thought of @impl to be the same like @Override annotation in Java where you could also have the reverse check if it really implements an interface or not.

already commented here:

*Created by: marc0olo* > Why do you want special syntax for the interface entrypoints? The most natural thing (looking at Java for instance), would be to just write > > ``` > contract interface Strokable = > entrypoint stroke : () => unit > > contract Cat : Strokable = > entrypoint stroke() = abort("not today") > ``` actually I agree on that and I see **Proposal 1** is fitting this mostly but additionally has the `@impl` annotation. actually I thought of `@impl` to be the same like `@Override` annotation in Java where you could also have the reverse check if it really implements an interface or not. already commented here: - https://gitlab.com/gajumaristas/aesophia/-/issues/307#issuecomment-819415414
zxq9 commented 2021-11-10 04:34:22 +09:00 (Migrated from gitlab.com)

Created by: thepiwo

I think @ annotations shouldn't be part of logic, just information for other systems, e.g. as pragma.
The proposal of Ulf sounds fine, also Proposal 2 (with full implementation, instead of impl) sound good.

*Created by: thepiwo* I think @ annotations shouldn't be part of logic, just information for other systems, e.g. as pragma. The proposal of Ulf sounds fine, also Proposal 2 (with full implementation, instead of impl) sound good.
zxq9 commented 2021-11-10 04:43:12 +09:00 (Migrated from gitlab.com)

Created by: UlfNorell

If I'm not mistaken, the @override annotation in java is there to prevent errors where you misspell the name of a method in a super class and the compiler can't know if you meant to override it or just declare a new method. In this case, that's not an issue because if you define the strokke entrypoint instead of stroke you'd get an error from not having defined all the methods of the interface.

*Created by: UlfNorell* If I'm not mistaken, the `@override` annotation in java is there to prevent errors where you misspell the name of a method in a super class and the compiler can't know if you meant to override it or just declare a new method. In this case, that's not an issue because if you define the `strokke` entrypoint instead of `stroke` you'd get an error from not having defined all the methods of the interface.
zxq9 commented 2021-11-10 05:23:19 +09:00 (Migrated from gitlab.com)

Created by: marc0olo

If I'm not mistaken, the @override annotation in java is there to prevent errors where you misspell the name of a method in a super class and the compiler can't know if you meant to override it or just declare a new method.

I think it's used for both, superclasses and interfaces. but I am sometimes mixing things up and you might be correct 😅

generally see this topic also closely related to contract inheritance but that's another kind of story.

*Created by: marc0olo* > If I'm not mistaken, the `@override` annotation in java is there to prevent errors where you misspell the name of a method in a super class and the compiler can't know if you meant to override it or just declare a new method. I think it's used for both, superclasses and interfaces. but I am sometimes mixing things up and you might be correct 😅 generally see this topic also closely related to contract inheritance but that's another kind of story.
zxq9 commented 2021-11-10 23:30:16 +09:00 (Migrated from gitlab.com)

Created by: VitalJeevanjot

Proposals 2 and 3 introduce a scaled approach to implement multiple interfaces without confusion.
Voted for 2nd because 3rd seems very separate from other basic languages (harder for traditional people to catch) but a much cleaner approach.

It will be great if you can also share the examples If the interfaces are declared in a separate file.

*Created by: VitalJeevanjot* Proposals **2** and **3** introduce a scaled approach to implement multiple interfaces without confusion. Voted for **2**nd because **3**rd seems very separate from other basic languages (harder for traditional people to catch) but a much cleaner approach. It will be great if you can also share the examples If the interfaces are declared in a separate file.
zxq9 commented 2021-11-11 04:15:40 +09:00 (Migrated from gitlab.com)

Created by: hanssv

I agree with Ulf, no need to introduce extra keywords - keep it simple 🙏

*Created by: hanssv* I agree with Ulf, no need to introduce extra keywords - keep it simple 🙏
zxq9 commented 2021-11-11 04:30:42 +09:00 (Migrated from gitlab.com)

Created by: the-icarus

Also agree with @UlfNorell but would suggest to drop the contract prior to interface. Other languages also just use the single interface keyword to define an interface (e.g. Java,C#,Solidity). Why should we have contract interface?

*Created by: the-icarus* Also agree with @UlfNorell but would suggest to drop the `contract` prior to `interface`. Other languages also just use the single `interface` keyword to define an interface (e.g. Java,C#,Solidity). Why should we have `contract interface`?
zxq9 commented 2021-11-11 18:16:07 +09:00 (Migrated from gitlab.com)

Created by: nikita-fuchs

Also agree with @UlfNorell but would suggest to drop the contract prior to interface. Other languages also just use the single interface keyword to define an interface (e.g. Java,C#,Solidity). Why should we have contract interface?

Absolutely agree with this one and the rest vouching for simplicity and no @ decorators.

 interface Strokable =
  entrypoint stroke : () => unit

contract Cat : Strokable =
  entrypoint stroke() = abort("not today")

One thing is an interface, the other thing is a contract. The contract uses the interface, that's it. Can we keep it simple please ? 🙏

Edit: After some thinking, I changed my mind. contract interface is a bit more clear, as it states more precisely the nature of the interface. This is the way it is currently implemented for inter contract calls, so let's stick with the current syntax please ? :)

*Created by: nikita-fuchs* > Also agree with @UlfNorell but would suggest to drop the `contract` prior to `interface`. Other languages also just use the single `interface` keyword to define an interface (e.g. Java,C#,Solidity). Why should we have `contract interface`? Absolutely agree with this one and the rest vouching for simplicity and no @ decorators. ``` interface Strokable = entrypoint stroke : () => unit contract Cat : Strokable = entrypoint stroke() = abort("not today") ``` One thing is an interface, the other thing is a contract. The contract uses the interface, that's it. Can we keep it simple please ? 🙏 **Edit**: After some thinking, I changed my mind. `contract interface` is a bit more clear, as it states more precisely the nature of the interface. This is the way it is currently implemented for inter contract calls, so let's stick with the current syntax please ? :)
zxq9 commented 2021-11-11 21:28:32 +09:00 (Migrated from gitlab.com)

Created by: omar-saadoun

@ghallak can we add @UlfNorell proposal to vote?

*Created by: omar-saadoun* @ghallak can we add @UlfNorell proposal to vote?
zxq9 commented 2021-11-11 21:32:11 +09:00 (Migrated from gitlab.com)

Created by: marc0olo

I think we already voted for it 😛

personally I also agree with @the-icarus to just use the single keyword interface. not sure why contract should be put in front

*Created by: marc0olo* I think we already voted for it 😛 personally I also agree with @the-icarus to just use the single keyword `interface`. not sure why `contract` should be put in front
zxq9 commented 2021-11-12 01:10:30 +09:00 (Migrated from gitlab.com)

Created by: nikita-fuchs

The idea is:

Let's use contract interface for both inter contract calls and the "interfacing". Else if I want to create a contract equivalent to Strokable and call it, I need to do:

 interface Strokable =
  entrypoint stroke : () => unit

contract interface Strokable =
  entrypoint stroke : () => unit

contract Cat : Strokable =
  entrypoint strokeAnotherCat(cat : Strokable) = Cat.stroke()

So why not just reuse contract interface ?

*Created by: nikita-fuchs* The idea is: Let's use `contract interface` for both inter contract calls and the "interfacing". Else if I want to create a contract equivalent to Strokable and call it, I need to do: ``` interface Strokable = entrypoint stroke : () => unit contract interface Strokable = entrypoint stroke : () => unit contract Cat : Strokable = entrypoint strokeAnotherCat(cat : Strokable) = Cat.stroke() ``` So why not just reuse `contract interface` ?
zxq9 commented 2021-11-12 01:23:50 +09:00 (Migrated from gitlab.com)

Created by: marc0olo

if inter contract calls require the keywords contract interface for a specific reason this makes sense. if inter contract calls can also be established by just providing interface keyword I would prefer removing the need for contract keyword there.

but I am generally happy to have the feature at all :-) ... just personal opinion

*Created by: marc0olo* if inter contract calls require the keywords `contract interface` for a specific reason this makes sense. if inter contract calls can also be established by just providing `interface` keyword I would prefer removing the need for `contract` keyword there. but I am generally happy to have the feature at all :-) ... just personal opinion
Sign in to join this conversation.
No Milestone
No project
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: QPQ-AG/sophia#307
No description provided.