Go: proposal: built-in wrapper interface

Created on 3 Jul 2019  Â·  22Comments  Â·  Source: golang/go

I was wondering if Unwrap Is As %w can be abstracted for other type's too, not only for errors and introduce a more general build-in wrapper interface instead?

package wrapper

type Wrapper interface {
    Unwrap() Wrapper
}

func Unwrap(w Wrapper) Wrapper {
    if w != nil {
        return w.Unwrap()
    }
    return nil
}
package wrapper_test

import (
    "testing"
    "wrapper"
)

type wrapperT struct{ s string }

func (w wrapperT) Unwrap() wrapper.Wrapper { return nil }

type wrappedT struct {
    s string
    w wrapper.Wrapper
}

func (w wrappedT) Unwrap() wrapper.Wrapper { return w.w }

func TestUnwrap(t *testing.T) {
    w1 := wrapperT{"wrapper"}
    w2 := wrappedT{"wrapper wrap", w1}

    testCases := []struct {
        w    wrapper.Wrapper
        want wrapper.Wrapper
    }{
        {nil, nil},
        {wrappedT{"wrapped", nil}, nil},
        {w1, nil},
        {w2, w1},
        {wrappedT{"wrap 3", w2}, w2},
    }
    for _, tc := range testCases {
        if got := wrapper.Unwrap(tc.w); got != tc.want {
            t.Errorf("Unwrap(%v) = %v, want %v", tc.w, got, tc.want)
        }
    }
}

https://github.com/golang/go/issues/29934

I am doing this in a separate proposal because the more I think about it, the more I think this decision needs to be made before the new error packages goes life because don't think this can be made backward compatible once the new error package goes life with it's locked in type error in wrap.go

In short this would be the way forward in my opinion in Go2

type error interface {
    Error() string
    Unwrap() wrapper
}

type wrapper interface {
    Unwrap() wrapper
}
FrozenDueToAge Proposal

All 22 comments

Change https://golang.org/cl/184818 mentions this issue: monad: Unwrap, Is, As, %w can be abstracted for other type's too, not only for errors and introduces a more general monad package

Monad is definitely not the right name for this. A “monad” has two characteristic operators: Haskell calls them return and >>=, although you may also seem them called unit and bind. To form a monad, those operators have to have particular algebraic relationship (often called the “monad laws”).

The Unwrap function does not correspond to either of those operators.

Note that the proposed Monad interface will not match any types in existence today.
See https://golang.org/doc/faq#covariant_types.

Thanks, will change the name to Wrapper.
But it could mach Error types in 1.13 right? I strongly believe a Wrapper interface would be in the same line as Reader and Writer?

No, it will never match any existing type. The type Wrapper (or Monad or what-have-you) is distinct from, say, the type error, so a method with the signature Unwrap() error will never match the method Unwrap() Wrapper in the interface definition.

Ok but doesn't make Unwrap() Wrapper more sense then Unwrap() error

I agree that for example the severHTTP method from the http Handler interface doesn't return a concrete type but didn't the http Handler interface proof that making a general interface like that has a huge advantage making http handlers more universal? Maybe I am comparing apples with oranges here but hope I can convince you of the pattern here what I am trying to explain. So we can refactor the new error package to have a Unwrap() Wrapper signature instead of Unwrap() error

So the errors package would look like this instead

package errors

func New(text string) error {
    return &errorString{text, nil}
}

type errorString struct {
    s string
    w wrapper
}

func (e *errorString) Error() string {
    return e.s
}

func (e *errorString) Unwrap() wrapper {
    return e.w
}

and create this new built-in's ?

package builtin

type error interface {
    Error() string
    Unwrap() wrapper
}

type wrapper interface {
    Unwrap() wrapper
}

I also made this suggestion but no one noticed AFAICT. I think it would be useful for the http optional interfaces but it requires thinking through.

I'm implementing it in https://golang.org/cl/184818 to have a exact estimate what would break but I am stuck :(

When I do ./all.bash I get

errorString does not implement error (wrong type for Unwrap method)
                have Unwrap() wrapper
                want Unwrap() <T>

I guess I am missing some wrapper register step or something, need help :(

I haven't looked at your CL, but you cannot add methods to the error interface (or any interface in the standard library really), as that would break preexisting code that wrote their own implementation of that interface.

Yep understand, but would like to have a better estimate how much would break, and how much benefit we can gain by testing it and be able to show examples. As far as I can tell it's not that many lines of code to make the compiler and the complete standard library work? But then again I could be totally wrong here.

Why would it matter how much would break? The Go 1 Compatibility docs says that no amount of breakage is acceptable with few acceptable exceptions (none of which seem to apply in this case).

Note: I'm not talking about breaking the standard library, I'm talking about breaking public non-standard-library code. For instance, here is a hypothetical pre-existing Go program which cannot break: https://play.golang.org/p/Ia3Ic7uySAe. There will be other such programs.

Yes but now modules can mitigate that specifying the go version. So if we can proof only a limited amount of cases will break then it still worth for Go2. If however it turns out it brings havoc to the complete Go community and even the standard library then, yes it's not worth it. But I still believe the complete standard library can work with minor changes. Which is a good start for a Go2 proposal

I don't believe that module go versions help in this situation.

A module with "go 1.13" needs to be able to import and use a library from a module with "go 1.14" and vice-versa; if the two modules don't agree on the "error" interface, I don't see how that could be managed sensibly. For instance, if the "go 1.14" module calls a function from the "go 1.13" module which returns an error, then it will be able to call "Unwrap" on that error, even though the implementation of that error type (inside the "go 1.13" module) didn't necessarily need to implement said "Unwrap" method.

(I should note that this is the simplest possible example; there are more complicated examples where the type gets from the "go 1.13" module to the "go 1.14" module not directly as a value of type error, but as an interface{}, and then the "go 1.14" module type-casts it to the error interface)

Agree then a conversion tool is going to be required or modifying the code which I assume for now be trivial fixes until I can test it better by compiling common repos with this change.

Sorry, your last comment was a little ambiguous, so I just wanted to make sure I said it clearly for the record:

If I upgrade my compiler and recompile my code, the Go 1 compatibility document essentially says that my code must compile with the new compiler (assuming it compiled with the old compiler). Relatedly, if I upgrade my compiler, all the code I depend on must also compile, even if I cannot make any modifications to that code.

If a language change requires a conversion tool when upgrading compilers, or requires modifying the code at all when upgrading compilers (no matter how trivial the modifications), then it is not meeting the standard set in the Go 1 compatibility document.

(Unfortunately, I might not get a chance to respond to further comments as I might have limited network connectivity for a while)

Here is the gist I wrote before arguing that Is/As could be made to use interface{} and work generally: https://gist.github.com/carlmjohnson/d06cd8d10e0aef65f565a55cc57cd986

The signature needed is just interface { Unwrap() interface{} } with optional Is and As extensions to override the normal wrapping behavior.

You will get better failed build messages with a specified wrapper interface I think but anyway if we manage to make this work then it going to be trivial to make your version work and compare because it requires a lot less changes to the compiler I assume.

I think this decision needs to be made before the new error packages goes life because don't think this can be made backward compatible once the new error package goes life with it's locked in type error in wrap.go

It's pretty clearly too late to make a language change for Go 1.13.

Also, in this example:

type error interface {
    Error() string
    Unwrap() wrapper
}

The error type is not mandated to have an Unwrap method, even in Go 1.13, nor will it ever be required to have one. And the current optional Unwrap method returns another error, not a wrapper. That is, it's important that it doesn't unwrap to some other non-error thing.

Given that error can't and won't adopt this, it doesn't seem like it needs to be a language built-in at all. And maybe if we do generics you will be able to write Wrapper(T) that has Unwrap() T.

Agree when we have generics you win :P but until then carlmjohnson or I win because wrapping anything you want was the main purpose so you can use it for wrapping logs etc also :)

I admit your new error handling is awesome but pushing it before we have generics feels to me like we are going to regret it in the future especially after I actually could try this proposal https://golang.org/cl/184818 thx to @mdempsky

Generics don’t really have anything to do with wrapping. Any type system powerful enough to express the concept of wrapping is also so powerful that it makes no meaningful guarantees about what it contains, so you don’t gain anything.

I think in general people overestimate the advantages of static typing for expressing complex types. If it’s complex, your static type is as likely to be buggy as anything else. Just use dynamic types and move on.

that can also be true, once I can play with generics then it could be I have to agree with you on that too

Was this page helpful?
0 / 5 - 0 ratings

Related issues

jayhuang75 picture jayhuang75  Â·  3Comments

gopherbot picture gopherbot  Â·  3Comments

michaelsafyan picture michaelsafyan  Â·  3Comments

bradfitz picture bradfitz  Â·  3Comments

Miserlou picture Miserlou  Â·  3Comments