tipos suma imprimir funciones funcion español ejemplos basicas haskell function-composition

suma - imprimir en haskell



Haskell: falla la función de composición con dos argumentos flotantes (3)

Estoy intentando componer una función de tipo (Floating a) => a -> a -> a con una función de tipo (Floating a) => a -> a para obtener una función de tipo (Floating a) => a -> a -> a . Tengo el siguiente código:

test1 :: (Floating a) => a -> a -> a test1 x y = x test2 :: (Floating a) => a -> a test2 x = x testBoth :: (Floating a) => a -> a -> a testBoth = test2 . test1 --testBoth x y = test2 (test1 x y)

Sin embargo, cuando lo compilo en GHCI, aparece el siguiente error:

/path/test.hs:8:11: Could not deduce (Floating (a -> a)) from the context (Floating a) arising from a use of `test2'' at /path/test.hs:8:11-15 Possible fix: add (Floating (a -> a)) to the context of the type signature for `testBoth'' or add an instance declaration for (Floating (a -> a)) In the first argument of `(.)'', namely `test2'' In the expression: test2 . test1 In the definition of `testBoth'': testBoth = test2 . test1 Failed, modules loaded: none.

Tenga en cuenta que compila la versión comentada de testBoth . Lo extraño es que si testBoth restricciones (Floating a) de todas las firmas de tipo o si cambio test1 para simplemente tomar x lugar de x e y , testBoth compila.

He buscado StackOverflow, Haskell wikis, Google, etc. y no he encontrado nada sobre una restricción en la composición de la función relevante para esta situación particular. ¿Alguien sabe por qué ocurre esto?


Su problema no tiene nada que ver con Floating , aunque la clase de tipo hace que su error sea más difícil de entender. Tome el siguiente código como un ejemplo:

test1 :: Int -> Char -> Int test1 = undefined test2 :: Int -> Int test2 x = undefined testBoth = test2 . test1

¿Cuál es el tipo de TestBoth? Bueno, tomamos el tipo de (.) :: (b -> c) -> (a -> b) -> a -> c y giramos la manivela para obtener:

  1. b ~ Int (el argumento de test2 unificado con el primer argumento de (.) )
  2. c ~ Int (el resultado de test2 unificado con el resultado del primer argumento de (.) )
  3. a ~ Int ( test1 argumento 1 unificado con argumento 2 de (.) )
  4. b ~ Char -> Int (resultado de test1 unificado con argumento 2 de (.) )

¡pero espera! esa variable de tipo, ''b'' (# 4, Char -> Int ), tiene que unificarse con el tipo de argumento de test2 (# 1, Int ). ¡Oh no!

¿Cómo deberías hacer esto? Una solución correcta es:

testBoth x = test2 . test1 x

Hay otras formas, pero considero que esta es la más legible.

Editar: Entonces, ¿cuál fue el error tratando de decirte? Estaba diciendo que unificar Floating a => a -> a con Floating b => b requiere una instance Floating (a -> a) ... mientras que eso es cierto, realmente no querías que GHC intentara tratar una función como un número de punto flotante.


/x y -> test2 (test1 x y) == /x y -> test2 ((test1 x) y) == /x y -> (test2 . (test1 x)) y == /x -> test2 . (test1 x) == /x -> (test2 .) (test1 x) == /x -> ((test2 .) . test1) x == (test2 .) . test1

Estas dos cosas no son como la una a la otra.

test2 . test1 == /x -> (test2 . test1) x == /x -> test2 (test1 x) == /x y -> (test2 (test1 x)) y == /x y -> test2 (test1 x) y


Su problema no tiene nada que ver con Floating , sino con el hecho de que desea componer una función con dos argumentos y una función con un argumento de una manera que no compruebe. Te daré un ejemplo en términos de una función compuesta reverse . foldr (:) [] reverse . foldr (:) [] .

reverse . foldr (:) [] reverse . foldr (:) [] tiene el tipo [a] -> [a] y funciona como se esperaba: devuelve una lista invertida ( foldr (:) [] es esencialmente id para las listas).

Sin embargo, al reverse . foldr (:) reverse . foldr (:) no escribe check. ¿Por qué?

Cuando los tipos coinciden con la composición de la función

Repasemos algunos tipos:

reverse :: [a] -> [a] foldr (:) :: [a] -> [a] -> [a] foldr (:) [] :: [a] -> [a] (.) :: (b -> c) -> (a -> b) -> a -> c

reverse . foldr (:) [] reverse . foldr (:) [] typechecks, porque (.) instancia a:

(.) :: ([a] -> [a]) -> ([a] -> [a]) -> [a] -> [a]

En otras palabras, en la anotación de tipo para (.) :

  • a convierte en [a]
  • b convierte en [a]
  • c convierte en [a]

Entonces al reverse . foldr (:) [] reverse . foldr (:) [] tiene el tipo [a] -> [a] .

Cuando los tipos no coinciden con la composición de funciones

reverse . foldr (:) reverse . foldr (:) no escribe verificación, porque:

foldr (:) :: [a] -> [a] -> [a]

Al ser el operador correcto de (.) , Crearía su tipo de a -> b a [a] -> ([a] -> [a]) . Es decir, en:

(b -> c) -> (a -> b) -> a -> c

  • La variable de tipo a se reemplazará por [a]
  • La variable de tipo b se reemplazará por [a] -> [a] .

Si el tipo de foldr (:) fuera a -> b , el tipo de (. foldr (:)) sería:

(b -> c) -> a -> c`

( foldr (:) se aplica como un derecho operante a (.) ).

Pero debido a que tipo de foldr (:) es [a] -> ([a] -> [a]) , el tipo de (. foldr (:)) es:

(([a] -> [a]) -> c) -> [a] -> c

reverse . foldr (:) reverse . foldr (:) no escribe check, porque reverse tiene el tipo [a] -> [a] , not ([a] -> [a]) -> c !

Operador de búho

Cuando las personas aprenden por primera vez la composición de funciones en Haskell, aprenden que cuando tienes el último argumento de función en el lado derecho del cuerpo de la función, puedes soltarlo tanto de los argumentos como del cuerpo, reemplazando o entre paréntesis (o signos de dólar ) con puntos. En otras palabras, las siguientes 4 definiciones de función son equivalentes :

f a x xs = g ( h a ( i x xs)) f a x xs = g $ h a $ i x xs f a x xs = g . h a . i x $ xs f a x = g . h a . i x

Entonces la gente tiene una intuición que dice "simplemente elimino la variable local más a la derecha del cuerpo y de los argumentos", pero esta intuición es defectuosa, porque una vez que quitaste xs ,

f a x = g . h a . i x f a = g . h a . i

no son equivalentes ! Debe comprender cuándo el tipo de composición de funciones comprueba y cuándo no. Si los 2 anteriores son equivalentes, significaría que los siguientes 2 también son equivalentes:

f a x xs = g . h a . i x $ xs f a x xs = g . h a . i $ x xs

lo cual no tiene sentido, porque x no es una función con xs como parámetro. x es un parámetro para la función i , y xs es un parámetro para la función (ix) .

Hay un truco para hacer una función con 2 parámetros sin puntos. Y eso es usar un operador "búho":

f a x xs = g . h a . i x xs f a = g . h a .: i where (.:) = (.).(.)

Las dos definiciones de funciones anteriores son equivalentes. Lea más sobre el operador "búho" .

Referencias

La programación de Haskell se vuelve mucho más sencilla y directa, una vez que comprende las funciones, los tipos, la aplicación parcial y el currying, la composición de funciones y el operador de dólares. Para identificar estos conceptos, lea las siguientes respuestas de :

  • En tipos y composición de funciones
  • En funciones de orden superior, currying y composición de funciones
  • En sistema de tipo Haskell
  • En estilo sin puntos
  • En const
  • En const , flip y tipos
  • En curry y uncurry

Lea también:

  • Haskell: diferencia entre. (punto) y $ (signo de dólar)
  • La función de la función Haskell (.) Y la aplicación de función ($) modismos: uso correcto