Alternate syntax for Go2 generics and contracts

501 views
Skip to first unread message

Manlio Perillo

unread,
Aug 31, 2018, 8:42:47 AM8/31/18
to golang-nuts
I just read the "official" proposal for Go2 generics and contracts.
The current proposal makes the function syntax more complex, and the syntax for the contract is a bit magic.

What about something like:


    type stringer template

    contract (x stringer) {
        var s string = x.String()
    }

    func Stringify(s []stringer) (ret []string) {
         for _, v := range s {
            ret = append(ret, v.String())
        }
        return ret
    }

instead of


    contract stringer(x T) {
        var s string = x.String()
    }

    func Stringify(type T stringer)(s []T) (ret []string) {
         for _, v := range s {
            ret = append(ret, v.String())
        }
        return ret
    }


Thanks
Manlio

Axel Wagner

unread,
Aug 31, 2018, 9:34:02 AM8/31/18
to Manlio Perillo, golang-nuts
AIUI your syntax can't cover that. And FWIW, I find the syntax of contracts in the doc far less "magic" than yours, but YMMV of course.

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

Tristan Colgate

unread,
Aug 31, 2018, 9:54:05 AM8/31/18
to Axel Wagner, Manlio Perillo, golang-nuts
Also, and I think this probably applies to the existing syntax in the design.
This example seems like a pathological use of generics. What would be the advantage over  just declaring the function takes a Stringer? Am I missing something (presumably this is potentially avoids the interface call allocation?).
This is a good example of my gut feeling about generics, I realise they'll be valuable, but do they devalue interfaces? They do not seem like an orthogonal feature. Of the two, interfaces feel like they encourage me to write better structured code.
This could also just be fear of change.

Daniela Petruzalek

unread,
Aug 31, 2018, 12:47:45 PM8/31/18
to Tristan Colgate, Axel Wagner, Manlio Perillo, golang-nuts
I agree with Tristan in regards to orthogonality.

Seeing the draft with all those complexities added to the language made me quite uneasy about this proposal. When I first started learning Go I was one of the first people to raise a hand and complain about the lack of generics in the language. Now, a year later, after learning about Go proper idioms, I strongly regret my previous stance. I was trying to make Go another language that was not Go. I didn't want to learn to write idiomatic Go code, I wanted to write Go in the same way I used to write C++ (for instance). That was wrong on so many levels.

I'm now sitting in the fence in regards to the generics addition to the language. My previous arguments don't hold anymore. I like how the current Go language forces our intentions to be explicit rather than hiding the real code under some clever abstractions that few people understand. I feel that I became a better programmer that way, thinking about the collective before trying to please myself showing how smart I am. I feel it also encourages collaboration since it's really easy to get an overall understanding of the language. It's accessible for junior developers and enthusiasts since there are fewer concepts to learn, yet they provide some powerful abstractions. Look at the things that were built with Go... Kubernetes, Docker... it's a really powerful language while still being truthful to its simplified flow control, simplified (yet verbose) error handling, simplified data structures...

I strongly wish that a generics proposal would stay true to the Go's simplicity principle, but I somehow don't feel this proposal does meet that requirement at it's current state.

Sorry to hijack the thread!

Best,

Dani

Scott Cotton

unread,
Aug 31, 2018, 8:57:26 PM8/31/18
to golang-nuts
I think the draft is a very good start.

In fact, there are many times when I find myself wondering whether to use interfaces 
when something like a contract in that draft is a more natural fit.

I would encourage the authors to consider that math std lib package for float32 as a test
case that would be very demanding of the design in the proposal (and useful if realised).

I would also encourage the authors to think about what the language would have looked like
if it had contracts (as in the proposal) instead of interfaces from the start.  My intuition is 
that interfaces would be obsolete -- and that's a very good thing.  You could implement interface
in terms of contract as some sort of bridge for compatibility.  But if the proposal could come up with a way to extend the interface syntax with contract-like semantics, perhaps the overlap between the two could just disappear. 

As for the question in the proposal about implementation by interface or not (compilation speed
vs execution speed), my own use cases often require re-thinking something without interfaces
(like the heap in my sat solver) for performance.  Here's a thumbs up for execution speed and 
hearty "go for it" for compiler writers to make it fast :)

Nice work on the proposal.

Scott

Axel Wagner

unread,
Sep 1, 2018, 12:26:29 PM9/1/18
to w...@iri-labs.com, golang-nuts
On Fri, Aug 31, 2018 at 6:57 PM Scott Cotton <w...@iri-labs.com> wrote:
My intuition is that interfaces would be obsolete -- and that's a very good thing.

They wouldn't. You can't have heterogeneous lists with contracts. For example, say you have a Reader contract:

contract Reader(r R) {
    var (
        n int
        err error
        p []byte
    )
    n, err = r.Read(p)
}

You can't use that to implement, say, io.MultiReader:

func MultiReader(type R Reader) (readers ...R) R {
    return &multiReader{readers} // Type error: Must return R, not *multiReader
}

func Foo() {
    r1 := bytes.NewReader([]byte("Hello "))
    r2 := strings.NewReader("world")
    r3 := MultiReader(r1, r2) // Type error: Uses different types in places of R
}

Saying that contracts subsume interface is a bit like saying that generic functions in Haskell subsume type classes. They are different things with different (but overlapping) uses.

Scott Cotton

unread,
Sep 1, 2018, 1:26:00 PM9/1/18
to golang-nuts


On Saturday, 1 September 2018 18:26:29 UTC+2, Axel Wagner wrote:
On Fri, Aug 31, 2018 at 6:57 PM Scott Cotton <w...@iri-labs.com> wrote:
My intuition is that interfaces would be obsolete -- and that's a very good thing.

They wouldn't. You can't have heterogeneous lists with contracts. For example, say you have a Reader contract:

contract Reader(r R) {
    var (
        n int
        err error
        p []byte
    )
    n, err = r.Read(p)
}

You can't use that to implement, say, io.MultiReader:

func MultiReader(type R Reader) (readers ...R) R {
    return &multiReader{readers} // Type error: Must return R, not *multiReader
}

I don't think there would be a type error as follows:

fun MultiReader(type R Reader) (readers ...R) Reader {
   return &multiReader{readers}
}
 

func Foo() {
    r1 := bytes.NewReader([]byte("Hello "))
    r2 := strings.NewReader("world")
    r3 := MultiReader(r1, r2) // Type error: Uses different types in places of R
}

Saying that contracts subsume interface is a bit like saying that generic functions in Haskell subsume type classes. They are different things with different (but overlapping) uses.

They are different things so far as Haskell is concerned, and probably traditionally w.r.t. type theory.  But a little thinking outside the box and a tweak of allowed syntax (together with whatever rabbit holes that might force a type checker into) such as the above may unify them...  


I don't think examples of it not working constitute valid arguments that they aren't unifiable in a practical or theoretical way.  

My type theory definitely could use a refresher for discussing this in formal detail.  As the idea of unifying interfaces and contracts is not formalised, just some intuitions of at least one so far, I don't think one could even legitimately arrive at a proof that they are not unifiable, since there is no agreed upon a priori formal framework to arrive at such conclusions.

Scott

Scott Cotton

unread,
Sep 1, 2018, 1:46:09 PM9/1/18
to golang-nuts


On Saturday, 1 September 2018 19:26:00 UTC+2, Scott Cotton wrote:


On Saturday, 1 September 2018 18:26:29 UTC+2, Axel Wagner wrote:
On Fri, Aug 31, 2018 at 6:57 PM Scott Cotton <w...@iri-labs.com> wrote:
My intuition is that interfaces would be obsolete -- and that's a very good thing.

They wouldn't. You can't have heterogeneous lists with contracts. For example, say you have a Reader contract:

contract Reader(r R) {
    var (
        n int
        err error
        p []byte
    )
    n, err = r.Read(p)
}

You can't use that to implement, say, io.MultiReader:

func MultiReader(type R Reader) (readers ...R) R {
    return &multiReader{readers} // Type error: Must return R, not *multiReader
}

I don't think there would be a type error as follows:

fun MultiReader(type R Reader) (readers ...R) Reader {
   return &multiReader{readers}
}
 

func Foo() {
    r1 := bytes.NewReader([]byte("Hello "))
    r2 := strings.NewReader("world")
    r3 := MultiReader(r1, r2) // Type error: Uses different types in places of R
}

Forgot to give an example for unifying this:  

contract Reader(r Reader, rs ...Reader) .....

which would allow heterogenous arguments.

This example is certainly not posed as a global solution to heterogenous type arguments, but just to give another example 
that it could work.

Axel Wagner

unread,
Sep 1, 2018, 2:29:31 PM9/1/18
to w...@iri-labs.com, golang-nuts
I don't understand what you are trying to say. You assert that there wouldn't be a type-error, but you don't actually justify that. It seems pretty obvious to me, that if you instantiate my MultiReader example to, say, *strings.Reader, it would fail to compile. Because *multiReader is not a *strings.Reader (and you'd have to replace R with *strings.Reader everywhere in the signature, that is the exact reason we want generics to begin with). The example you give for "unifying this" isn't actually syntactically correct, AFAICT. At least I can't find anything in the design that would allow using the identifier of the contract in its arguments - and it's unclear to me what that would mean.

To provide another way to clarify that interfaces and contracts are different:
* How would you build fmt.Println using contracts?
* One of the main motivators behind adding generics is the lack of type-safety for containers (say, container/list). While all methods mention the same type, interface{}, it is not checked for consistency between invocations. This is a bug for type-safe containers, but it's a feature for fmt.Println.

Scott Cotton

unread,
Sep 1, 2018, 3:15:31 PM9/1/18
to golang-nuts


On Saturday, 1 September 2018 20:29:31 UTC+2, Axel Wagner wrote:
I don't understand what you are trying to say. You assert that there wouldn't be a type-error, but you don't actually justify that.

There are 2 examples,  both are (to me) intuitive suggestions and not the result of a phd thesis worth of research :)  continued below.



It seems pretty obvious to me, that if you instantiate my MultiReader example to, say, *strings.Reader, it would fail to compile. Because *multiReader is not a *strings.Reader (and you'd have to replace R with *strings.Reader everywhere in the signature, that is the exact reason we want generics to begin with).

That's why I didn't use R for another, distinct Reader.  For the signature of your MultiReader example I changed the return type from "R" (the type argument) to "Reader" (the name of the contract).  In a hand-wavy formal way, the return type would serve as a new type argument to Reader, independent of R.  Put in an intuitive (to me) way: Since MultiReader returns something which presumably conforms to the contract Reader, why not just say that instead of the type argument R?

 
The example you give for "unifying this" isn't actually syntactically correct, AFAICT. At least I can't find anything in the design that would allow using the identifier of the contract in its arguments - and it's unclear to me what that would mean.

Apologies, I'm responding to the proposal, just giving my 2 cents.  I am not asserting that the examples I give are syntactically correct according to the proposal.  I am suggesting that the concepts of "contract" and "interface",  can (probably) be defined for Go 
in a unified way.  I am not  supposing the definitions in the proposal of "interface" and "contract" in suggesting this; to the contrary I am suggesting these concepts are what Go would define them to be and hence are flexible and not yet fixed.  I do not think saying interfaces and contracts are different because the proposal or other background such as Haskell or type theory treat them differently is a convincing counterargument to my suggestion of exploring the unification of contract and interface.
 

To provide another way to clarify that interfaces and contracts are different:

This again assumes a priori interfaces and contracts are different.  I am trying to suggest to drop this assumption and see what happens.  To me, it seems, so far, good things would happen.
 
* How would you build fmt.Println using contracts?
* One of the main motivators behind adding generics is the lack of type-safety for containers (say, container/list). While all methods mention the same type, interface{}, it is not checked for consistency between invocations. This is a bug for type-safe containers, but it's a feature for fmt.Println.

So perhaps containers could use a contract which does not instantiate each type argument distinctly, forcing uniformity of types and 
more or less like in the proposal.  And perhaps situations desiring heterogenous types, such as fmt,*f(), could use a different kind of 
contract which instantiates each type argument independently at the call site.  To distinguish them, some syntax could be proposed such as

contract Reader(r Reader.(*)) { ... }
vs
contract Reader(r Reader) {...}

I am far from the genius who is capable of thinking this idea through and making it as solid as the proposal overnight.  Just expressing the opinion that unifying the concepts of contract and interface seems to me like a good idea to explore, and that a priori assuming they are different doesn't help advance exploring it.

Scott

 


 

roger peppe

unread,
Sep 1, 2018, 4:55:01 PM9/1/18
to Scott Cotton, golang-nuts
FWIW I've published some ideas about how contracts and interface types
could be partially unified, and contracts significantly simplified,
which I think is quite pertinent to the above discussion.

Doc here:

https://gist.github.com/rogpeppe/45f5a7578507989ec4ba5ac639ae2c69

Tristan Colgate

unread,
Sep 2, 2018, 4:09:43 AM9/2/18
to roger peppe, Scott Cotton, golang-nuts
Just read:


I think I concur, keep specification of behaviour in interfaces if possible.

Contracts overlap too much. If interfaces can do the job, all be it less elegantly, the extra verbosity seems worth it to avoid the feature overlap.

What I'm mostly worried about is a future where new comers have to learn two overlapping features. Inevitably people won't stop using interfaces.

Contracts are too expensive, cognitively, if they aren't going to become pervasive, but that seems to throw out interfaces (even if they can be made to cooperate).

Scott Cotton

unread,
Sep 2, 2018, 12:29:00 PM9/2/18
to golang-nuts
Hi all,

I just found out that there is a wiki for this kind of discussion,  looks to me like a better venue, more organised, more interest, 
and just recently proposed on golang.org.

Scott
Reply all
Reply to author
Forward
0 new messages