que monkey duck programming-languages duck-typing

programming-languages - monkey - duck typing ruby



¿Qué está escribiendo pato? (10)

Encontré el término escribir pato mientras leía temas aleatorios sobre software en línea y no lo entendí completamente.

¿Qué es "escribir pato"?


Explicación simple (sin código)

La discusión de la semántica de la pregunta es bastante matizada (y muy académica), pero aquí está la idea general:

Escribiendo pato

("Si camina como un pato y canta como un pato, entonces es un pato"). Sí, pero ¿qué significa eso? Esto se ilustra mejor con el ejemplo:

Ejemplo: lenguas tipificadas dinámicamente

Imagina que tengo una varita mágica. Tiene poderes especiales. Si agito la varita y digo "¡Conduce!" ¡A un coche, pues bien, se conduce!

¿Funciona en otras cosas? No estoy seguro: así que lo intento en un camión. Wow - ¡también conduce! Luego lo pruebo en aviones, trenes y 1 maderas. ¡Todos conducen!

¿Pero funcionaría en decir, una taza de té? Error: KAAAA-BOOOOOOM! Eso no funcionó tan bien. ====> ¡Las tazas de té no pueden conducir! duh !?

Este es básicamente el concepto de tipificación de pato. Es un sistema tipo try-before-you-buy . Si funciona, todo está bien. Pero si falla, bueno, estallará en tu cara.

En otras palabras, nos interesa lo que puede hacer el objeto, en lugar de lo que es el objeto .

Ejemplo: lenguas tipificadas estáticamente

Si estábamos preocupados por lo que realmente era el objeto , entonces nuestro truco de magia funcionará solo en tipos preestablecidos, autorizados, en este caso, pero fallará en otros objetos que pueden conducir : camiones, ciclomotores, tuk-tuks, etc. No funcionará en camiones porque nuestra varita mágica espera que solo funcione en automóviles .

En otras palabras, en este escenario, la varita mágica observa muy de cerca lo que es el objeto (¿es un automóvil?) En lugar de lo que puede hacer el objeto (por ejemplo, si los automóviles, camiones, etc. pueden conducir).

La única forma en que puede hacer que un camión conduzca es si de alguna manera puede obtener la varita mágica para esperar tanto a los camiones como a los automóviles (tal vez "implementando una interfaz común"). Si no sabes lo que eso significa, simplemente ignóralo por el momento.

Resumen: Salida clave

Lo que es importante en la escritura de pato es lo que realmente puede hacer el objeto , en lugar de lo que es el objeto.


Considere que está diseñando una función simple, que obtiene un objeto de tipo Bird y llama a su método walk() . Hay dos enfoques, que puedes pensar:

  1. Esta es mi función y debo estar seguro de que solo acepta el Bird o su código no se compilará. Si alguien quiere usar mi función, debe tener en cuenta que solo acepto Bird
  2. Mi función obtiene cualquier objects y acabo de llamar al método walk() del objeto. Entonces, si el object puede walk() es correcto, si no puede, mi función fallará. Así que aquí no es importante que el objeto sea un Bird o cualquier otra cosa, es importante que pueda walk() (Esto es escribir pato )

Se debe tener en cuenta que la tipificación de pato puede ser útil en algunos casos, por ejemplo, Python usa la tipificación de pato mucho.

Lectura útil


Duck Typing no es Type Hinting!

Básicamente, para usar "tipografía de pato", no se dirigirá a un tipo específico, sino a una gama más amplia de subtipos (sin hablar de herencia, cuando me refiero a subtipos me refiero a "cosas" que se ajustan a los mismos perfiles) mediante una interfaz común .

Puedes imaginar un sistema que almacene información. Para escribir / leer información necesita algún tipo de almacenamiento e información.

Los tipos de almacenamiento pueden ser: archivo, base de datos, sesión, etc.

La interfaz le permitirá conocer las opciones (métodos) disponibles independientemente del tipo de almacenamiento, lo que significa que en este punto no se implementa nada. En otras palabras, la interfaz no sabe nada sobre cómo almacenar información.

Cada sistema de almacenamiento debe conocer la existencia de la interfaz implementando sus mismos métodos.

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; } }

Así que ahora, cada vez que necesite escribir / leer información:

$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'');

En este ejemplo terminas usando Duck Typing en el constructor de almacenamiento:

function __construct(StorageInterface $storage) ...

Espero que haya ayudado;)


Es un término usado en lenguajes dinámicos que no tienen una tipificación fuerte .

La idea es que no necesita un tipo para invocar un método existente en un objeto; si un método está definido en él, puede invocarlo.

El nombre proviene de la frase "Si parece un pato y los curanderos como un pato, es un pato".

Wikipedia tiene mucha más información.


Escribiendo pato

Si habla y camina como un pato, entonces es un pato.

Esto suele denominarse abducción (razonamiento abductivo o también llamada retroducción , una definición más clara, creo):

  • de C (conclusión, lo que vemos ) y R (regla, lo que sabemos ), aceptamos / decidimos / asumimos P (Premisa, propiedad ) en otras palabras, un hecho dado

    ... la base misma del diagnóstico médico

    con patos: C = camina, habla , R = como un pato , P = es un pato

Volver a la programación:

  • el objeto o tiene el método / propiedad mp1 y la interfaz / tipo T requiere / define mp1

  • el objeto o tiene el método / propiedad mp2 y la interfaz / tipo T requiere / define mp2

  • ...

Entonces, más que simplemente aceptar mp1 ... en cualquier objeto siempre que cumpla con alguna definición de mp1 ..., el compilador / tiempo de ejecución también debe estar de acuerdo con la afirmación o es de tipo T

Y bueno, ¿es el caso con los ejemplos anteriores? ¿Es Duck typing esencialmente no es tipado? ¿O deberíamos llamarlo tipificación implícita?


Mirar el lenguaje en sí puede ayudar; a menudo me ayuda (no soy un hablante nativo de inglés).

En duck typing :

1) la palabra typing no significa escribir en un teclado (como era la imagen persistente en mi mente), significa determinar " ¿qué tipo de cosa es esa cosa? "

2) la palabra duck expresa cómo se hace esa determinación; Es una especie de determinación ''suelta'', como en: " si camina como un pato ... es un pato ". Es ''suelto'' porque la cosa puede ser un pato o no, pero si realmente es un pato no importa; lo que importa es que puedo hacer con él lo que puedo hacer con los patos y esperar comportamientos exhibidos por los patos. Puedo alimentarlo con pan rallado y la cosa puede ir hacia mí o cargarme o retroceder ... pero no me devorará como lo haría un grizzly.


Sé que no estoy dando respuesta generalizada. En Ruby, no declaramos los tipos de variables o métodos, todo es solo un tipo de objeto. Así que la regla es "Las clases no son tipos"

En Ruby, la clase nunca es (OK, casi nunca) el tipo. En su lugar, el tipo de un objeto se define más por lo que ese objeto puede hacer. En Ruby, llamamos a este pato escribiendo. Si un objeto camina como un pato y habla como un pato, entonces el intérprete estará encantado de tratarlo como si fuera un pato.

Por ejemplo, puede estar escribiendo una rutina para agregar información de la canción a una cadena. Si proviene de un fondo de C # o Java, puede tener la tentación de escribir esto:

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)"

Abraza la escritura de pato de Ruby y escribirías algo mucho más simple:

def append_song(result, song) result << song.title << " (" << song.artist << ")" end result = "" append_song(result, song) # => "I Got Rhythm (Gene Kelly)"

No es necesario verificar el tipo de los argumentos. Si admiten << (en el caso del resultado) o título y artista (en el caso de la canción), todo funcionará. Si no lo hacen, tu método lanzará una excepción de todos modos (tal como lo hubiera hecho si hubieras marcado los tipos). Pero sin el control, su método es, de repente, mucho más flexible. Podría pasarle una matriz, una cadena, un archivo o cualquier otro objeto que se agregue usando <<, y simplemente funcionará.


Travesía de árboles con técnica de tipado de pato.

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=" ")


Wikipedia tiene una explicación bastante detallada:

Wikipedia

duck typing es un estilo de escritura dinámica en el que el conjunto actual de métodos y propiedades de un objeto determina la semántica válida, en lugar de su herencia de una clase particular o implementación de una interfaz específica.

La nota importante es probable que, al escribir pato, un desarrollador se preocupa más por las partes del objeto que se consumen en lugar de por el tipo subyacente real.


La escritura de pato significa que una operación no especifica formalmente los requisitos que deben cumplir sus operandos, sino que simplemente lo prueba con lo que se da.

A diferencia de lo que otros han dicho, esto no necesariamente se relaciona con lenguajes dinámicos o problemas de herencia.

Ejemplo de tarea: llamar a algún método Quack en un objeto.

Sin usar la escritura de pato, una función que realiza esta tarea debe especificar de antemano que su argumento debe ser compatible con algún método de Quack . Una forma común es el uso de interfaces.

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

Llamar a f(42) falla, pero f(donald) funciona siempre que donald sea ​​una instancia de un IQuack IQuack.

Otro enfoque es la tipificación estructural , pero, una vez más, el método Quack() se especifica formalmente, cualquier cosa que no pueda probar que Quack() s por adelantado causará una falla en el compilador.

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

Incluso podríamos escribir

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

en Haskell, donde la Quackable tipos de Quackable garantiza la existencia de nuestro método.

Entonces, ¿cómo la escritura de pato cambia esto?

Bueno, como dije, un sistema de escritura de pato no especifica requisitos, pero solo intenta si algo funciona .

Por lo tanto, un sistema de tipos dinámico como Python''s siempre usa la escritura de pato:

def f(x): x.Quack()

Si f obtiene una x soporta un Quack() , todo está bien, si no, se bloqueará en el tiempo de ejecución.

Pero la tipificación de pato no implica una tipificación dinámica en absoluto, de hecho, existe un enfoque de tipificación de pato muy popular pero completamente estático que no da ningún requisito también:

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

La función no dice de ninguna manera que quiere alguna x que pueda Quack , así que en lugar de eso solo intenta en el momento de la compilación y si todo funciona, está bien.