opciones - if en haskell ejemplos
Qué hace(f.). g significa en Haskell? (2)
He visto muchas funciones definidas de acuerdo con el patrón (f .) . g
(f .) . g
. Por ejemplo:
countWhere = (length .) . filter
duplicate = (concat .) . replicate
concatMap = (concat .) . map
¿Qué significa esto?
El operador punto (es decir (.)
) Es el operador de composición de funciones . Se define de la siguiente manera:
infixr 9 .
(.) :: (b -> c) -> (a -> b) -> a -> c
f . g = /x -> f (g x)
Como puede ver, toma una función de tipo b -> c
y otra función de tipo a -> b
y devuelve una función de tipo a -> c
(es decir, que aplica el resultado de la segunda función a la primera función).
El operador de composición de funciones es muy útil. Le permite canalizar la salida de una función a la entrada de otra función. Por ejemplo, puede escribir un programa tac en Haskell de la siguiente manera:
main = interact (/x -> unlines (reverse (lines x)))
No es muy legible Sin embargo, al usar la composición de la función, puede escribirla de la siguiente manera:
main = interact (unlines . reverse . lines)
Como puede ver, la composición de la función es muy útil, pero no puede usarla en todas partes. Por ejemplo, no puede canalizar la salida del filter
en length
utilizando la composición de funciones:
countWhere = length . filter -- this is not allowed
La razón por la que esto no está permitido es porque el filter
es de tipo (a -> Bool) -> [a] -> [a]
. Si lo comparamos con a -> b
encontramos que a
es de tipo (a -> Bool)
y b
es de tipo [a] -> [a]
. Esto da como resultado un desajuste de tipo porque Haskell espera que la length
sea del tipo b -> c
(es decir, ([a] -> [a]) -> c
). Sin embargo, en realidad es del tipo [a] -> Int
.
La solución es bastante simple:
countWhere f = length . filter f
Sin embargo, a algunas personas no les gusta esa f
colgando extra. Prefieren escribir countWhere
en estilo pointfree siguiente manera:
countWhere = (length .) . filter
¿Cómo lo consiguen? Considerar:
countWhere f xs = length (filter f xs)
-- But `f x y` is `(f x) y`. Hence:
countWhere f xs = length ((filter f) xs)
-- But `/x -> f (g x)` is `f . g`. Hence:
countWhere f = length . (filter f)
-- But `f . g` is `(f .) g`. Hence:
countWhere f = (length .) (filter f)
-- But `/x -> f (g x)` is `f . g`. Hence:
countWhere = (length .) . filter
Como puedes ver (f .) . g
(f .) . g
es simplemente /xy -> f (gxy)
. Este concepto puede ser iterado:
f . g --> /x -> f (g x)
(f .) . g --> /x y -> f (g x y)
((f .) .) . g --> /x y z -> f (g x y z)
(((f .) .) .) . g --> /w x y z -> f (g w x y z)
No es bonito, pero hace bien el trabajo. Dadas dos funciones, también puede escribir sus propios operadores de composición de funciones:
f .: g = (f .) . g
f .:: g = ((f .) .) . g
f .::: g = (((f .) .) .) . g
Usando el operador (.:)
podrías escribir countWhere
siguiente manera:
countWhere = length .: filter
Sin embargo, es interesante que puedas escribir (.:)
en estilo libre de puntos:
f .: g = (f .) . g
-- But `f . g` is `(.) f g`. Hence:
f .: g = (.) (f .) g
-- But `/x -> f x` is `f`. Hence:
(f .:) = (.) (f .)
-- But `(f .)` is `((.) f)`. Hence:
(f .:) = (.) ((.) f)
-- But `/x -> f (g x)` is `f . g`. Hence:
(.:) = (.) . (.)
Del mismo modo, obtenemos:
(.::) = (.) . (.) . (.)
(.:::) = (.) . (.) . (.) . (.)
Como puede ver (.:)
, (.::)
y (.:::)
son solo potencias de (.)
(Es decir, son funciones iteradas de (.)
). Para números en Matemáticas:
x ^ 0 = 1
x ^ n = x * x ^ (n - 1)
Del mismo modo para las funciones en Matemáticas:
f .^ 0 = id
f .^ n = f . (f .^ (n - 1))
Si f
es (.)
Entonces:
(.) .^ 1 = (.)
(.) .^ 2 = (.:)
(.) .^ 3 = (.::)
(.) .^ 4 = (.:::)
Eso nos acerca al final de este artículo. Para un desafío final, escribamos la siguiente función en estilo pointfree:
mf a b c = filter a (map b c)
mf a b c = filter a ((map b) c)
mf a b = filter a . (map b)
mf a b = (filter a .) (map b)
mf a = (filter a .) . map
mf a = (. map) (filter a .)
mf a = (. map) ((filter a) .)
mf a = (. map) ((.) (filter a))
mf a = ((. map) . (.)) (filter a)
mf = ((. map) . (.)) . filter
mf = (. map) . (.) . filter
Podemos simplificar aún más esto de la siguiente manera:
compose f g = (. f) . (.) . g
compose f g = ((. f) . (.)) . g
compose f g = (.) ((. f) . (.)) g
compose f = (.) ((. f) . (.))
compose f = (.) ((. (.)) (. f))
compose f = ((.) . (. (.))) (. f)
compose f = ((.) . (. (.))) (flip (.) f)
compose f = ((.) . (. (.))) ((flip (.)) f)
compose = ((.) . (. (.))) . (flip (.))
Usando compose
ahora puedes escribir mf
como:
mf = compose map filter
Sí, es un poco feo, pero también es un concepto alucinante realmente increíble. Ahora puede escribir cualquier función de la forma /xyz -> fx (gyz)
como compose fg
y eso es muy claro.
Esta es una cuestión de gusto, pero creo que ese estilo es desagradable. Primero describiré lo que significa, y luego sugiero una alternativa que prefiera.
Necesita saber que (f . g) x = f (gx)
y (f ?) x = f ? x
(f ?) x = f ? x
para cualquier operador ?
. De esto podemos deducir que
countWhere p = ((length .) . filter) p
= (length .) (filter p)
= length . filter p
asi que
countWhere p xs = length (filter p xs)
Prefiero usar una función llamada .:
(.:) :: (r -> z) -> (a -> b -> r) -> a -> b -> z
(f .: g) x y = f (g x y)
Luego countWhere = length .: filter
. Personalmente, esto me parece mucho más claro.
( .:
se define en Data.Composition
y probablemente también en otros lugares).