uso usando una tipo ser requiere referencia pueden poo partir parametrizada objeto inferir genericos generico generica ejemplos debe cuando crear clase argumentos c# generics haskell polymorphism type-systems

usando - Contrastando los genéricos de C#con los tipos parametrizados de Haskell



no se pueden inferir a partir del uso c# (4)

Basado en un consejo que encontré en StackOverflow, estoy investigando en Haskell. Me complació ver que los tipos parametrizados de Haskell se comportan de manera muy similar a los genéricos de C #. Ambos idiomas aconsejan una sola letra para el parámetro de tipo (generalmente), y ambos idiomas parecen seguir un proceso similar para sustituir un tipo real para el parámetro de tipo. Cogí el concepto bastante rápido debido a eso.

Lo que lleva a esto: ¿cuáles son algunas maneras en que los tipos parametrizados de Haskell difieren de los tipos genéricos de C #? Sé por aprender Ruby que puedes tener grandes problemas al pensar que un concepto con el que estás familiarizado con un idioma es el mismo en otro idioma en el que eres nuevo. Por lo general, el problema es peor cuando las características en realidad son muy similares ... porque generalmente no son iguales al 100%. Entonces, ¿cuáles son algunos de los "errores" por los que me pueden morder si asumo que entiendo tipos parametrizados según mi conocimiento de los genéricos de C #?

Gracias.


Aquí hay una diferencia a tener en cuenta:

C # tiene subtipos, pero Haskell no, lo que significa que, para una cosa, sabes más cosas simplemente mirando un tipo de Haskell.

id :: a -> a

Esta función de Haskell toma un valor de un tipo y devuelve ese mismo valor de ese mismo tipo. Si le das un Bool , te devolverá un Bool . Dale un Int , te devolverá un Int . Dale una Person , te devolverá una Person .

En C #, no puedes estar tan seguro. Esto es esa ''función'' en C #:

public T Id<T>(T x);

Ahora, debido a los subtipos se podría llamar así:

var pers = Id<Person>(new Student());

Mientras que pers es de tipo Person , el argumento de la función Id no lo es. De hecho, las personas pueden tener un tipo más específico que solo Person . Person podría incluso ser un tipo abstracto, garantizando que las personas tendrán un tipo más específico.

Como puede ver, incluso con una función tan simple como la id el sistema de tipo .NET ya permite mucho más que el sistema de tipo más estricto de Haskell . Si bien eso podría ser útil para hacer algún trabajo de programación, también hace que sea más difícil razonar acerca de un programa con solo mirar los tipos de cosas (lo que es una alegría hacer en Haskell).

Y una segunda cosa, hay un polimorfismo ad hoc (también conocido como sobrecarga) en Haskell, a través de un mecanismo conocido como "clases de tipo".

equals :: Eq a => a -> a -> Bool

Esta función comprueba si dos valores son iguales. Pero no solo dos valores, solo los valores que tienen instancias para la clase Eq . Esto es algo así como restricciones en los parámetros de tipo en C #:

public bool Equals<T>(T x, T y) where T : IComparable

Hay una diferencia, sin embargo. Por un lado, los subtipos: usted podría crear una instancia con la Person y llamarlo con el Student y el Teacher .

Pero también hay una diferencia en lo que esto compila. El código C # compila casi exactamente lo que dice su tipo. El verificador de tipos se asegura de que los argumentos implementen la interfaz adecuada, y de que usted sea bueno.

Mientras que el código de Haskell cumple con algo como esto:

equals :: EqDict -> a -> a -> Bool

La función obtiene un argumento adicional , un diccionario de todas las funciones que necesita para hacer las cosas de la Eq . Aquí le mostramos cómo puede usar esta función y qué compila para:

b1 = equals 2 4 --> b1 = equals intEqFunctions 2 4 b2 = equals True False --> b2 = equals boolEqFunctions True False

Esto también muestra lo que hace que el subtipo sea un dolor, imagina si esto es posible.

b3 = equals someStudent someTeacher --> b3 = equals personEqFunctions someStudent someTeacher

¿Cómo se supone que el diccionario personEqFunctions determine si un Student es igual a un Teacher ? Ni siquiera tienen los mismos campos.

En resumen, aunque las restricciones de tipo de Haskell a primera vista pueden parecer restricciones de tipo .NET, se implementan de manera completamente diferente y se compilan en dos cosas realmente diferentes.


Otra gran diferencia es que los genéricos de C # no permiten la abstracción sobre los constructores de tipos (es decir, tipos distintos a *) mientras que Haskell sí lo hace. Intente traducir el siguiente tipo de datos en una clase de C #:

newtype Fix f = In { out :: f (Fix f) }


Para continuar con el tema, "puedes tener grandes problemas pensando que un concepto con el que estás familiarizado con un idioma es el mismo en otro idioma [para el que eres nuevo]" parte de esta pregunta:

Aquí está la diferencia clave (de, por ejemplo, Ruby) que necesita comprender cuando usa clases de tipo Haskell. Dada una función tal como

add :: Num a => a -> a -> a add x y = x + y

Esto no significa que x y y sean ambos tipos de clase Num . Esto significa que y son exactamente del mismo tipo, cuyo tipo es de clase Num . "Bueno, por supuesto que dices; a es lo mismo que a ". Lo que digo también, pero me tomó muchos meses dejar de pensar que si x era un Int y era un Integer , sería como agregar un Fixnum y un Bignum en Ruby. Más bien:

*Main> add (2::Int) (3::Integer) <interactive>:1:14: Couldn''t match expected type `Int'' against inferred type `Integer'' In the second argument of `add'', namely `(3 :: Integer)'' In the expression: add (2 :: Int) (3 :: Integer) In the definition of `it'': it = add (2 :: Int) (3 :: Integer)

En otras palabras, la subclasificación (aunque ambas instancias de Num son, por supuesto, también instancias de Eq ) y la tipificación de pato han desaparecido, bebé.

Esto suena bastante simple y obvio, pero lleva bastante tiempo entrenarse para comprender esto instintivamente, en lugar de solo intelectualmente, al menos si ha venido de años de Java y Ruby.

Y no, una vez que entendí esto, no me pierdo las subclases. (Bueno, tal vez un poco de vez en cuando, pero he ganado mucho más de lo que he perdido. Y cuando realmente lo extraño, puedo intentar abusar de los tipos existenciales).


También podemos hacer otras cosas con clases de tipo Haskell ahora. Buscar "genéricos" en Haskell abre un campo completo de programación genérica polimórfica de rango superior, más allá del polimorfismo paramétrico estándar que la mayoría de la gente considera "genéricos".

Por ejemplo, GHC ganó recientemente familias de tipos, permitiendo todo tipo de capacidades de programación de tipos interesantes. Un ejemplo muy simple son las decisiones de representación de datos por tipo para contenedores polimórficos arbitrarios.

Puedo hacer una clase para decir, listas,

class Listy a where data List a -- this allows me to write a specific representation type for every particular ''a'' I might store! empty :: List a cons :: a -> List a -> List a head :: List a -> a tail :: List a -> List a

Puedo escribir funciones que operan en cualquier cosa que ejemplifique la Lista:

map :: (Listy a, Listy b) => (a -> b) -> List a -> List b map f as = go as where go xs | null xs = empty | otherwise = f (head xs) `cons` go (tail xs)

Y sin embargo, nunca hemos dado un tipo particular de representación.

Ahora que es una clase para una lista genérica. Puedo dar representaciones astutas particulares basadas en los tipos de elementos. Por ejemplo, para las listas de Int, podría usar una matriz:

instance Listy Int where data List Int = UArray Int Int ...

Así que puedes empezar a hacer una programación genérica bastante poderosa.