programa - Tipo de conversión automática en haskell
programa en haskell (2)
Esta respuesta probablemente no sea útil para usted, ya que con toda probabilidad ya ha considerado la alternativa que voy a sugerir y la consideró insuficiente para sus propósitos diabólicos. Aún así, los lectores que tropiezan con esta pregunta pueden encontrar útil saber cómo lograr algo similar a lo que estabas buscando, si no tan ingenioso, sin trucos de tipo de clase.
En tus planes, un BoolLike a
...
newtype BoolLike a = BoolLike ((a -> Bool) -> Bool)
... consiste en una función que produce un resultado Bool
cuando se le da una a -> Bool
continuación de a -> Bool
. Sus ejemplos de uso se reducen a la combinación de los resultados de Bool
con (&&)
y (||)
antes de suministrar la continuación. Esto se puede lograr usando la instancia Applicative
para funciones (en este caso, (->) (a -> Bool)
) y usando (&)
/ flip ($)
para promover valores simples en (a -> Bool) -> Bool
"cálculos suspendidos":
GHCi> ((||) <$> ((&&) <$> ($ 1) <*> ($ 2)) <*> ($ 3)) (`elem` [2])
False
Eso, por supuesto, no es del todo ordenado para escribir. Sin embargo, podemos mejorar mucho las cosas definiendo:
(<&&>) :: Applicative f => f Bool -> f Bool -> f Bool
x <&&> y = (&&) <$> x <*> y
(<||>) :: Applicative f => f Bool -> f Bool -> f Bool
x <||> y = (||) <$> x <*> y
(Para una pequeña biblioteca que los define, eche un vistazo a control-bool ).
Armado con estos, el ruido de línea adicional se vuelve bastante ligero:
GHCi> (($ 1) <&&> ($ 2) <||> ($ 3)) (`elem` [2])
False
Esto también funciona de forma predeterminada para el caso de contains
; todo lo que se necesita es cambiar la continuación suministrada:
GHCi> (($ [1, 2]) <&&> ($ [2, 3]) <||> ($ [3, 4])) (elem 1)
False
Como nota final, vale la pena señalar que el caso contains
puede expresarse directamente en términos de intersect
y union
de Data.List
:
GHCi> [1, 2] `intersect` [2, 3] `union` [3, 4] & elem 1
False
He escrito algunas funciones útiles para hacer una operación lógica. Parece que (a and b or c) `belongs` x
.
Gracias a Num
, IsList
y OverloadedLists
, puedo tenerlo para enteros y listas.
λ> (1 && 2 || 3) `belongs` [2]
False
λ> (1 && 2 || 3) `belongs` [1, 2]
True
λ> (1 && 2 || 3) `belongs` [3]
True
λ> :set -XOverloadedLists
λ> ([1, 2] && [2, 3] || [3, 4]) `contains` 1
False
λ> ([1, 2] && [2, 3] || [3, 4]) `contains` 2
True
Lo hago de esta manera:
newtype BoolLike a = BoolLike ((a -> Bool) -> Bool)
(&&) :: BoolLike a -> BoolLike a -> BoolLike a
BoolLike f && BoolLike g = BoolLike $ /k -> f k P.&& g k
infixr 3 &&
toBoolLike :: a -> BoolLike a
toBoolLike x = BoolLike $ /k -> k x
belongs :: Eq a => BoolLike a -> [a] -> Bool
belongs (BoolLike f) xs = f (/x -> x `elem` xs)
contains :: Eq a => BoolLike [a] -> a -> Bool
contains (BoolLike f) x = f (/xs -> x `elem` xs)
instance Num a => Num (BoolLike a) where
fromInteger = toBoolLike . fromInteger
Lo que voy a hacer es adaptarlo a cualquier tipo, sin convertirlo manualmente a BoolLike a
.
¿Cómo puedo lograrlo?
Primer intento:
class IsBool a where
type BoolItem a
toBool :: a -> BoolItem a
instance IsBool (BoolLike a) where
type BoolItem (BoolLike a) = BoolLike a
toBool = id
instance IsBool a where
type BoolItem a = BoolLike a
toBool = toBoolLike
Ha fallado:
Conflicting family instance declarations:
BoolItem (BoolLike a) -- Defined at BoolLike.hs:54:8
BoolItem a -- Defined at BoolLike.hs:58:8
Podrías probar esto
type family BoolItem a where
BoolItem (BoolLike a) = BoolLike a
BoolItem a = BoolLike a
class IsBool a where
toBool :: a -> BoolItem a
instance IsBool (BoolLike a) where
toBool = id
instance (BoolItem a ~ BoolLike a) => IsBool a where
toBool = toBoolLike
Al mover la familia tipográfica de la clase, puede definirla para todos los tipos. Entonces todo lo que queda es restringir una de las instancias. No olvides que también necesitarás hacer que sea OVERLAPPABLE