son productos niza marcas lista las espaƱol cuales clasificaciĆ³n clasificacion clases alfabetica haskell record typeclass

haskell - productos - cuales son las 45 clases de marcas



Elegir entre una clase y un registro (3)

Pregunta básica: ¿qué principios de diseño se deben seguir al elegir entre usar una clase o usar un registro (con campos polimórficos)?

Primero, sabemos que las clases y los registros son esencialmente equivalentes (ya que en Core, las clases se desglosan en diccionarios, que son solo registros). Sin embargo, existen diferencias: las clases se pasan implícitamente, los registros deben ser explícitos.

Mirando un poco más profundo, las clases son realmente útiles cuando:

  1. tenemos muchas representaciones diferentes de ''lo mismo'', y
  2. en el uso real, qué representación se usa puede inferirse.

Las clases son incómodas cuando tenemos (hasta el polimorfismo paramétrico) solo una representación de nuestros datos, pero tenemos varias instancias. Esto lleva al ruido sintáctico de tener que usar newtype para agregar etiquetas adicionales (que solo existen en nuestro código, ya que sabemos que esas etiquetas se borran en tiempo de ejecución) si no queremos activar todo tipo de extensiones problemáticas (es decir, casos superpuestos y / o indecidibles).

Por supuesto, las cosas se ponen más turbias: ¿y si quiero tener restricciones en mis tipos? Tomemos un ejemplo real:

class (Bounded i, Enum i) => Partition a i where index :: a -> i

Podría haberlo hecho tan fácilmente

data Partition a i = Partition { index :: a -> i}

Pero ahora he perdido mis limitaciones, y tendré que agregarlas a funciones específicas en su lugar.

¿Hay pautas de diseño que me ayuden?


Las clases de tipo a veces pueden proporcionar seguridad de tipo adicional (un ejemplo sería Ord con Data.Map.union ). Si tiene circunstancias similares donde elegir las clases de tipo puede ayudar a su tipo de seguridad, entonces use clases de tipo.

Presentaré un ejemplo diferente donde creo que las clases de tipos no proporcionarían seguridad adicional:

class Drawing a where drawAsHtml :: a -> Html drawOpenGL :: a -> IO () exampleFunctionA :: Drawing a => a -> a -> Something exampleFunctionB :: (Drawing a, Drawing b) => a -> b -> Something

No hay nada que el exampleFunctionA pueda hacer y exampleFunctionB no puede hacer ( me resulta difícil explicar por qué, las ideas son bienvenidas ).

En este caso, no veo beneficio de usar una clase de tipo.

(Editado siguiendo los comentarios de Jacques y la pregunta de missingo)


Tiendo a no ver ningún problema con solo requerir restricciones en las funciones. El problema es, supongo, que su estructura de datos ya no modela exactamente lo que usted pretende. Por otro lado, si lo considera como una estructura de datos en primer lugar, entonces eso debería importar menos.

Siento que no necesariamente todavía tengo una buena comprensión de la pregunta, y esto es lo más vago posible, pero mi regla de oro tiende a ser que las clases tipo son cosas que obedecen las leyes (o el significado del modelo) y los tipos de datos. son cosas que codifican cierta cantidad de información.

Cuando queremos configurar el comportamiento de las capas de manera compleja, descubrimos que las clases de tipos comienzan de forma tentadora, pero pueden ser dolorosas rápidamente y cambiar a diccionario de paso hace que las cosas sean más sencillas. Lo que quiere decir que cuando queremos que las implementaciones sean interoperables, debemos recurrir a un tipo de diccionario uniforme.

Esto es tomar dos, expandir un poco en un ejemplo concreto, pero sigue siendo una especie de ideas giratorias ...

Supongamos que queremos modelar las distribuciones de probabilidad sobre los reales. Dos representaciones naturales vienen a la mente.

A) Tipo de clase impulsado

class PDist a where sample :: a -> Gen -> Double

B) Impulsado por diccionario

data PDist = PDist (Gen -> Double)

El primero nos deja hacer

data NormalDist = NormalDist Double Double -- mean, var instance PDist NormalDist where... data LognormalDist = LognormalDist Double Double instance PDist LognormalDist where...

Este último nos deja hacer

mkNormalDist :: Double -> Double -> PDist... mkLognormalDist :: Double -> Double -> PDist...

En el primero, podemos escribir

data SumDist a b = SumDist a b instance (PDist a, PDist b) => PDist (SumDist a b)...

en este último simplemente podemos escribir

sumDist :: PDist -> PDist -> PDist

¿Cuáles son las compensaciones? Type-class impulsada nos permite especificar qué distribuciones se nos da. La compensación es que tenemos que construir un álgebra de distribuciones explícitamente, incluyendo nuevos tipos para sus combinaciones. Impulsado por datos no nos permite restringir las distribuciones que recibimos (o incluso si están bien formadas), pero a cambio podemos hacer lo que queramos.

Además, podemos escribir un parseDist :: String -> PDist relativa facilidad, pero tenemos que pasar por algo de angustia para hacer el equivalente para el enfoque de clase de tipo.

Así que esto es, en cierto sentido, la compensación estática / dinámica tipeada / no tipificada en otro nivel. Sin embargo, podemos darle un giro y argumentar que la clase de tipo, junto con las leyes algebraicas asociadas, especifica la semántica de una distribución de probabilidad. Y el tipo PDist se puede convertir en una instancia de la clase de tipo PDist. Mientras tanto, podemos resignarnos a usar el tipo de PDist (en lugar de typeclass) en casi todas partes, mientras lo consideramos como iso a la torre de instancias y tipos de datos necesarios para usar la clase de tipos de forma más "rica".

De hecho, incluso podemos definir la función PDist básica en términos de funciones de clase de tipo. es decir, mkNormalPDist mv = PDist (sample $ NormalDist mv) De modo que hay mucho espacio en el espacio de diseño para deslizar entre las dos representaciones según sea necesario ...


Nota: No estoy seguro de entender el OP exactamente. Sugerencias / comentarios de mejora apreciados!

Fondo:

Cuando aprendí por primera vez sobre las clases de tipos en Haskell, la regla general que aprendí fue que, en comparación con los lenguajes de Java:

  • las clases de tipos son similares a las interfaces
  • data son similares a las clases

Here''s otra pregunta y respuesta que describen las pautas para el uso de interfaces (también algunos inconvenientes del uso excesivo de la interfaz). Mi interpretación:

  • registros / clases de Java son algo que
  • interfaces / typeclasses son roles que una concreción puede cumplir
  • múltiples concreciones no relacionadas pueden cumplir el mismo rol

Apuesto a que ya sabes todo esto.

Las pautas que trato de seguir para mi propio código son:

  • las clases de tipos son para abstracciones
  • los registros son para concreciones

Entonces en la práctica esto significa:

  • deja que las necesidades de los datos determinen los registros
  • deje que el código del cliente determine cuáles son las interfaces: los clientes deben depender de las abstracciones y, por lo tanto, impulsar la creación y el diseño de clases de tipos

Ejemplo:

typeclass Show , con función show :: (Show s) => s -> String : para datos que se pueden representar como String .

  • los clientes solo quieren convertir los datos en cadenas
  • a los clientes no les importa cuáles son los datos (concreción), solo cuidado de que se pueda representar como una cadena
  • rol de la implementación de datos: puede ser string-ified
  • esto no podría lograrse sin una clase de tipos: cada tipo de datos requeriría una función de conversión con un nombre diferente, ¡qué dolor enfrentar!