Wątek przeniesiony 2021-09-16 09:48 z Inne języki programowania przez cerrato.

Dlaczego interfejsy w go w żaden sposób nie wymuszają typu receivera?

0

Trudno mi sformułować pytanie, więc podzielę się obserwacjami licząc na to, że ktoś z Was rzuci mi trochę światła na to, jak to dokładnie działa. Mamy taki kod:

Screenshot 2019-08-12 at 18.15.06.png

Jak widać GoLand pokazuje, że dwie struktury implementują mój interfejs. Jak się okazuje nie do końca, bo nie mogę przekazać animal do funkcji rename, ponieważ jego implementacja metody rename jest inna - oczekuję pointer receivera. Jeżeli zamienię pointer receivera na value receivera, to wszystko działa i nagle moja struktura już implementuje Renamer.

Wygląda to trochę tak, jakby struktura miała wpływ na to, jak finalnie wygląda mój interfejs.

tenor.gif

Dlaczego to, czy metoda wymaga pointera, czy nie, nie jest narzucone przez interfejs, tylko przez struktury, które go implementują? Czemu nie ma czegoś w stylu:

type Renamer interface {
  (r *Renamer) rename(name string)
  // albo
  (r *struct) rename(name string) 
}

Wtedy jasno widać, że ta metoda wymaga wskaźnika do struktury, a nie po prostu struktury. Wydaje mi się, że język powinien mieć jakiś sposób na wymuszenie tego, że jak metoda coś mutuje, to ma mieć pointer receiver inaczej nie spełnia kontraktu. Tym czasem jest to dla mnie mały WTF.

0

Ale z czym masz problem? Bo zadeklarowałeś metodę dla animal, która przyjmuje pointer, a podałeś wartość finalnie (to a) i jesteś zdziwiony, że masz błąd?

1

W go interfejs definiuje Ci API, kontrakt, a kwestią imlementacji jest to czy metody implementujące interfejs otrzymują recievera przez płytką kopię czy przez wskaźnik. Potem musisz używać zgodnie z tym jak zadeklarowałeś. Adres możesz oczywiście pobrać przez użycie & i mieć takiego potworka (&val).method() czyli wcale nie musiałeś zmieniać deklaracji metody, tylko poprawie ją wywołać. Go to bardzo prosty język, reciever jest zwykłym parametrem do funkcji, tylko stoi z innej strony.
Taką decyzję podjęli twórcy i nie sądzę by któryś z nich się tłumaczył.

0

jesteś zdziwiony, że masz błąd?

Nie tyle jestem zdziwiony, że mam błąd, bo rozumiem jak to działa, co wydaje mi się to kompletnie bez sensu zaprojektowane i szukam jakiegoś uzasadnienia. Bo na przykład dla czegoś takiego:

package main

import "fmt"

func main() {
	a := animal{"Bob"}
	a.Rename("Max")
	fmt.Printf("%v\n", a)
	(&a).Rename("John")
	fmt.Printf("%v\n", a)
}

type animal struct {
	name string
}

func (a *animal) Rename(name string) {
	a.name = name
	fmt.Printf("%T\n", a)
}

Dzieje się automatycznie (&p).Rename(), to już przy przekazaniu tego do funkcji, która oczkuje interfejsu, który de facto nie narzuca jaki ma to być receiver już się taka magia nie dzieje. Na początku spodziewałem się, że dokładnie tak się stanie i w tym przypadku, skoro przy samej deklaracji interfejsu nie mam możliwości narzucenia, że częścią kontraktu jest receiver typu pointer.

0

Ten kod to jakieś dziwactwo.

Po pierwsze, jak chcesz to jako pointer, to przy deklaracji zrób od razu:

a := &animal{"Bob"} // lub new(animal{"Bob"})
a.Rename()

A nie jakieś cudaki w tylu (&a).costam()

Ale najważniejsza sprawa, to pytanie z cyklu: Co chcesz osiągnąć?
Czy chcesz coś zmieniać w tej strukturze, nie chcesz zmieniać, chcesz zabronić zmiany, czy po prostu bawisz się składnią?

Jak zadeklarujesz metode z pointer receiver - musisz podać pointer. Odwrotnie jest z value receiver - wtedy możesz też podać pointer. Tylko znowu: jaki jest use case?
Ponadto, jak Ci bardzo zależy, to możesz w jakiejś funkcji/metodzie sprawdzić, czy przyszedł pointer czy value (jak od tego coś zależy)

2

generalnie, zgadzam się z przedmówcami. Go w przeciwieństwie do javy, c# czy php rozróżnia interfejsy dla typów oraz dla wskaźników. Zasada jest prosta - jeśli chcesz, żeby w danej metodzie zmieniać stan obiektu, na którym operujesz, to dajesz wskaźnik. Wszystkie inne przypadki to przez wartość.

Tutaj masz przykład: https://goplay.space/#Otmt9XLkC0P O co chodzi? Wyobraź sobie przypadek, że przekazujesz jakąś strukturę jako argument do funkcji. Definiując niektóre metody do wskaźników tego obiektu zabezpieczasz się przed sytuacją, że gdy przekażesz przez wartość, to ktoś Ci zmieni stan tego obiektu. Więc podsumowujac - przekazując coś jako wskaźnik dajesz wolną rękę do zmiany tego obiektu. Nie zawsze jest to to co chcemy osiągnąć

1 użytkowników online, w tym zalogowanych: 0, gości: 1