Co to jest pisanie kaczek?


429

Podczas pisania przypadkowych tematów na temat oprogramowania online natknąłem się na pisanie kaczek i nie do końca go rozumiałem.

Co to jest „pisanie kaczek”?


1
@Mitch próbowałem i dostałem coś w formie dziedziczenia. Ale nie mógł wiele. Przepraszam, jeśli zadałem złe pytanie.
sushil bharwani,

3
@sushil bharwani: nie, nie zły. Ale ludzie oczekują, że jako pierwszy port zawinięcia (tj. Pierwsza rzecz, którą zrobisz) jest próba wyszukiwania przed opublikowaniem tutaj.
Mitch Wheat

104
Biorąc pod uwagę powyższe argumenty, nie wydaje się, że przepełnienie stosu jest w rzeczywistości konieczne, ponieważ jestem pewien, że prawie na każde pytanie, które można pomyśleć, można znaleźć odpowiedź w Internecie, a jeśli nie, odpowiedź można uzyskać łatwiej i bez krytyki, wysyłając wiadomość e-mail znajomy znajomy. Myślę, że wielu z was nie zdążyło na moment przepełnienia stosu.
rhody

41
Jestem pewien, że gdzieś przeczytałem, że SO miało być „repozytorium pytań kanonicznych” i jestem pewien, że nie można uzyskać więcej kanoniczności niż to.
heltonbiker

Odpowiedzi:


302

Jest to termin używany w dynamicznych językach , które nie mają silnego pisania .

Chodzi o to, że nie potrzebujesz typu, aby wywołać istniejącą metodę na obiekcie - jeśli metoda jest na nim zdefiniowana, możesz ją wywołać.

Nazwa pochodzi od wyrażenia „Jeśli wygląda jak kaczka i kwacze jak kaczka, to jest kaczka”.

Wikipedia ma znacznie więcej informacji.


25
Uważaj na używanie silnego pisania. To nie jest tak dobrze zdefiniowane. Kaczka też nie pisze. Google Go lub Ocaml są językami o typie statycznym i strukturalnym podtytułem. Czy te języki są pisane kaczką?
ODPOWIADAMY NA ODPADY

7
lepszym wyrażeniem przy pisaniu kaczek jest: „Jeśli mówi się, że to kaczka… cóż, to mi wystarczy”. patrz pyvideo.org/video/1669/keynote-3 28:30 lub youtube.com/watch?v=NfngrdLv9ZQ#t=1716
tovmeod

7
Pisanie kaczek niekoniecznie jest używane tylko w dynamicznych językach. Objective-C nie jest językiem dynamicznym i używa pisania kaczego.
eyuelt,

12
Zarówno Python, jak i Ruby są językami o silnym kroju pisma i oba mają funkcję pisania kaczego. Wpisywanie ciągów nie oznacza, że ​​nie ma wpisywania Duck.
alanjds

8
Głosuję za tym. Kaczątko nie ma nic wspólnego z siłą typu, tylko umiejętnością korzystania z dowolnego obiektu posiadającego metodę, niezależnie od tego, czy implementuje interfejs, czy nie.
e-satis

209

Wpisywanie kaczki oznacza, że ​​operacja nie określa formalnie wymagań, które muszą spełnić jej operandy, a jedynie wypróbowuje to, co podano.

W przeciwieństwie do tego, co powiedzieli inni, niekoniecznie dotyczy to dynamicznych języków lub problemów z dziedziczeniem.

Przykładowe zadanie: Wywołaj metodę Quackna obiekcie.

Bez użycia kaczego typu funkcja fwykonująca to zadanie musi z góry określić, że jej argument musi obsługiwać jakąś metodę Quack. Powszechnym sposobem jest użycie interfejsów

interface IQuack { 
    void Quack();
}

void f(IQuack x) { 
    x.Quack(); 
}

Wywoływanie f(42)kończy się niepowodzeniem, ale f(donald)działa tak długo, jak długo donaldwystępuje instancja IQuackpodtypu.

Innym podejściem jest typowanie strukturalne - ale ponownie, metoda Quack()formalnie określa wszystko, co nie może quackz góry udowodnić, że spowoduje awarię kompilatora.

def f(x : { def Quack() : Unit }) = x.Quack() 

Moglibyśmy nawet pisać

f :: Quackable a => a -> IO ()
f = quack

w Haskell, gdzie Quackable krój pisma zapewnia istnienie naszej metody.


Więc jak pisze kaczka zmienia to ?

Cóż, jak powiedziałem, system pisania kaczek nie określa wymagań, ale po prostu próbuje, jeśli coś działa .

Zatem dynamiczny system typów, taki jak Python, zawsze używa pisania kaczego:

def f(x):
    x.Quack()

Jeśli fotrzyma xwsparcie Quack(), wszystko jest w porządku, jeśli nie, ulegnie awarii w czasie wykonywania.

Ale pisanie kaczką wcale nie oznacza dynamicznego pisania - w rzeczywistości istnieje bardzo popularne, ale całkowicie statyczne pisanie kaczki, które również nie stwarza żadnych wymagań:

template <typename T>
void f(T x) { x.Quack(); } 

Ta funkcja nie mówi w żaden sposób, że chce takich, xktóre mogą Quack, więc zamiast tego próbuje po prostu skompilować czas, a jeśli wszystko działa, jest w porządku.


5
nie miałeś na myśli: void f (IQuak x) {x.Quak (); } (zamiast K.Quack), ponieważ parametr funkcji f to IQuack x nie Iquack k, bardzo mały błąd, ale czułem, że trzeba go poprawić :)
dominicbri7

Według Wikipedii twoim ostatnim przykładem jest „pisanie strukturalne”, a nie „pisanie kaczych znaków”.
Brilliand

Wydaje się, że jest osobne pytanie do tej dyskusji: stackoverflow.com/questions/1948069/…
Brilliand

1
Więc jeśli rozumiem, co powiedziałeś, różnica między językiem obsługującym pisanie kaczką a tym, który tego nie robi, jest taka, że ​​przy pisaniu kaczym nie musisz określać typów obiektów, które akceptuje funkcja? def f(x)zamiast def f(IQuack x).
PProteus

124

Proste objaśnienie (bez kodu)

Dyskusja na temat semantyki pytania jest dość szczegółowa (i bardzo akademicka), ale oto ogólna idea:

Pisanie kaczek

(„Jeśli chodzi jak kaczka i kwacze jak kaczka, to jest to kaczka.”) - TAK! ale co to znaczy?Najlepiej ilustruje to przykład:

Przykłady funkcji Kaczego Pisania:

Wyobraź sobie, że mam magiczną różdżkę. Ma specjalne uprawnienia. Jeśli macham różdżką i powiem „Jedź!”do samochodu, więc jeździ!

Czy działa na inne rzeczy? Nie jestem pewien: próbuję na ciężarówce. Wow - to też prowadzi! Następnie próbuję na samolotach, pociągach i 1 lesie (to rodzaj klubu golfowego, którego ludzie używają do „prowadzenia” piłki golfowej). Oni wszyscy jeżdżą!

Ale czy to zadziała powiedzmy, filiżanka do herbaty? Błąd: KAAAA-BOOOOOOM! to nie wyszło tak dobrze. ====> Filiżanki nie mogą prowadzić !! duh !?

Jest to w zasadzie koncepcja pisania kaczek. To system wypróbowania przed zakupem . Jeśli to działa, wszystko jest dobrze. Ale jeśli zawiedzie, jak granat wciąż w dłoni, wybuchnie ci w twarz.

Innymi słowy, jesteśmy zainteresowani tym, co obiekt może zrobić , a nie tym tym, czym jest obiekt .

Przykład: języki wpisywane statycznie

Jeśli martwimy się o to, czym właściwie jest obiekt , wówczas nasza magiczna sztuczka będzie działać tylko na wcześniej ustalonych, autoryzowanych typach - w tym przypadku samochodach, ale zawiedzie na innych obiektach, które mogą prowadzić : ciężarówkach, motorowerach, tuk-tuksach itp. Nie działa na ciężarówkach, ponieważ nasza magiczna różdżka oczekuje, że będzie działać tylko na samochodach .

Innymi słowy, w tym scenariuszu magiczna różdżka bardzo dokładnie przygląda się temu, czym jest obiekt (czy to samochód?), A nie tym, co może zrobić obiekt (np. Czy samochody, ciężarówki itp. Mogą jeździć).

Jedynym sposobem, aby zmusić ciężarówkę do jazdy, jest zdobycie magicznej różdżki, której można oczekiwać zarówno od ciężarówek, jak i samochodów (być może poprzez „wdrożenie wspólnego interfejsu”). Jeśli nie wiesz, co to znaczy, po prostu zignoruj ​​to na chwilę.

Podsumowanie: Kluczowe wynos

W pisaniu kaczek ważne jest to, co obiekt może faktycznie zrobić, a nie to, czym jest obiekt .


Uważam, że interesujące jest to, że bardziej zależy ci na zachowaniu, to jest jego definicja. Bez wątpienia BDD odnosi takie sukcesy w językach takich jak ruby.
Pablo Olmos de Aguilera C.

27

Rozważ, że projektujesz prostą funkcję, która pobiera obiekt typu Birdi wywołuje jego walk()metodę. Istnieją dwa podejścia, o których możesz pomyśleć:

  1. To jest moja funkcja i muszę być pewien, że akceptuje ona tylko Bird, albo ich kod się nie skompiluje. Jeśli ktoś chce skorzystać z mojej funkcji, musi mieć świadomość, że akceptuję tylko Birds
  2. Moja funkcja jest dostępna objectsi po prostu wywołuję walk()metodę obiektu . Więc jeśli objectpuszka walk()jest poprawna, jeśli nie, moja funkcja zawiedzie. Więc tutaj nie jest ważne, że obiekt jest Birdcokolwiek innego, ważne jest, aby mógł walk() (To jest pisanie kaczki )

Należy wziąć pod uwagę, że pisanie kaczką może być przydatne w niektórych przypadkach, na przykład Python często używa pisania kaczego .


Przydatne czytanie


1
Dobre wyjaśnienie, jakie są zalety?
sushil bharwani,

2
Ta odpowiedź jest prosta, jasna i prawdopodobnie najlepsza dla początkujących. Przeczytaj tę odpowiedź wraz z odpowiedzią nad nią (lub jeśli się poruszy, odpowiedź, która mówi o samochodach i filiżankach)
DORRITO

18

Wikipedia ma dość szczegółowe wyjaśnienie:

http://en.wikipedia.org/wiki/Duck_typing

typowanie dynamiczne to styl dynamicznego pisania, w którym bieżący zestaw metod i właściwości obiektu określa prawidłową semantykę, a nie jej dziedziczenie po określonej klasie lub implementacji określonego interfejsu.

Ważna uwaga jest prawdopodobna, że ​​podczas pisania kaczką deweloper bardziej interesuje się częściami obiektu, które są konsumowane, niż faktycznym typem bazowym.


13

Widzę wiele odpowiedzi, które powtarzają stary idiom:

Jeśli wygląda jak kaczka i kwakanie jak kaczka, to jest kaczka

a następnie zapoznaj się z wyjaśnieniem, co możesz zrobić z pisaniem kaczek, lub przykładem, który wydaje się zaciemniać tę koncepcję.

Nie znajduję zbyt wiele pomocy.

To najlepsza próba zwykłej angielskiej odpowiedzi na temat pisania kaczką, którą znalazłem:

Wpisywanie kaczki oznacza, że ​​obiekt jest zdefiniowany przez to, co może zrobić, a nie przez to, czym jest.

Oznacza to, że jesteśmy mniej zainteresowani klasą / typem obiektu, a bardziej tym, jakie metody można na nim wywoływać i jakie operacje można na nim wykonywać. Nie dbamy o jego typ, dbamy o to, co może zrobić .


3

Pisanie kaczek:

Jeśli mówi i chodzi jak kaczka, to jest to kaczka

Jest to zazwyczaj nazywa porwanie ( abductive rozumowanie lub zwany także retroduction , jaśniejszej definicji myślę):

  • z C (wniosek, co widzimy ) i R (zasada, co wiemy ), akceptujemy / decydujemy / zakładamy P (przesłanka, właściwość ), innymi słowy dany fakt

    ... podstawa diagnozy medycznej

    z kaczkami: C = spacery, rozmowy , R = jak kaczka , P = to kaczka

Powrót do programowania:

  • obiekt o ma metodę / właściwość mp1, a interfejs / typ T wymaga / definiuje mp1

  • obiekt o ma metodę / właściwość mp2, a interfejs / typ T wymaga / definiuje mp2

  • ...

Tak więc, więcej niż zwykłe zaakceptowanie mp1 ... dla dowolnego obiektu, o ile spełnia on jakąś definicję mp1 ..., kompilator / środowisko wykonawcze również powinno być w porządku z twierdzeniem o jest typu T

Cóż, czy tak jest w przypadku powyższych przykładów? Czy pisanie w Kaczce w ogóle nie polega na pisaniu? A może powinniśmy nazwać to niejawnym pisaniem?


3

Pomocne może być samo spojrzenie na język; często mi to pomaga (nie jestem rodzimym językiem angielskim).

W duck typing:

1) słowo typingto nie oznacza pisania na klawiaturze (tak jak utrzymywał się w moim umyśle obraz), oznacza określenie „ jaki rodzaj rzeczy to ta rzecz?

2) słowo duckwyraża, w jaki sposób dokonuje się tego ustalenia; to rodzaj „luźnego” określenia, jak w: „ jeśli chodzi jak kaczka ... to jest kaczka ”. Jest „luźny”, ponieważ rzecz może być kaczką lub nie, ale to, czy tak naprawdę jest kaczką, nie ma znaczenia; ważne jest to, że mogę to zrobić, co mogę zrobić z kaczkami i oczekiwać zachowań wykazywanych przez kaczki. Mogę nakarmić to bułką tartą i rzecz może pójść w moją stronę, rzucić się na mnie lub się wycofać ... ale nie pochłonie mnie tak, jak zrobiłby to grizzly.


2

Wiem, że nie udzielam ogólnej odpowiedzi. W Ruby nie deklarujemy typów zmiennych ani metod - wszystko jest tylko rodzajem obiektu. Zatem reguła brzmi: „Klasy nie są typami”

W Ruby klasa nigdy nie jest (OK, prawie nigdy) typem. Zamiast tego typ obiektu jest bardziej określony przez to, co ten obiekt może zrobić. W Ruby nazywamy to pisaniem kaczek. Jeśli obiekt chodzi jak kaczka i mówi jak kaczka, tłumacz chętnie traktuje go jak kaczkę.

Na przykład możesz pisać procedurę dodawania informacji o utworze do ciągu. Jeśli pochodzisz z języka C # lub Java, możesz ulec pokusie napisania:

def append_song(result, song)
    # test we're given the right parameters 
    unless result.kind_of?(String)
        fail TypeError.new("String expected") end
    unless song.kind_of?(Song)
        fail TypeError.new("Song expected")
end

result << song.title << " (" << song.artist << ")" end
result = ""

append_song(result, song) # => "I Got Rhythm (Gene Kelly)"

Wpisz kaczkę Ruby Ruby, a napiszesz coś o wiele prostszego:

def append_song(result, song)
    result << song.title << " (" << song.artist << ")"
end

result = ""
append_song(result, song) # => "I Got Rhythm (Gene Kelly)"

Nie musisz sprawdzać typu argumentów. Jeśli obsługują << (w przypadku wyniku) lub tytuł i wykonawcę (w przypadku piosenki), wszystko po prostu zadziała. Jeśli nie, Twoja metoda i tak zgłosi wyjątek (tak jak zrobiłby to, gdybyś sprawdził typy). Ale bez sprawdzenia twoja metoda nagle staje się znacznie bardziej elastyczna. Możesz przekazać mu tablicę, ciąg, plik lub dowolny inny obiekt, który dołącza się za pomocą <<, i to po prostu zadziała.


2

Wpisywanie kaczek nie jest typem podpowiedzi!

Zasadniczo, aby użyć „pisania kaczego”, nie będziesz kierować na konkretny typ, ale raczej na szerszy zakres podtypów (nie mówiąc o dziedziczeniu, kiedy mam na myśli podtypy, mam na myśli „rzeczy” pasujące do tych samych profili) za pomocą wspólnego interfejsu .

Możesz sobie wyobrazić system, który przechowuje informacje. Aby pisać / czytać informacje, potrzebujesz pewnego rodzaju pamięci i informacji.

Rodzaje przechowywania mogą być następujące: plik, baza danych, sesja itp.

Interfejs poinformuje Cię o dostępnych opcjach (metodach) niezależnie od rodzaju pamięci, co oznacza, że ​​w tym momencie nic nie jest zaimplementowane! Innymi słowy, interfejs nie wie nic o tym, jak przechowywać informacje.

Każdy system pamięci masowej musi wiedzieć o istnieniu interfejsu, wdrażając jego bardzo te same metody.

interface StorageInterface
{
   public function write(string $key, array $value): bool;
   public function read(string $key): array;
}


class File implements StorageInterface
{
    public function read(string $key): array {
        //reading from a file
    }

    public function write(string $key, array $value): bool {
         //writing in a file implementation
    }
}


class Session implements StorageInterface
{
    public function read(string $key): array {
        //reading from a session
    }

    public function write(string $key, array $value): bool {
         //writing in a session implementation
    }
}


class Storage implements StorageInterface
{
    private $_storage = null;

    function __construct(StorageInterface $storage) {
        $this->_storage = $storage;
    }

    public function read(string $key): array {
        return $this->_storage->read($key);
    }

    public function write(string $key, array $value): bool {
        return ($this->_storage->write($key, $value)) ? true : false;
    }
}

Teraz za każdym razem, gdy musisz pisać / czytać informacje:

$file = new Storage(new File());
$file->write('filename', ['information'] );
echo $file->read('filename');

$session = new Storage(new Session());
$session->write('filename', ['information'] );
echo $session->read('filename');

W tym przykładzie używasz Duck Typing in Storage Konstruktor:

function __construct(StorageInterface $storage) ...

Mam nadzieję, że to pomogło;)


2

Przemierzanie drzewa techniką pisania kaczką

def traverse(t):
    try:
        t.label()
    except AttributeError:
        print(t, end=" ")
    else:
        # Now we know that t.node is defined
        print('(', t.label(), end=" ")
        for child in t:
            traverse(child)
        print(')', end=" ")

0

Myślę, że mieszanie pisania dynamicznego, pisania statycznego i pisania kaczego jest mylące. Pisanie kaczką jest niezależną koncepcją, a nawet statyczny język pisma, taki jak Go, może mieć system sprawdzania typów, który implementuje pisanie kaczych. Jeśli system typów sprawdzi metody (zadeklarowanego) obiektu, ale nie typ, można go nazwać językiem kaczym.


-1

Staram się zrozumieć na swój sposób słynne zdanie: „Python nie obchodzi, czy przedmiot jest prawdziwą kaczką, czy nie. Wszystko zależy od tego, czy obiekt, pierwszy„ kwak ”, a drugi„ jak kaczka ”.

Jest dobra strona internetowa. http://www.voidspace.org.uk/python/articles/duck_typing.shtml#id14

Autor wskazał, że pisanie kaczych pozwala tworzyć własne klasy, które mają własną wewnętrzną strukturę danych - ale są dostępne przy użyciu normalnej składni Pythona.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.