Soy relativamente nuevo en Haskell, así que perdóname si esto es realmente obvio.
Básicamente tengo dos Bool
y en base a ellos quiero elegir la implementación de 3 funciones diferentes. En el caso de que ambos bools sean iguales (por ejemplo, ambos True o ambos False), las funciones no deberían hacer nada. Luego hay una implementación diferente si uno u otro Bool es True.
Estas funciones implican restricciones, por lo que, por ejemplo, la primera función tiene una restricción Ord o Bounded en los parámetros. La segunda función tiene una restricción Num sobre los parámetros.
El problema que tengo es que no tengo ni idea de cómo hacer que el verificador de tipos esté bien con esta construcción. Vea a continuación un ejemplo mínimo que se queja cuando coincido con el patrón en el resultado:
f :: (Ord a, Bounded a) => a -> a -> a
f a b = if a > b then maxBound else minBound
g :: (Ord a, Bounded a) => a -> a -> a
g a b = if a > b then minBound else maxBound
a = True
b = False
test
| a == b = (const, const, const)
| a = (f, (-), (+))
| b = (g, (+), (-))
(resF, _, _) = test
(_, resG, _) = test -- error Could not deduce (Ord b0) arising from a use of ‘test’
-- from the context: Num b
-- Same error occurs for the last value in the tuple.
No estoy seguro de cómo la función con la mayoría de las restricciones resF
está completamente bien al ser asignada a una variable, pero se resG
queja ...
¡Se agradece cualquier ayuda!
El problema aquí es que tienes un tipo ambiguo . En primer lugar, verifiquemos la firma de tipo de test
según lo inferido por GHC. Un buen truco que descubrí hace un tiempo es agregarlo test :: _
a su programa y dejar que GHC nos dé su tipo inferido en un mensaje de error:
so.hs:13:9: error:
• Found type wildcard ‘_’
standing for ‘(b0 -> b0 -> b0, Integer -> Integer -> Integer,
Integer -> Integer -> Integer)’
Where: ‘b0’ is an ambiguous type variable
To use the inferred type, enable PartialTypeSignatures
• In the type signature: test :: _
|
13 | test :: _
| ^
Entonces, el tipo de test
según lo inferido por GHC es (b0 -> b0 -> b0, Integer -> Integer -> Integer, Integer -> Integer -> Integer)
(aunque debería haber una (Ord b0, Bounded b0)
restricción adicional que GHC omite por alguna razón). Ahora, veamos resF
y resG
:
(resF, _, _) = test
(_, resG, _) = test
En la definición de resF
, el b0
parámetro de tipo termina usándose también fuera de esa expresión (en el tipo de resF :: b0 -> b0 -> b0
), por lo que no es ambiguo. Sin embargo, en la definición de resG
, b0
solo se usa dentro de esa expresión, ¡así que podría ser cualquier cosa ! Dado que GHC no tiene absolutamente ninguna forma de determinar qué b0
hay en esa declaración, se marca como ambigua, produciendo este error.
(Si eso no fue lo suficientemente claro, la idea es que si tiene una expresión con una variable de tipo ambiguo, y se refiere a esta variable en el lado izquierdo del =
, entonces se desambigua, ya que la variable se está utilizando fuera del expresión. Sé que esta no es una explicación muy buena; no soy muy bueno con esta área de Haskell, así que si alguien más tiene una explicación mejor, ¡comenten!)
Entonces, ¿cómo se puede resolver este problema? Una forma es simplemente combinar resF
y resG
, por b0
lo tanto , termina usándose en el exterior test
:
(resF, resG, _) = test
Otra forma es agregar una firma de tipo que restrinja b0
:
(_, resG, _) = test :: (() -> () -> (), Integer -> Integer -> Integer, Integer -> Integer -> Integer)
Esta es la forma más común de sortear errores de tipo ambiguo, ya que funcionará en todas las circunstancias. En este caso, resulta ser mucho más largo, pero debería poder usarlo en más situaciones que la técnica anterior, que realmente solo funciona aquí.
Sin embargo, todavía hay algunos puntos sutiles aquí. En primer lugar, ¿por qué GHC informa que el segundo y tercer campo utilizan Integer
, en lugar de permitir cualquier tipo? Esto se debe a la restricción de monomorfismo , que en determinadas situaciones especializa las variables de tipo automáticamente. Puede evitar esto agregando una firma de tipo:
test :: (Ord a, Bounded a, Num b, Num c) => (a -> a -> a, b -> b -> b, c -> c -> c)
¡Es por eso que se considera una buena práctica agregar firmas de tipo a todas las funciones!
Por supuesto, esto tiene la desventaja de hacer que el segundo y tercer campo también utilicen variables de tipo; por lo tanto, también se vuelven propensos a tipos ambiguos. Puede evitar esto vinculando los tres campos para permitir que estas variables de tipo se 'propaguen' en un sentido fuera de esa declaración:
(resF, resG, resH) = test
(Tenga en cuenta que 'propagar' es mi propio término, ¡no un término reconocido por Haskell!)
EDITAR: Entonces, resulta que esta estrategia no funciona. Se dan más detalles al final de esta respuesta, ya que es un poco detallada.
O puede agregar una firma de tipo nuevamente para restringir b
y c
:
(resF, _, _) = test :: (Ord a, Bounded a) => (a -> a -> a, Int -> Int -> Int, Int -> Int -> Int)
El otro punto que quería hacer es con la definición de test
sí mismo. En Haskell, es muy poco común usar variables globales como lo hace aquí; Por lo general, los agregaría como parámetros y test
luego los pasaría desde afuera de esta manera:
test :: (Ord a, Bounded a, Num b, Num c)
=> Bool
-> Bool
-> (a -> a -> a, b -> b -> b, c -> c -> c)
test a b =
| a == b = (const, const, const)
| a = (f, (-), (+))
| b = (g, (+), (-))
(resF, resG, resH) = test True False
Hacerlo de esta manera permite una mayor reutilización del código, ya test
que ahora se puede usar varias veces con diferentes condiciones booleanas.
EDITAR:
No estoy seguro de que lo anterior sea incorrecto como tal, pero hay un factor importante que me perdí por completo. Si tiene algo de tipo (Constr1 a, Constr2 b) => (a, b)
, toda la tupla depende de ambos Constr1 a
y Constr2 b
! Por lo tanto, no puede eliminar fácilmente una variable de tipo para aislar la otra. (Más detalles en esta excelente respuesta).
Sin embargo, ¡hay una solución! En test
, cada campo es independiente entre sí. Por lo tanto, teóricamente debería ser posible cambiar el tipo al siguiente signo:
test :: Bool -> Bool
-> ( forall a. (Ord a, Bouded a) => a -> a -> a
, forall b. Num b => b -> b -> b
, forall c. Num c => c -> c -> c
)
test a b =
| a == b = (const, const, const)
| a = (f, (-), (+))
| b = (g, (+), (-))
Ahora, en cierto sentido, todas las restricciones se han "introducido" en la tupla, por lo que ahora puede aislar un campo.
Por supuesto, nada es tan simple como eso, y si intenta ejecutar lo anterior, se encontrará con un error sobre 'polimorfismo impredicativo'. Las soluciones envuelven los campos en tipos de datos auxiliares:
newtype Wrapper1 = Wrapper1 (forall a. (Ord a, Bounded a) => a -> a -> a)
newtype Wrapper2 = Wrapper2 (forall b. Num b => b -> b -> b)
test :: (Wrapper1, Wrapper2, Wrapper2)
test
| a == b = (Wrapper1 const, Wrapper2 const, Wrapper2 const)
| a = (Wrapper1 f , Wrapper2 (-) , Wrapper2 (+))
| b = (Wrapper1 g , Wrapper2 (+) , Wrapper2 (-))
(Wrapper1 resF, Wrapper2 resG, Wrapper2 resH) = test
(También deberá agregar {-# LANGUAGE RankNTypes #-}
al principio del archivo para que esto se compile).
Y esto, ¡finalmente! - escribe correctamente.
Como ventaja adicional, resulta que este método incluso elimina los errores de tipo ambiguos. El siguiente tipo de código también se comprueba correctamente:
test
| a == b = (Wrapper1 const, Wrapper2 const, Wrapper2 const)
| a = (Wrapper1 f , Wrapper2 (-) , Wrapper2 (+))
| b = (Wrapper1 g , Wrapper2 (+) , Wrapper2 (-))
(Wrapper1 resF, _, _) = test
Como mencioné anteriormente, no entiendo muy bien los tipos ambiguos, pero la razón de esto es probablemente porque toda la información sobre otras variables de tipo ha sido 'extraída' de los otros campos de la tupla, por lo que GHC sabe que puede ignorarla de manera segura. ellos ahora.
TL; Versión DR: porque GHC estará predeterminado Num b2
y Num b3
no (Ord b1, Bounded b1)
.
Este es un problema con el tipo predeterminado . Se test
infiere que el tipo de (Ord b1, Bounded b1, Num b2, Num b3) => (b1 -> b1 -> b1, b2 -> b2 -> b2, b3 -> b3 -> b3)
. Este tipo significa que si proporciona una instancia Ord
y Bounded
para algún tipo b1
, y una Num
instancia para algunos tipos b2
y b3
, obtendrá una tupla de funciones. Cuando separas la tupla y solo mantienes una pieza, las otras restricciones no desaparecen, por lo que básicamente tienes resF :: (Ord b1, Bounded b1, Num b2, Num b3) => b1 -> b1 -> b1
y resG :: (Ord b1, Bounded b1, Num b2, Num b3) => b2 -> b2 -> b2
.
Ahora, en resF
, b2
y b3
son tipos ambiguos, ya que se usan a la izquierda =>
pero no a la derecha. De acuerdo con las reglas de tipo predeterminado, estas serán predeterminadas Integer
, por lo que terminará resF
teniendo realmente un tipo de (Ord b1, Bounded b1) => b1 -> b1 -> b1
y las cosas funcionarán bien.
resG
es diferente. En él, b1
y b3
están los tipos ambiguos. b3
se establece de forma predeterminada Integer
como lo hizo para resF
. Sin embargo, GHC no tiene un valor predeterminado b1
que satisfaga la restricción (Ord b1, Bounded b1)
, por lo que le da un error en ese sentido (que definitivamente podría ser más claro).
Para solucionar el problema, debe indicarle a GHC qué tipo b1
sería. Dado que la parte de la tupla que estás guardando no se usa b1
, puedes elegir lo que quieras y no importará. Aquí hay una forma de hacerlo (en este ejemplo, elegí Int
):
(_, resG, _) = test :: (Num b2, Num b3) => (Int -> Int -> Int, b2 -> b2 -> b2, b3 -> b3 -> b3)
Una forma en que el error dice que no puede deducir de qué tipo a
porque es ambiguo el tipo de la clase de tipos Num
en las funciones (+)
y (-)
, para resolver este problema, debe especificar cuál, ni la instancia de Ord, Bounded
ni la instancia de Num
:
f :: (Ord a, Bounded a) => a -> a -> a
f a b = if a > b then maxBound else minBound
g :: (Ord a, Bounded a) => a -> a -> a
g a b = if a > b then minBound else maxBound
b1 = True
b2 = False
test a b | a == b = (const, const, const)
| a = (f , (-), (+))
| b = (g , (+), (-))
(resF, resG , plusH) = test b1 b2
three = 3 :: Int
two = 2 :: Int
one = 1 :: Int
main = do
putStrLn $ "Max: " ++ show (resF three two) putStrLn $ "Min: " ++ show (resF one two)
putStrLn $ "2 - 1: " ++ show (resG two one) putStrLn $ "1 + 2: " ++ show (plusH one two)
λ> Max: 9223372036854775807
λ> Min: -9223372036854775808
λ> 2 - 1: 1
λ> 1 + 2: 3
Hasta ahora, todo bien, lo arreglé dando los tipos a las funciones resF, resG , plusH
con los números escritos three, two, one
:: Int, incluso tú puedes hacer:
(resF, _ , _) = test b1 b2
Y también funcionará bien.
Ahora el problema es cuando lo haces:
(_, resG', _) = test b1 b2
----- ^ ^ ----- (¡¡¡Problema !!!) Con el main, el resG'
funcionará bien, porque está escrito por three, two, one
valores Int, ¡pero los _
valores no están escritos! tres = 3 :: Int dos = 2 :: Int uno = 1 :: Int main = do putStrLn $ "2 - 1:" ++ mostrar (resG 'dos uno)
¿Cuál es el tipo de _
allí? y el otro _
, no están especificados, y ese es el principal problema que tienes
Entonces, una solución alternativa es la siguiente:
test a b | a == b = (const, const, const)
| a = (f , (-), (+))
| b = (g , (+), (-))
type NoneTypedFunction = () -> () -> () -- this type is in case you don't actually want / need the function from the first ternary value
(resF, resG , resH) = test b1 b2
(_, resG', _) = test b1 b2 :: (NoneTypedFunction, Int -> Int -> Int, Int -> Int -> Int)
(_, _, _) = test b1 b1 :: (NoneTypedFunction, Int -> Int -> Int, Int -> Int -> Int)
three = 3 :: Int
two = 2 :: Int
one = 1 :: Int
main = do
putStrLn $ "Max: " ++ show (resF three two)
putStrLn $ "Min: " ++ show (resF one two) putStrLn $ "2 - 1: " ++ show (resG two one)
putStrLn $ "2 - 1: " ++ show (resH two one) putStrLn $ "2 - 1: " ++ show (resG' two one)
Ahora le das el tipo correcto a cada uno
otra cosa importante
:t resF
resF :: Int -> Int -> Int
:t resG
resG :: Int -> Int -> Int
resG :: Int -> Int -> Int
:t resH
Los tipos en las funciones, después de la llamada test
y dando una hora específica por primera vez, son fijos y ya no polimórficos.
Si quieres ser ilimitado:
first (a, _, _) = a
second (_, b, _ = b
third (a, b, c) = c
y luego puedes hacer:
(first (test b1 b2)) one two
$> -9223372036854775808 (first (test b1 b2)) True False $> True
(first (test b1 b2)) 'c' 'b'
$> '\1114111'
La estrella de HGTV, Christina Hall, revela que le diagnosticaron envenenamiento por mercurio y plomo, probablemente debido a su trabajo como manipuladora de casas.
Recientemente salió a la luz un informe policial que acusa a la estrella de 'Love Is Blind', Brennon, de violencia doméstica. Ahora, Brennon ha respondido a los reclamos.
Conozca cómo Wynonna Judd se dio cuenta de que ahora es la matriarca de la familia mientras organizaba la primera celebración de Acción de Gracias desde que murió su madre, Naomi Judd.
Descubra por qué un destacado experto en lenguaje corporal cree que es fácil trazar "tales paralelismos" entre la princesa Kate Middleton y la princesa Diana.
Los inodoros arrojan columnas de aerosol invisibles con cada descarga. ¿Como sabemos? La prueba fue capturada por láseres de alta potencia.
Air travel is far more than getting from point A to point B safely. How much do you know about the million little details that go into flying on airplanes?
The world is a huge place, yet some GeoGuessr players know locations in mere seconds. Are you one of GeoGuessr's gifted elite? Take our quiz to find out!
¿Sigue siendo efectivo ese lote de repelente de insectos que te quedó del verano pasado? Si es así, ¿por cuánto tiempo?
Durante mucho tiempo, el mundo se ha reído de la voz de Peter Dinklage actuando en Destiny, un videojuego sobre la lenta e inevitable progresión de la muerte. Pronto, esa actuación de voz va a cambiar.
Tapas elásticas de silicona de Tomorrow's Kitchen, paquete de 12 | $14 | Amazonas | Código promocional 20OFFKINJALids son básicamente los calcetines de la cocina; siempre perdiéndose, dejando contenedores huérfanos que nunca podrán volver a cerrarse. Pero, ¿y si sus tapas pudieran estirarse y adaptarse a todos los recipientes, ollas, sartenes e incluso frutas en rodajas grandes que sobran? Nunca más tendrás que preocuparte por perder esa tapa tan específica.
Hemos pirateado algunas ciudades industriales en esta columna, como Los Ángeles y Las Vegas. Ahora es el momento de una ciudad militar-industrial-compleja.
Un minorista está enlatando su sección de tallas grandes. Pero no están tomando la categoría solo en línea o descontinuándola por completo.
El equipo está a la espera de las medallas que ganó en los Juegos Olímpicos de Invierno de 2022 en Beijing, ya que se está resolviendo un caso de dopaje que involucra a la patinadora artística rusa Kamila Valieva.
Miles de compradores de Amazon recomiendan la funda de almohada de seda Mulberry, y está a la venta en este momento. La funda de almohada de seda viene en varios colores y ayuda a mantener el cabello suave y la piel clara. Compre las fundas de almohada de seda mientras tienen hasta un 46 por ciento de descuento en Amazon
El jueves se presentó una denuncia de delito menor amenazante agravado contra Joe Mixon.
El Departamento de Policía de Lafayette comenzó a investigar a un profesor de la Universidad de Purdue en diciembre después de recibir varias denuncias de un "hombre sospechoso que se acercaba a una mujer".
Al igual que el mundo que nos rodea, el lenguaje siempre está cambiando. Mientras que en eras anteriores los cambios en el idioma ocurrían durante años o incluso décadas, ahora pueden ocurrir en cuestión de días o incluso horas.
Estoy de vuelta por primera vez en seis años. No puedo decirte cuánto tiempo he estado esperando esto.
“And a river went out of Eden to water the garden, and from thence it was parted and became into four heads” Genesis 2:10. ? The heart is located in the middle of the thoracic cavity, pointing eastward.
Creo, un poco tarde en la vida, en dar oportunidades a la gente. Generosamente.