Dlaczego mogę wpisywać funkcje aliasów i używać ich bez rzutowania?

97

W Go, jeśli zdefiniujesz nowy typ, np .:

type MyInt int

Nie możesz wtedy przekazać a MyIntdo funkcji oczekującej int lub odwrotnie:

func test(i MyInt) {
    //do something with i
}

func main() {
    anInt := 0
    test(anInt) //doesn't work, int is not of type MyInt
}

W porządku. Ale dlaczego w takim razie to samo nie dotyczy funkcji? na przykład:

type MyFunc func(i int)
func (m MyFunc) Run(i int) {
    m(i)
}

func run(f MyFunc, i int) {
    f.Run(i)
}

func main() {
    var newfunc func(int) //explicit declaration
    newfunc = func(i int) {
        fmt.Println(i)
    }
    run(newfunc, 10) //works just fine, even though types seem to differ
}

Teraz nie narzekam, ponieważ oszczędza mi to konieczności jawnego rzutowania newfuncna typ MyFunc, tak jak musiałbym to zrobić w pierwszym przykładzie; po prostu wydaje się niespójne. Jestem pewien, że jest ku temu dobry powód; czy ktoś może mnie oświecić?

Pytam głównie dlatego, że chciałbym w ten sposób skrócić niektóre z moich dość długich typów funkcji, ale chcę się upewnić, że jest to oczekiwane i dopuszczalne :)

jsdw
źródło
typejest bardziej przydatny w Go niż w Scali. Niestety, Scala ma tylko aliasy typów.
Rick-777
4
Go teraz ma aliasy typów github.com/golang/go/issues/18130
Hut8
czy ktoś mógłby wyjaśnić drugi fragment kodu? Naprawdę nie mogę uzyskać tych deklaracji funkcji
DevX

Odpowiedzi:

149

Okazuje się, że jest to nieporozumienie, które miałem na temat tego, jak Go radzi sobie z typami, które można rozwiązać, czytając odpowiednią część specyfikacji:

http://golang.org/ref/spec#Type_identity

Istotne rozróżnienie, którego nie byłem świadomy, dotyczyło typów nazwanych i nienazwanych .

Nazwane typy to typy z nazwą, na przykład int, int64, float, string, bool. Ponadto każdy typ utworzony za pomocą „typu” jest nazwanym typem.

Typy nienazwane to takie, jak [] string, map [string] string, [4] int. Nie mają nazwy, a jedynie opis odpowiadający ich strukturze.

Jeśli porównujesz dwa nazwane typy, nazwy muszą być zgodne, aby można było je stosować zamiennie. Jeśli porównasz nazwany i nienazwany typ, to tak długo, jak pasuje reprezentacja bazowa , możesz działać!

np. biorąc pod uwagę następujące typy:

type MyInt int
type MyMap map[int]int
type MySlice []int
type MyFunc func(int)

następujące informacje są nieprawidłowe:

var i int = 2
var i2 MyInt = 4
i = i2 //both named (int and MyInt) and names don't match, so invalid

co jest w porządku:

is := make([]int)
m := make(map[int]int)
f := func(i int){}

//OK: comparing named and unnamed type, and underlying representation
//is the same:
func doSlice(input MySlice){...}
doSlice(is)

func doMap(input MyMap){...}
doMap(m)

func doFunc(input MyFunc){...}
doFunc(f)

Jestem trochę wypatroszony, że nie wiedziałem tego wcześniej, więc mam nadzieję, że to trochę wyjaśnia ten typ dla kogoś innego! A to oznacza znacznie mniej castingów, niż myślałem na początku :)

jsdw
źródło
1
Możesz także użyć is := make(MySlice, 0); m := make(MyMap), który jest bardziej czytelny w niektórych kontekstach.
R2B2
13

Zarówno pytanie, jak i odpowiedź są dość pouczające. Chciałbym jednak przywołać rozróżnienie, które nie jest jasne w odpowiedzi Lytnusa.

  • Typ nazwany różni się od Typu bez nazwy .

  • Zmienną typu nazwanego można przypisać do zmiennej typu nienazwanego i odwrotnie.

  • Zmiennych o różnych nazwanych typach nie można przypisywać sobie nawzajem.

http://play.golang.org/p/uaYHEnofT9

import (
    "fmt"
    "reflect"
)

type T1 []string
type T2 []string

func main() {
    foo0 := []string{}
    foo1 := T1{}
    foo2 := T2{}
    fmt.Println(reflect.TypeOf(foo0))
    fmt.Println(reflect.TypeOf(foo1))
    fmt.Println(reflect.TypeOf(foo2))

    // Output:
    // []string
    // main.T1
    // main.T2

    // foo0 can be assigned to foo1, vice versa
    foo1 = foo0
    foo0 = foo1

    // foo2 cannot be assigned to foo1
    // prog.go:28: cannot use foo2 (type T2) as type T1 in assignment
    // foo1 = foo2
}
Mingyu
źródło