haskell type-safety type-alias newtype

haskell - ¿Es una mala forma hacer nuevos tipos/datos para mayor claridad?



type-safety type-alias (5)

Me gustaría saber si es una mala forma de hacer algo como esto:

data Alignment = LeftAl | CenterAl | RightAl type Delimiter = Char type Width = Int setW :: Width -> Alignment -> Delimiter -> String -> String

En lugar de algo como esto:

setW :: Int -> Char -> Char -> String -> String

Sé que rehacer esos tipos no hace nada más que ocupar algunas líneas a cambio de un código más claro. Sin embargo, si uso el Delimiter tipo para múltiples funciones, esto sería mucho más claro para alguien que esté importando este módulo o que lea el código más adelante.

Soy relativamente nuevo en Haskell, así que no sé qué es una buena práctica para este tipo de cosas. Si esto no es una buena idea, o si hay algo que mejoraría la claridad, ¿cuál sería?


Definitivamente es bueno, y aquí hay otro ejemplo, supongamos que tiene este tipo de datos con alguna op:

data Form = Square Int | Rectangle Int Int | EqTriangle Int perimeter :: Form -> Int perimeter (Square s) = s * 4 perimeter (Rectangle b h) = (b * h) * 2 perimeter (EqTriangle s) = s * 3 area :: Form -> Int area (Square s) = s ^ 2 area (Rectangle b h) = (b * h) area (EqTriangle s) = (s ^ 2) `div` 2

Ahora imagina que agregas el círculo:

data Form = Square Int | Rectangle Int Int | EqTriangle Int | Cicle Int

sumar sus operaciones:

perimeter (Cicle r ) = pi * 2 * r area (Cicle r) = pi * r ^ 2

no es muy bueno verdad? Ahora quiero usar Float ... Tengo que cambiar cada Int por Float

data Form = Square Double | Rectangle Double Double | EqTriangle Double | Cicle Double area :: Form -> Double perimeter :: Form -> Double

pero, ¿qué pasa si, para mayor claridad e incluso para reutilización, utilizo el tipo?

data Form = Square Side | Rectangle Side Side | EqTriangle Side | Cicle Radius type Distance = Int type Side = Distance type Radius = Distance type Area = Distance perimeter :: Form -> Distance perimeter (Square s) = s * 4 perimeter (Rectangle b h) = (b * h) * 2 perimeter (EqTriangle s) = s * 3 perimeter (Cicle r ) = pi * 2 * r area :: Form -> Area area (Square s) = s * s area (Rectangle b h) = (b * h) area (EqTriangle s) = (s * 2) / 2 area (Cicle r) = pi * r * r

Eso me permite cambiar el tipo solo cambiando una línea en el código, supongo que quiero que la Distancia esté en Int, solo cambiaré eso

perimeter :: Form -> Distance ... totalDistance :: [Form] -> Distance totalDistance = foldr (/x rs -> perimeter x + rs) 0

Quiero que la Distancia esté en Flotador, así que solo cambio:

type Distance = Float

Si quiero cambiarlo a Int, tengo que hacer algunos ajustes en las funciones, pero eso es otro problema.


El uso de newtype que crea un nuevo tipo con la misma representación que el tipo subyacente pero no sustituible con él, se considera una buena forma. Es una forma barata de evitar la obsesión primitiva , y es especialmente útil para Haskell porque en Haskell los nombres de los argumentos de función no están visibles en la firma.

Newtypes también puede ser un lugar donde colgar instancias de clase de tipos útiles.

Dado que los nuevos tipos son omnipresentes en Haskell, con el tiempo el lenguaje ha ganado algunas herramientas e idiomas para manipularlos:

  • Coercible Una Coercible "mágica" que simplifica las conversiones entre los nuevos tipos y sus tipos subyacentes, cuando el constructor del nuevo tipo está dentro del alcance. A menudo es útil para evitar repeticiones en implementaciones de funciones.

    ghci> coerce (Sum (5::Int)) :: Int

    ghci> coerce [Sum (5::Int)] :: [Int]

    ghci> coerce ((+) :: Int -> Int -> Int) :: Identity Int -> Identity Int -> Identity Int

  • ala Un idioma (implementado en varios paquetes) que simplifica la selección de un nuevo tipo que podríamos usar con funciones como foldMap .

    ala Sum foldMap [1,2,3,4 :: Int] :: Int

  • GeneralizedNewtypeDeriving . Una extensión para las instancias de derivación automática para su newtype según las instancias disponibles en el tipo subyacente.

  • DerivingVia Una extensión más general, para las instancias de derivación automática para su newtype basadas en instancias disponibles en otro newtype con el mismo tipo subyacente.


Estás usando alias de tipo, solo ayudan ligeramente con la legibilidad del código. Sin embargo, es mejor usar newtype lugar del type para una mejor seguridad de tipos. Me gusta esto:

data Alignment = LeftAl | CenterAl | RightAl newtype Delimiter = Delimiter { unDelimiter :: Char } newtype Width = Width { unWidth :: Int } setW :: Width -> Alignment -> Delimiter -> String -> String

Usted se ocupará de la envoltura y desenvolvimiento adicional de newtype . Pero el código será más robusto contra futuras refactorizaciones. Esta guía de estilo sugiere utilizar el type solo para especializar tipos polimórficos.


No consideraría esa mala forma, pero claramente, no hablo por la comunidad de Haskell en general. La función de idioma existe, por lo que puedo decir, para ese propósito particular: hacer que el código sea más fácil de leer.

Uno puede encontrar ejemplos del uso de alias de tipo en varias bibliotecas ''centrales''. Por ejemplo, la clase de Read define este método:

readList :: ReadS [a]

El tipo ReadS es solo un alias de tipo

type ReadS a = String -> [(a, String)]

Otro ejemplo es el tipo de Forest en Data.Tree :

type Forest a = [Tree a]

Como señala Shersh , también puede envolver nuevos tipos en declaraciones newtype . Esto suele ser útil si necesita restringir de alguna manera el tipo original (por ejemplo, con constructores inteligentes ) o si desea agregar funcionalidad a un tipo sin crear instancias huérfanas (un ejemplo típico es definir instancias Arbitrary QuickCheck para tipos que no lo hacen). De lo contrario vengo con tal instancia).


Una cosa importante a tener en cuenta es que la Alignment contra Char no es solo una cuestión de claridad, sino una cuestión de corrección. Su tipo de Alignment expresa el hecho de que solo hay tres alineaciones válidas, a diferencia de la cantidad de habitantes que tiene Char . Al usarlo, evita problemas con valores y operaciones no válidos, y también permite que GHC le informe informativamente sobre las coincidencias de patrones incompletos si las advertencias están activadas.

En cuanto a los sinónimos, las opiniones varían. Personalmente, creo que los sinónimos de type para tipos pequeños como Int pueden aumentar la carga cognitiva, al hacer que rastreen diferentes nombres para lo que es rigurosamente lo mismo. Dicho esto, Leftaroundabout hace un gran punto en que este tipo de sinónimo puede ser útil en las primeras etapas de creación de prototipos de una solución, cuando no necesariamente quiere preocuparse por los detalles de la representación concreta que va a adoptar para su dominio. objetos.

(Vale la pena mencionar que los comentarios aquí sobre el type no se aplican en gran medida al newtype . Sin embargo, los casos de uso son diferentes: mientras que el type simplemente introduce un nombre diferente para la misma cosa, newtype introduce una cosa diferente por fiat. un movimiento sorprendentemente poderoso - vea la respuesta de danidiaz para una discusión más a fondo.)