simbolos opciones mundo hola hacer ejemplos como ciclos basico haskell functional-programming pointfree function-composition tacit-programming

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).