Pregunta

Las clases de tipo parece ser una gran posibilidad para escribir genérico y reutilizables funciones en un muy coherente, eficiente y extensible manera.Pero todavía no "mainstream-lenguaje" les proporciona - Por el contrario: Conceptos, que son bastante analógico idea, han sido excluidos a partir de la próxima C++!

¿Cuál es el razonamiento en contra de typeclasses?Al parecer, muchas lenguas están buscando una manera de lidiar con problemas similares:.NET introducido restricciones genéricas y las interfaces como IComparable que permiten funciones como

T Max<T>(T a, T b) where T : IComparable<T> { // }

para operar en todos los tipos que implementan la interfaz.

Scala en su lugar utiliza una combinación de rasgos y los llamados parámetros implícitos/ver límites, que se pasan automáticamente a las funciones genéricas.

Pero ambos conceptos se muestra aquí tienen grandes desventajas - Interfaces son herencia-base y por lo tanto relativamente lento debido a la indirección y además, no hay posibilidad de dejar que un tipo existente ponerlas en práctica.

Si necesitábamos una abstracción de un Monoid, podríamos muy bien escribir una interfaz y dejar que nuestros tipos de implementar esto, pero builtin tipos como int nunca podría operar en sus funciones de forma nativa.

Parámetros implícitos en su lugar son incompatibles con regular interfaces/rasgos.

Con el tipo de clase, no sería un problema (pseudo-código)

typeclass Monoid of A where
    static operator (+) (x : A, y : A) : A
    static val Zero : A 
end

instance Int of Monoid where
   static operator (+) (x : Int, y : Int) : Int = x + y
   static val Zero : Int = 0
end

Así que ¿por qué no usar el tipo de clases?¿Tienen serias desventajas, después de todo?

Editar:Por favor, no confundir typeclasses estructurales de escribir, puro plantillas de C++ o de pato a escribir.Un typeclass es crear instancias explícitamente por tipos, y no sólo satisfecho por convención.Además puede llevar útil implementaciones y no sólo definir una interfaz.

¿Fue útil?

Solución

Los conceptos se excluyeron porque el comité no creía que pudiera acertarlos a tiempo y porque no se consideraban esenciales para la liberación. No es que no piensen que son una buena idea, simplemente no piensan que su expresión para C ++ es madura: http://herbsutter.wordpress.com/2009/07/21/trip-report/

Los tipos estáticos intentan evitar que pase un objeto a una función, que no satisface los requisitos de la función. En C ++, este es un gran problema, porque en el momento en que el código accede al objeto, no se puede verificar que sea lo correcto.

Los conceptos intentan evitar que pase un parámetro de plantilla, que no satisface los requisitos de la plantilla. Pero en el momento en que el compilador accede al parámetro de plantilla, ya está comprobando que es lo correcto, incluso sin Conceptos. Si intenta usarlo de una manera que no es compatible, obtendrá un error de compilación [*]. En el caso del código pesado que usa plantillas, puede obtener tres pantallas llenas de corchetes angulares, pero en principio es un mensaje informativo. La necesidad de detectar errores antes de una compilación fallida es menos urgente que la necesidad de detectar errores antes de un comportamiento indefinido en tiempo de ejecución.

Los conceptos facilitan la especificación de interfaces de plantilla que funcionarán en múltiples instancias . Esto es significativo, pero es un problema mucho menos acuciante que especificar interfaces de funciones que funcionen en varias llamadas.

En respuesta a su pregunta: cualquier declaración formal " Implemento esta interfaz " tiene una gran desventaja, ya que requiere que se invente la interfaz antes de implementarla. Los sistemas de inferencia de tipos no lo hacen, pero tienen la gran desventaja de que los idiomas en general no pueden expresar la totalidad de una interfaz utilizando tipos, por lo que es posible que tenga un objeto que se infiere que es del tipo correcto, pero que no tiene el semántica atribuida a ese tipo. Si su idioma aborda las interfaces (en particular, si las combina con las clases), AFAIK debe adoptar una postura aquí y elegir su desventaja.

[*] Por lo general. Hay algunas excepciones, por ejemplo, el sistema de tipo C ++ actualmente no le impide usar un iterador de entrada como si fuera un iterador directo. Necesitas rasgos iteradores para eso. Solo escribir pato no te impide pasar un objeto que camina, nada y grazna, pero en una inspección minuciosa en realidad no hace ninguna de esas cosas como hace un pato, y se sorprende al saber que pensaste que lo haría ;-)

Otros consejos

Las Interfaces no necesita ser de herencia basado en...eso es diferente y separado decisión de diseño.El nuevo Ir el lenguaje dispone de interfaces, pero no tiene herencia, por ejemplo:"un tipo automáticamente satisface cualquier interfaz que especifica un subconjunto de sus métodos", como el Go Preguntas frecuentes lo pone.Simionato del reflexiones acerca de la herencia e interfaces, impulsado por la reciente liberación, puede ser vale la pena leer.

Estoy de acuerdo en que typeclasses son aún más potentes, esencialmente porque, como clases base abstractas, que le permiten especificar también útil (código de la definición de un extra método de X en términos de los demás para todos los tipos que de otro modo el partido de la clase base, pero no se definen a X a sí mismos), sin la herencia de equipaje que el Abecedario (a diferencia de las interfaces) casi inevitablemente llevan. Casi inevitablemente, porque, por ejemplo, el de Python Abc "hacer creer" que implican la herencia, en términos de la conceptualización que ofrecen...pero, de hecho, no es necesario patrimonio-base (muchos de ellos son sólo la comprobación de la presencia y la firma de ciertos métodos, como Ir de interfaces).

En cuanto a por qué lo haría un diseñador del lenguaje (como Guido, en el caso de Python) elegir a los "lobos en la ropa de la oveja" como Python, Abc, sobre el más sencillo de Haskell-como typeclasses que me había propuesto desde que en el año 2002, esa es una pregunta más difícil de contestar.Después de todo, no es como si Python tiene ningún remordimiento en contra de préstamo de conceptos de Haskell (por ejemplo, lista de comprensión / generador de expresiones -- Python necesita una dualidad aquí, mientras Haskell no, porque Haskell es "perezoso").La hipótesis mejor que puedo ofrecer es que, por ahora, la herencia es tan familiar para la mayoría de los programadores que la mayoría de los diseñadores de lenguajes sienten que pueden ganar más fácil aceptación por la fundición de las cosas de esa manera (aunque Vaya a los diseñadores deben ser elogiados por no hacerlo).

Permítanme comenzar en negrita: Entiendo perfectamente la motivación de tenerlo y no puedo entender la motivación de algunas personas para argumentar en contra ...

Lo que quiere es polimorfismo ad hoc no virtual.

  • ad hoc: la implementación puede variar
  • no virtual: por razones de rendimiento; despacho en tiempo de compilación

El resto es azúcar en mi opinión.

C ++ ya tiene polimorfismo ad hoc a través de plantillas. " Conceptos " sin embargo, aclararía qué tipo de funcionalidad polimórfica ad hoc utiliza la entidad definida por el usuario.

C # simplemente no tiene forma de hacerlo. Un enfoque que no sería no virtual : si tipos como float simplemente implementaran algo como " INumeric " o " IAddable " (...) al menos podríamos escribir un min, max, lerp genérico y basado en esa abrazadera, maprange, bezier (...). Sin embargo, no sería rápido. No quieres eso.

Formas de arreglar esto: Dado que .NET hace la compilación JIT de todos modos, también genera un código diferente para List<int> que para List<MyClass> (debido a las diferencias de valor y tipos de referencia) probablemente no agregaría tanta sobrecarga para generar también código diferente para el anuncio partes polimórficas hoc. El lenguaje C # solo necesitaría una forma de expresarlo. Una forma es lo que bosquejaste.

Otra forma sería agregar restricciones de tipo a la función usando una función polimórfica ad hoc:

    U SuperSquare<T, U>(T a) applying{ 
         nonvirtual operator (*) T (T, T) 
         nonvirtual Foo U (T)
    }
    {
        return Foo(a * a);
    }

Por supuesto, podría terminar con más y más restricciones al implementar Bar que usa Foo. Por lo tanto, es posible que desee un mecanismo para dar un nombre a varias restricciones que usa regularmente ... Sin embargo, esto nuevamente es azúcar y una forma de abordarlo sería simplemente usar el concepto de tipo de clase ...

Dar un nombre a varias restricciones es como definir una clase de tipo, pero me gustaría verlo como una especie de mecanismo de abreviatura: azúcar para una colección arbitraria de restricciones de tipo de función:

    // adhoc is like an interface: it is about collecting signatures
    // but it is not a type: it dissolves during compilation 
    adhoc AMyNeeds<T, U>
    {
         nonvirtual operator (*) T (T, T) 
         nonvirtual Foo U (T)
    } 

    U SuperSquare<T, U>(T a) applying AMyNeeds<T, U>        
    {
        return Foo(a * a);
    }

En algún lugar " main " Todos los argumentos de tipo son conocidos y todo se vuelve concreto y se puede compilar juntos.

Lo que falta todavía es la falta de crear diferentes implementaciones. En el ejemplo superior, simplemente utilizamos funciones polimórficas y dejamos que todos sepan ...

La implementación nuevamente podría seguir el camino de los métodos de extensión, en su capacidad de agregar funcionalidad a cualquier clase en cualquier momento:

 public static class SomeAdhocImplementations
 {
    public nonvirtual int Foo(float x)
    {
        return round(x);
    }
 }

En main ahora puedes escribir:

    int a = SuperSquare(3.0f); // 3.0 * 3.0 = 9.0 rounded should return 9

El compilador verifica todo " no virtual " funciones ad hoc, encuentra tanto un operador flotante incorporado (*) como un int Foo (float) y, por lo tanto, puede compilar esa línea.

El polimorfismo ad hoc, por supuesto, viene con la desventaja de que tiene que volver a compilar para cada tipo de tiempo de compilación para que se inserten las implementaciones correctas. Y probablemente IL no soporta que se ponga en un dll. Pero tal vez trabajan en eso de todos modos ...

No veo una necesidad real de instanciación de una construcción de clase de tipo. Si algo fallara en la compilación, obtenemos los errores de las restricciones o si se unen con un & "; Adhoc &"; codeclock el mensaje de error podría volverse aún más legible.

    MyColor a = SuperSquare(3.0f); 
    // error: There are no ad hoc implementations of AMyNeeds<float, MyColor> 
    // in particular there is no implementation for MyColor Foo(float)

Pero, por supuesto, también la instanciación de una clase de tipo / & "; interfaz de polimorfismo adhoc &"; Es pensable. El mensaje de error indicaría: & Quot; The AMyNeeds constraint of SuperSquare has not been matched. AMyNeeds is available as StandardNeeds : AMyNeeds<float, int> as defined in MyStandardLib & Quot ;. También sería posible poner la implementación en una clase junto con otros métodos y agregar & Quot; interfaz adhoc & Quot; a la lista de interfaces compatibles.

Pero independientemente del diseño de lenguaje particular: no veo la desventaja de agregarlos de una forma u otra. Guardar lenguajes tipados estáticamente siempre tendrá que empujar los límites del poder expresivo, ya que comenzaron permitiendo muy poco, lo que tiende a ser un conjunto más pequeño de poder expresivo que un programador normal.hubiera esperado que fuera posible ...

tldr: estoy de tu lado. Cosas como esta apestan en los principales idiomas escritos estáticamente. Haskell mostró el camino.

  

¿Cuál es el razonamiento contra las clases de tipos?

La complejidad de implementación para los escritores de compiladores siempre es una preocupación cuando se consideran nuevas características del lenguaje. C ++ ya cometió ese error y ya hemos sufrido años de compiladores de C ++ con errores como consecuencia.

  

Las interfaces se basan en la herencia y, por lo tanto, son relativamente lentas debido a la indirección y, además, no hay posibilidad de permitir que un tipo existente las implemente

No es cierto. Mire el sistema de objetos de tipo estructural de OCaml, por ejemplo:

# let foo obj = obj#bar;;
val foo : < bar : 'a; .. > -> 'a = <fun>

Esa función foo acepta cualquier objeto de cualquier tipo que proporcione el método bar necesario.

Lo mismo para el sistema de módulos de orden superior de ML. De hecho, incluso hay una equivalencia formal entre eso y las clases de tipos. En la práctica, las clases de tipos son mejores para las abstracciones a pequeña escala, como la sobrecarga del operador, mientras que los módulos de orden superior son mejores para las abstracciones a gran escala, como la parametrización de listas catenables sobre colas de Okasaki.

  

¿Tienen serias desventajas después de todo?

Mira tu propio ejemplo, aritmética genérica. F # ya puede manejar ese caso en particular gracias a la interfaz INumeric. El tipo F # Matrix incluso usa ese enfoque.

Sin embargo, acaba de reemplazar el código de máquina para agregarlo con despacho dinámico a una función separada, haciendo que los órdenes de magnitud aritméticos sean más lentos. Para la mayoría de las aplicaciones, eso es inútilmente lento. Puede resolver ese problema realizando optimizaciones completas del programa, pero eso tiene desventajas obvias. Además, hay poca coincidencia entre los métodos numéricos para int vs float debido a la solidez numérica, por lo que su abstracción también es prácticamente inútil.

La pregunta seguramente debería ser: ¿alguien puede presentar un argumento convincente para la adopción de clases de tipos?

  

Pero todavía no hay "mainstream-language" proporciona [clases de tipo.]

Cuando se hizo esta pregunta, esto podría haber sido cierto. Hoy en día, hay un interés mucho más fuerte en idiomas como Haskell y Clojure. Haskell tiene clases de tipo ( class / instancia ), Clojure 1.2+ tiene protocolos ( defprotocol / extender ).

  

¿Cuál es el razonamiento contra [clases de tipo]?

No creo que las clases de tipos sean objetivamente "peor" que otros mecanismos de polimorfismo; solo siguen un enfoque diferente. Entonces, la verdadera pregunta es, ¿encajan bien en un lenguaje de programación particular?

Consideremos brevemente cómo las clases de tipos son diferentes de las interfaces en lenguajes como Java o C #. En estos idiomas, una clase solo admite interfaces que se mencionan explícitamente y se implementan en la definición de esa clase. Las clases de tipos, sin embargo, son interfaces que luego pueden agregarse a cualquier tipo ya definido, incluso en otro módulo. Este tipo de extensibilidad de tipo es, obviamente, bastante diferente de los mecanismos en ciertos '' mainstream ''. OO idiomas.


Consideremos ahora las clases de tipos para algunos lenguajes de programación convencionales.

Haskell : no es necesario decir que este idioma tiene clases de tipo .

Clojure : Como se dijo anteriormente, Clojure tiene algo así como clases de tipo en forma de protocolos .

C ++ : Como usted mismo ha dicho, los conceptos se eliminaron de la especificación C ++ 11.

  

Por el contrario: ¡los conceptos, que son una idea bastante analógica, han sido excluidos del próximo C ++!

No he seguido todo el debate sobre esta decisión. Por lo que he leído, los conceptos aún no estaban "listos": todavía había debate sobre los mapas conceptuales. Sin embargo, los conceptos no se abandonaron por completo, se espera que lleguen a la próxima versión de C ++.

C # : con la versión 3 del lenguaje, C # se ha convertido esencialmente en un híbrido de los paradigmas de programación orientados a objetos y funcionales. Se hizo una adición al lenguaje que es conceptualmente bastante similar a las clases de tipos: métodos de extensión . La principal diferencia es que (aparentemente) está asociando nuevos métodos a un tipo existente, no interfaces.

(Por supuesto, el mecanismo del método de extensión no es tan elegante como la instancia de Haskell ... donde la sintaxis. Los métodos de extensiones no están realmente unidos a un tipo, se implementan como una transformación sintáctica. Al final, sin embargo, esto no hace una gran diferencia práctica).

No creo que esto vaya a suceder pronto: los diseñadores de idiomas probablemente ni siquiera agregarán las propiedades de extensión al idioma, y ??las interfaces de extensión incluso ir un paso más allá que eso.

( VB.NET : Microsoft ha estado "co-evolucionando" los lenguajes C # y VB.NET durante algún tiempo, por lo que mis declaraciones sobre C # también son válidas para VB.NET .)

Java : No conozco muy bien Java, pero de los lenguajes C ++, C # y Java, es probable que sea el "más puro". OO lenguaje. No veo cómo las clases de tipos encajarían naturalmente en este idioma.

F # : he encontrado una publicación en el foro que explica ¿Por qué las clases de tipos nunca podrían introducirse en F #? . Esa explicación se centra en el hecho de que F # tiene un sistema de tipo nominativo y no estructural. (Aunque no estoy seguro de si esta es una razón suficiente para que F # no tenga clases de tipo).

Intenta definir un Matroid, que es lo que hacemos (lógicamente y no diciendo oralmente un Matroid), y todavía es probable que sea algo así como una estructura C. Principio de Liskov (último medallista de Turing) se vuelve demasiado abstracto, demasiado categórico, demasiado teórico, menos tratamiento real datos y un sistema de clase teórico más puro, para resolver problemas pragmáticos de forma manual, lo revisé brevemente como PROLOG, código sobre código sobre código sobre código ... mientras que un algoritmo describe secuencias y viajes que entendemos en papel o pizarra. Depende de la meta que tenga, resolviendo el problema con un código mínimo o más abstracto.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top