imprimir - haskell ejemplos
"Coincidencia de patrones" de constructores de datos de tipo algebraico (5)
Consideremos un tipo de datos con muchos constructores:
data T = Alpha Int | Beta Int | Gamma Int Int | Delta Int
Quiero escribir una función para verificar si se producen dos valores con el mismo constructor:
sameK (Alpha _) (Alpha _) = True
sameK (Beta _) (Beta _) = True
sameK (Gamma _ _) (Gamma _ _) = True
sameK _ _ = False
Mantener sameK
no es muy divertido, no se puede verificar su corrección con facilidad. Por ejemplo, cuando se agregan nuevos constructores a T
, es fácil olvidarse de actualizar sameK
. Omití una línea para dar un ejemplo:
-- it’s easy to forget:
-- sameK (Delta _) (Delta _) = True
La pregunta es ¿cómo evitar un sameK
en sameK
? ¿O cómo asegurarse de que comprueba todos T
constructores T
?
La solución alternativa que encontré es usar tipos de datos separados para cada uno de los constructores, derivar Data.Typeable
y declarar una clase de tipo común, pero no me gusta esta solución, porque es mucho menos legible y, por lo demás, simplemente un tipo algebraico simple. funciona para mi:
{-# LANGUAGE DeriveDataTypeable #-}
import Data.Typeable
class Tlike t where
value :: t -> t
value = id
data Alpha = Alpha Int deriving Typeable
data Beta = Beta Int deriving Typeable
data Gamma = Gamma Int Int deriving Typeable
data Delta = Delta Int deriving Typeable
instance Tlike Alpha
instance Tlike Beta
instance Tlike Gamma
instance Tlike Delta
sameK :: (Tlike t, Typeable t, Tlike t'', Typeable t'') => t -> t'' -> Bool
sameK a b = typeOf a == typeOf b
En algunos casos, la biblioteca "Scrap Your Boilerplate" ayudará.
Mire el módulo Data.Data, la función toConstr
en particular. Junto con {-# LANGUAGE DeriveDataTypeable #-}
que le proporcionará una solución de 1 línea que funciona para cualquier tipo que sea una instancia de Data.Data
. ¡No necesita averiguar todo SYB!
Si, por alguna razón (¿con Abrazos?), Esa no es una opción, entonces aquí hay un truco muy feo y muy lento. Funciona solo si su tipo de datos es Show
able (por ejemplo, mediante deriving (Show)
, lo que significa que no hay tipos de funciones dentro, por ejemplo).
constrT :: T -> String
constrT = head . words . show
sameK x y = constrT x == constrT y
constrT
obtiene la representación de cadena del constructor más externo de un valor T
mostrándolo, cortándolo en palabras y obteniendo el primero. Doy una firma de tipo explícita para que no tengas la tentación de usarla en otros tipos (y para evadir la restricción de monomorfismo).
Algunas desventajas notables:
- Esto se rompe horriblemente cuando tu tipo tiene constructores de infijo (como los
data T2 = Eta Int | T2 :^: T2
) - Si algunos de sus constructores tienen un prefijo compartido, esto va a ser más lento, ya que se debe comparar una gran parte de las cadenas.
- No funciona en tipos con un
show
personalizado, como muchos tipos de biblioteca.
Dicho esto, es Haskell 98 ... ¡pero eso es lo único bueno que puedo decir al respecto!
Otra posible forma:
sameK x y = f x == f y
where f (Alpha _) = 0
f (Beta _) = 1
f (Gamma _ _) = 2
-- runtime error when Delta value encountered
Un error de tiempo de ejecución no es ideal, pero es mejor que dar una respuesta incorrecta en silencio.
Tendrá que utilizar una biblioteca de genéricos como Chatar su boilerplate o uniplatar para hacer esto en general.
Si no quiere ser tan torpe, puede usar la solución de Dave Hinton, junto con el atajo de registro vacío:
...
where f (Alpha {}) = 0
f (Beta {}) = 1
f (Gamma {}) = 2
Por lo tanto, no es necesario saber cuántos argumentos tiene cada constructor. Pero obviamente todavía deja algo que desear.
Definitivamente puede usar los genéricos para eliminar la repetición. Su código es un ejemplo de libro de texto por el que yo (y muchos otros nunca usan el _
comodín en el nivel superior). Si bien es tedioso escribir todos los casos, es menos tedioso que lidiar con los errores.
En este feliz ejemplo, no solo utilizaría la solución de Dave Hinton, sino que lanzaría un pragma EN LÍNEA en la función auxiliar f
.