El Operador de punto en Haskell:necesita más explicación
-
08-07-2019 - |
Pregunta
Estoy tratando de entender lo que el operador está haciendo en este código Haskell:
sumEuler = sum . (map euler) . mkList
El código fuente completo está por debajo.
Mi comprensión
El operador de punto es tomar las dos funciones sum
y el resultado de map euler
y el resultado de mkList
como la entrada.
Pero, sum
no es una función es el argumento de la función, ¿verdad?Entonces, ¿qué está pasando aquí?
También, ¿cuál es (map euler)
haciendo?
Código
mkList :: Int -> [Int]
mkList n = [1..n-1]
euler :: Int -> Int
euler n = length (filter (relprime n) (mkList n))
sumEuler :: Int -> Int
sumEuler = sum . (map euler) . mkList
Solución
En pocas palabras, .
es composición de funciones, al igual que en matemáticas:
f (g x) = (f . g) x
En su caso, está creando una nueva función, sumEuler
que también podría definirse así:
sumEuler x = sum (map euler (mkList x))
El estilo en su ejemplo se llama " point-free " estilo: se omiten los argumentos de la función. Esto hace que el código sea más claro en muchos casos. (Puede ser difícil asimilarlo la primera vez que lo ve, pero se acostumbrará después de un tiempo. Es un idioma común de Haskell).
Si todavía está confundido, puede ser útil relacionar f
con algo como una tubería UNIX. Si la salida de g
se convierte en la entrada de h
, cuya salida se convierte en la entrada de f < x | g | h
, escribiría eso en la línea de comando como |
. En Haskell, h . g . f $ x
funciona como UNIX map (\x -> x * 2 + 10) [1..10]
, pero & Quot; al revés & Quot; - (+10) . (*2) <$> [1..10]
. Considero que esta notación es bastante útil cuando, por ejemplo, procesa una lista. En lugar de una construcción difícil de manejar como (+10) . (*2) $ 10
, podría simplemente escribir <=>. (Y, si solo desea aplicar esa función a un solo valor; es <=>. ¡Consistente!)
El wiki de Haskell tiene un buen artículo con más detalles: http://www.haskell.org/ haskellwiki / Pointfree
Otros consejos
El. El operador compone funciones. Por ejemplo,
a . b
Donde a y b son funciones es una nueva función que ejecuta b en sus argumentos, luego a en esos resultados. Tu código
sumEuler = sum . (map euler) . mkList
es exactamente lo mismo que:
sumEuler myArgument = sum (map euler (mkList myArgument))
pero espero que sea más fácil de leer. La razón por la que hay parens alrededor de map euler es porque aclara que se componen 3 funciones: sum , map euler y mkList - map euler es una función única.
sum
es una función en el Preludio de Haskell, no un argumento para sumEuler
. Tiene el tipo
Num a => [a] -> a
El operador de composición de funciones .
tiene tipo
(b -> c) -> (a -> b) -> a -> c
Entonces tenemos
sum :: Num a => [a] -> a
map :: (a -> b) -> [a] -> [b]
euler :: Int -> Int
mkList :: Int -> [Int]
(map euler) :: [Int] -> [Int]
(map euler) . mkList :: Int -> [Int]
sum . (map euler) . mkList :: Int -> Int
Tenga en cuenta que Int
es una instancia de Num
.
El. El operador se utiliza para la composición de funciones. Al igual que las matemáticas, si tiene que usar las funciones f (x) y g (x) f. g se convierte en f (g (x)).
map es una función incorporada que aplica una función a una lista. Al poner la función entre paréntesis, la función se trata como un argumento. Un término para esto es currying . Deberías buscar eso.
Lo que hace es que toma una función con digamos dos argumentos, aplica el argumento euler. (mapa euler) ¿verdad? y el resultado es una nueva función, que toma solo un argumento.
suma. (mapa euler). mkList es básicamente una forma elegante de juntar todo eso. Debo decir que mi Haskell está un poco oxidado, pero ¿tal vez puedas armar esa última función tú mismo?
El operador punto se aplica la función a la izquierda (sum
) a la salida de la función a la derecha.En su caso, va a encadenar varias funciones - eres el resultado de pasar de mkList
a (map euler)
, y , a continuación, pasar el resultado de que a sum
.
Este sitio tiene una buena introducción a varios de los conceptos.
Operador de puntos en Haskell
Estoy tratando de entender qué está haciendo el operador de puntos en este código Haskell:
sumEuler = sum . (map euler) . mkList
Respuesta corta
Código equivalente sin puntos, eso es solo
sumEuler = \x -> sum ((map euler) (mkList x))
o sin la lambda
sumEuler x = sum ((map euler) (mkList x))
porque el punto (.) indica la composición de la función.
Respuesta más larga
Primero, simplifiquemos la aplicación parcial de euler
a map
:
map_euler = map euler
sumEuler = sum . map_euler . mkList
Ahora solo tenemos los puntos. ¿Qué se indica con estos puntos?
De la fuente :
(.) :: (b -> c) -> (a -> b) -> a -> c (.) f g = \x -> f (g x)
Así (.)
es el operador de composición .
Componer
En matemáticas, podríamos escribir la composición de funciones, f (x) y g (x), es decir, f (g (x)), como
(f & # 8728; g) (x)
que se puede leer " f compuesto con g " ;.
Entonces en Haskell, f & # 8728; g, o f compuesto con g, se puede escribir:
f . g
La composición es asociativa, lo que significa que f (g (h (x))), escrita con el operador de composición, puede omitir los paréntesis sin ninguna ambigüedad.
Es decir, ya que (f & # 8728; g) & # 8728; h es equivalente a f & # 8728; (g & # 8728; h), simplemente podemos escribir f & # 8728; g & # 8728; h.
Dando la vuelta
Volviendo a nuestra simplificación anterior, esto:
sumEuler = sum . map_euler . mkList
solo significa que sumEuler
es una composición no aplicada de esas funciones:
sumEuler = \x -> sum (map_euler (mkList x))