interface - monkey - duck typing ruby
¿En qué se diferencia la tipificación de pato del tipo y las interfaces "variante" anterior? (10)
Sigo viendo la frase "pato escribiendo", e incluso encontré uno o dos ejemplos de código. Estoy demasiado perezosa para hacer mi propia investigación, ¿puede alguien decirme brevemente:
- la diferencia entre un ''tipo de pato'' y un ''tipo de variante'' de skool antiguo, y
- proporcionar un ejemplo de donde podría preferir el tipado de pato sobre el tipado de variantes, y
- proporcionar un ejemplo de algo que tendría que usar pato escribir para lograrlo?
No pretendo parecer avieso dudando del poder de esta ''nueva'' construcción, y no voy a eludir el problema al negarme a investigar, pero me estoy quedando boquiabierto ante todo el alboroto que he estado viendo sobre últimamente Parece que no mecanografía (también conocido como mecanografía dinámica), así que no veo las ventajas de inmediato.
ADDENDUM: Gracias por los ejemplos hasta ahora. Me parece que usar algo como ''O-> can (Blah)'' es equivalente a hacer una búsqueda de reflexión (que probablemente no es barato), y / o es casi lo mismo que decir (O es IBlah) que el compilador podría poder verificar por usted, pero este último tiene la ventaja de distinguir mi interfaz de IBlah de su interfaz de IBlah, mientras que las otras dos no lo hacen. De acuerdo, tener un montón de pequeñas interfaces flotando alrededor de cada método sería complicado, pero también lo es comprobar muchos métodos individuales ...
... así que de nuevo no lo entiendo. ¿Es un ahorro de tiempo fantástico, o lo mismo en un nuevo saco? ¿Dónde está el ejemplo que requiere tipa de pato?
@Kent Fredric
Su ejemplo puede ciertamente ser hecho sin pato escribiendo usando interfaces explícitas ... más feo sí, pero no es imposible.
Y, personalmente, encuentro que tener contratos bien definidos en las interfaces es mucho mejor para aplicar el código de calidad que confiar en el tipado de patos ... pero esa es solo mi opinión y la tomo con mofa.
public interface ICreature { }
public interface IFly { fly();}
public interface IWalk { walk(); }
public interface IQuack { quack(); }
// ETC
// Animal Class
public class Duck : ICreature, IWalk, IFly, IQuack
{
fly() {};
walk() {};
quack() {};
}
public class Rhino: ICreature, IWalk
{
walk();
}
// In the method
List<ICreature> creatures = new List<ICreature>();
creatures.Add(new Duck());
creatures.Add(new Rhino());
foreach (ICreature creature in creatures)
{
if (creature is IFly)
(creature as IFly).fly();
if (creature is IWalk)
(creature as IWalk).walk();
}
// Etc
Una variante (al menos como las he usado en VB6) contiene una variable de tipo único, bien definido, usualmente estático. Por ejemplo, podría contener un int, o un float, o un string, pero los variats se usan como enteros, los flotantes variantes se usan como flotantes, y las variantes se usan como cadenas.
Duck typing en su lugar utiliza la escritura dinámica. En el tipado de pato, una variable podría ser utilizable como int, o float, o como cadena, si resulta que admite los métodos particulares que admite un int o float o cadena en un contexto particular.
Ejemplo de variantes versus tipa de pato:
Para una aplicación web, supongamos que deseo que mi información de usuario provenga de LDAP en lugar de una base de datos, pero aún quiero que mi información de usuario pueda ser utilizada por el resto del marco web, que se basa en una base de datos y un ORM.
Usando variantes: sin suerte. Puedo crear una variante que pueda contener un objeto UserFromDbRecord o un objeto UserFromLdap, pero los objetos UserFromLdap no serán utilizables por las rutinas que esperan objetos de la jerarquía FromDbRecord.
Usando pato escribiendo: Puedo tomar mi clase UserFromLdap y agregar un par de métodos que lo hacen actuar como una clase UserFromDbRecord. No necesito replicar toda la interfaz FromDbRecord, solo lo suficiente para las rutinas que necesito usar. Si hago esto bien, es una técnica extremadamente poderosa y flexible. Si lo hago mal, produce un código muy confuso y quebradizo (sujeto a rotura si la biblioteca de DB o la biblioteca de LDAP cambian).
Duck typing es solo otro término para tipeo dinámico o de enlace tardío. Un objeto variante que analiza / compila con cualquier acceso de miembro (por ejemplo, obj.Anything) que pueda definirse o no en realidad durante el tiempo de ejecución es el tipado de pato.
Intenta leer el primer párrafo del artículo de Wikipedia sobre tipeo de patos.
Pato escribiendo en Wikipedia
Puedo tener una interfaz (IRunnable) que define el método Run ().
Si tengo otra clase con un método como este:
vacío público RunSomeRunnable (IRunnable rn) {...}
En un lenguaje amigable tipo pato, podía pasar cualquier clase que tuviera un método Run () en el método RunSomeRunnable ().
En un lenguaje estáticamente tipado, la clase que se pasa a RunSomeRunnable necesita implementar explícitamente la interfaz IRunnable.
"Si se ejecuta () como un pato"
La variante se parece más a un objeto en .NET al menos.
Con respecto a su solicitud de un ejemplo de algo que necesitaría usar la tipificación de pato para lograrlo, no creo que exista tal cosa. Pienso en ello como si pensara en usar recursión o si usar iteración. A veces uno solo funciona mejor que el otro.
En mi experiencia, la tipificación de pato hace que el código sea más legible y fácil de comprender (tanto para el programador como para el lector). Pero creo que la tipificación estática más tradicional elimina muchos errores de tipeo innecesarios. Simplemente no hay forma de decir objetivamente que uno es mejor que otro o incluso decir qué situaciones uno es más efectivo que el otro.
Digo que si te sientes cómodo usando tipeo estático, entonces úsalo. Pero al menos deberías intentar escribir pato (y usarlo en un proyecto no trivial si es posible).
Para responderle más directamente:
... así que de nuevo no lo entiendo. ¿Es un ahorro de tiempo fantástico, o lo mismo en un saco nuevo?
Son ambos. Todavía estás atacando los mismos problemas. Lo estás haciendo de otra manera. A veces eso es todo lo que necesita hacer para ahorrar tiempo (incluso si no es por ninguna otra razón para obligarse a pensar en hacer algo de otra manera).
¿Es una panacea que salvará a toda la humanidad de la extinción? No. Y cualquiera que te diga lo contrario es un fanático.
Probablemente, nada requiera pato-tipado, pero puede ser conveniente en ciertas situaciones. Digamos que tienes un método que toma y usa un objeto de la clase sellada Duck de una biblioteca de terceros. Y quiere que el método sea comprobable. Y Duck tiene una API terriblemente grande (algo así como ServletRequest) de la cual solo necesitas preocuparte por un pequeño subconjunto. ¿Cómo lo pruebas?
Una forma es hacer que el método tome algo que grazna. Entonces puedes simplemente crear un objeto de simulación de graznido.
Creo que el punto central de la tipificación de pato es cómo se usa. Uno usa la detección de métodos y la introspección de la entidad para saber qué hacer con ella, en lugar de declarar de antemano cuál será (dónde sabe qué hacer con ella).
Probablemente sea más práctico en los lenguajes OO, donde las primitivas no son primitivas, y en cambio son objetos.
Creo que la mejor manera de resumirlo, en tipo de variante, una entidad es / puede ser cualquier cosa, y lo que es es incierto, a diferencia de una entidad que solo se parece a cualquier cosa, pero puedes averiguar qué es preguntándole .
Aquí hay algo que no creo que sea plausible sin patos.
sub dance {
my $creature = shift;
if( $creature->can("walk") ){
$creature->walk("left",1);
$creature->walk("right",1);
$creature->walk("forward",1);
$creature->walk("back",1);
}
if( $creature->can("fly") ){
$creature->fly("up");
$creature->fly("right",1);
$creature->fly("forward",1);
$creature->fly("left", 1 );
$creature->fly("back", 1 );
$creature->fly("down");
} else if ( $creature->can("walk") ) {
$creature->walk("left",1);
$creature->walk("right",1);
$creature->walk("forward",1);
$creature->walk("back",1);
} else if ( $creature->can("splash") ) {
$creature->splash( "up" ) for ( 0 .. 4 );
}
if( $creature->can("quack") ) {
$creature->quack();
}
}
my @x = ();
push @x, new Rhinoceros ;
push @x, new Flamingo;
push @x, new Hyena;
push @x, new Dolphin;
push @x, new Duck;
for my $creature (@x){
new Thread(sub{
dance( $creature );
});
}
De otra forma, necesitarías poner restricciones de tipo para las funciones, lo que cortaría diferentes especies, necesitando que crees diferentes funciones para diferentes especies, haciendo que el código sea realmente infernal de mantener.
Y eso realmente apesta en términos de tratar de realizar una buena coreografía.
En algunas de las respuestas aquí, he visto un uso incorrecto de la terminología, que ha llevado a las personas a dar respuestas incorrectas.
Entonces, antes de dar mi respuesta, voy a proporcionar algunas definiciones:
Fuertemente tipado
Un lenguaje está fuertemente tipado si se aplica el tipo de seguridad de un programa. Eso significa que garantiza dos cosas: algo llamado progreso y otra cosa llamada preservación. El progreso básicamente significa que todos los programas "escritos válidamente" pueden ser ejecutados por la computadora, pueden bloquearse, lanzar una excepción o ejecutarse en un ciclo infinito, pero en realidad se pueden ejecutar. Conservación significa que si un programa está "tipeado válidamente", siempre será "Tipeado válidamente" y que ninguna variable (o ubicación de la memoria) contendrá un valor que no se ajuste a su tipo asignado.
La mayoría de los idiomas tienen la propiedad de "progreso". Sin embargo, hay muchos que no satisfacen la propiedad de "preservación". Un buen ejemplo es C ++ (y C también). Por ejemplo, es posible en C ++ forzar cualquier dirección de memoria para que se comporte como si fuera de cualquier tipo. Esto básicamente permite a los programadores violar el sistema de tipos en cualquier momento que deseen. Aquí hay un ejemplo simple:
struct foo { int x; iny y; int z; } char * x = new char[100]; foo * pFoo = (foo *)x; foo aRealFoo; *pFoo = aRealFoo;
Este código permite que alguien tome una matriz de caracteres y le escriba una instancia "foo". Si C ++ fue fuertemente tipado, esto no sería posible. Escriba lenguajes seguros, como C #, Java, VB, lisp, ruby, python y muchos otros, lanzarían una excepción si intentara lanzar una matriz de caracteres a una instancia "foo".
Débilmente mecanografiado
Algo está débilmente tipado si no está fuertemente tipado.
Estáticamente tipado
Un lenguaje está tipado estáticamente si su sistema de tipo se verifica en tiempo de compilación. Un lenguaje estáticamente tipado puede ser "tipeado débilmente" como C o fuertemente tipado como C #.
Tipeado dinámicamente
Un lenguaje de tipo dinámico es un lenguaje donde los tipos se verifican en tiempo de ejecución. Muchos idiomas tienen una mezcla, de algún tipo, entre tipeo estático y dinámico. C #, por ejemplo, verificará muchas conversiones dinámicamente en tiempo de ejecución porque no es posible verificarlas en tiempo de compilación. Otros ejemplos son lenguajes como Java, VB y Objective-C.
También hay algunos idiomas que son "completamente" o "en su mayoría" tipados dinámicamente, como "lisp", "ruby" y "small talk"
Pato escribiendo
La tipificación de pato es algo completamente ortogonal a la escritura estática, dinámica, débil o fuerte. Es la práctica de escribir código que funcionará con un objeto independientemente de su identidad de tipo subyacente. Por ejemplo, el siguiente código VB.NET:
function Foo(x as object) as object return x.Quack() end function
Funcionará, independientemente de cuál sea el tipo de objeto que se pase a "Foo", siempre que se defina un método llamado "Quack". Es decir, si el objeto se ve como un pato, camina como un pato y habla como un pato, entonces es un pato. Duck typing viene en muchas formas. Es posible tener tipado de pato estático, tipado de pato dinámico, tipado de pato fuerte y tipado de pato semanal. Las funciones de plantilla de C ++ son un buen ejemplo de "tipado de pato estático débil". El ejemplo que se muestra en la publicación "JaredPar" muestra un ejemplo de "fuerte tipificación de pato estática". La vinculación tardía en VB (o el código en Ruby o Python) habilita "tipado de pato dinámico fuerte".
Variante
Una variante es una estructura de datos tipada dinámicamente que puede contener un rango de tipos de datos predefinidos, que incluyen cadenas, tipos enteros, fechas y objetos com. A continuación, define un conjunto de operaciones para asignar, convertir y manipular datos almacenados en variantes. Si una variante está fuertemente tipada depende del idioma en el que se usa. Por ejemplo, una variante en un programa VB 6 está fuertemente tipada. El tiempo de ejecución de VB asegura que las operaciones escritas en código VB se ajustarán a las reglas de tipado para las variantes. La vinculación para agregar una cadena a un IUnknown a través del tipo de variante en VB dará como resultado un error de tiempo de ejecución. En C ++, sin embargo, las variantes están débilmente tipadas porque todos los tipos de C ++ están débilmente tipados.
OK ... ahora que he sacado las definiciones del camino, ahora puedo responder su pregunta:
Una variante, en VB 6, permite una forma de hacer tipa de pato. Hay formas mejores de hacer tipa de pato (el ejemplo de Jared Par es uno de los mejores), que las variantes, pero puedes hacer pato escribiendo con variantes. Es decir, puede escribir una pieza de código que operará en un objeto independientemente de su identidad de tipo subyacente.
Sin embargo, hacerlo con variantes en realidad no da mucha validación. Un mecanismo de tipo de pato estáticamente tipado, como el que describe JaredPar, ofrece los beneficios de la tipificación de pato, más alguna validación adicional del compilador. Eso puede ser realmente útil.
La respuesta simple es la variante está débilmente tipada, mientras que la tipificación de pato está fuertemente tipada.
La tipificación de pato se puede resumir muy bien ya que "si camina como un pato, parece un pato, actúa como un pato, entonces es un pato". Los términos de informática consideran que Pato es la siguiente interfaz.
interface IDuck {
void Quack();
}
Ahora vamos a examinar a Daffy
class Daffy {
void Quack() {
Console.WriteLine("Thatsssss dispicable!!!!");
}
}
Daffy no es realmente un idiota en este caso. Sin embargo, actúa como un pato. Por qué hacer que Daffy implemente IDuck cuando es bastante obvio que Daffy es en realidad un pato.
Aquí es donde entra el tipado Duck. Permite una conversión de tipo seguro entre cualquier tipo que tenga todos los comportamientos de un IDuck y una referencia de IDuck.
IDuck d = new Daffy();
d.Quack();
Ahora se puede invocar el método Quack en "d" con seguridad de tipo completo. No hay posibilidad de un error de tipo de tiempo de ejecución en esta asignación o llamada de método.
Todo lo que puedes hacer con pato-tipado también lo puedes hacer con interfaces. El tipado de patos es rápido y cómodo, pero algunos argumentan que puede conducir a errores (si se nombran dos métodos / propiedades distintos). Las interfaces son seguras y explícitas, pero las personas pueden decir "¿por qué indicar lo obvio?". El descanso es una llama. Todo el mundo elige lo que le conviene y nadie tiene "razón".