¡Algunas explicaciones están en orden!
¿Para qué sirve la función id? ¿Cuál es el papel de? ¿Por qué deberíamos necesitarlo aquí?
ides la función de identidad , id x = xy se utiliza como el equivalente de cero cuando la construcción de una cadena de funciones con la composición de funciones , (.). Puede encontrarlo definido en el Preludio .
En el ejemplo anterior, ¿la función id es el acumulador en la función lambda?
El acumulador es una función que se crea mediante la aplicación de funciones repetidas. No hay lambda explícita, ya que nombramos el acumulador, step. Puedes escribirlo con una lambda si quieres:
foldl f a bs = foldr (\b g x -> g (f x b)) id bs a
O como escribiría Graham Hutton :
5.1 El foldl operador
Ahora generalicemos del sumlejemplo y consideremos el operador estándar foldlque procesa los elementos de una lista en orden de izquierda a derecha usando una función fpara combinar valores y un valorv como valor inicial:
foldl :: (β → α → β) → β → ([α] → β)
foldl f v [ ] = v
foldl f v (x : xs) = foldl f (f v x) xs
Con este operador, sumlse puede redefinir simplemente con suml = foldl (+) 0. Muchas otras funciones se pueden definir de forma sencilla utilizando foldl. Por ejemplo, la función estándar se reversepuede redefinir usando foldllo siguiente:
reverse :: [α] → [α]
reverse = foldl (λxs x → x : xs) [ ]
Esta definición es más eficiente que nuestra definición original usando fold, porque evita el uso del operador de adición ineficiente (++) para listas.
Una simple generalización del cálculo en la sección anterior para la función sumlmuestra cómo redefinir la función foldlen términos de fold:
foldl f v xs = fold (λx g → (λa → g (f a x))) id xs v
Por el contrario, no es posible redefinir folden términos de foldl, debido al hecho de que
foldles estricto en la cola de su argumento de lista pero foldno lo es. Hay una serie de 'teoremas de dualidad' útiles sobrefold y foldl, y también algunas pautas para decidir qué operador se adapta mejor a aplicaciones particulares (Bird, 1998).
El prototipo de foldr es foldr :: (a -> b -> b) -> b -> [a] -> b
Un programador de Haskell diría que el tipo de foldres(a -> b -> b) -> b -> [a] -> b .
y el primer parámetro es una función que necesita dos parámetros, pero la función de paso en la implementación de myFoldl usa 3 parámetros, estoy completamente confundido
¡Esto es confuso y mágico! Jugamos una mala pasada y reemplazamos el acumulador con una función, que a su vez se aplica al valor inicial para obtener un resultado.
Graham Hutton explica el truco para convertirse foldlen foldren el artículo anterior. Comenzamos por escribir una definición recursiva de foldl:
foldl :: (a -> b -> a) -> a -> [b] -> a
foldl f v [] = v
foldl f v (x : xs) = foldl f (f v x) xs
Y luego refactorice a través de la transformación de argumento estático en f:
foldl :: (a -> b -> a) -> a -> [b] -> a
foldl f v xs = g xs v
where
g [] v = v
g (x:xs) v = g xs (f v x)
Ahora reescribamos gpara flotar vhacia adentro:
foldl f v xs = g xs v
where
g [] = \v -> v
g (x:xs) = \v -> g xs (f v x)
Que es lo mismo que pensar gen función de un argumento, que devuelve una función:
foldl f v xs = g xs v
where
g [] = id
g (x:xs) = \v -> g xs (f v x)
Ahora tenemos guna función que recorre recursivamente una lista, aplica alguna función f. El valor final es la función de identidad, y cada paso también da como resultado una función.
Pero , ya tenemos a mano una función recursiva muy similar en las listas foldr,!
2 El operador de plegado
El foldoperador tiene su origen en la teoría de la recursividad (Kleene, 1952), mientras que el uso de foldcomo concepto central en un lenguaje de programación se remonta al operador de reducción de APL (Iverson, 1962), y más tarde al operador de inserción de FP (Backus , 1978). En Haskell, el foldoperador de listas se puede definir de la siguiente manera:
fold :: (α → β → β) → β → ([α] → β)
fold f v [ ] = v
fold f v (x : xs) = f x (fold f v xs)
Es decir, dada una función fde tipo α → β → βy un valor vde tipo β, la función
fold f vprocesa una lista de tipo [α]para dar un valor de tipo βreemplazando el constructor nil []al final de la lista por el valor v, y cada constructor cons (:)dentro de la lista por la función f. De esta manera, el foldoperador encapsula un patrón simple de recursividad para procesar listas, en el que los dos constructores de listas simplemente se reemplazan por otros valores y funciones. Varias funciones familiares en listas tienen una definición simple usando fold.
Esto parece un esquema recursivo muy similar a nuestra gfunción. Ahora el truco: usando toda la magia disponible (también conocida como Bird, Meertens y Malcolm) aplicamos una regla especial, la propiedad universal de fold , que es una equivalencia entre dos definiciones para una función gque procesa listas, expresada como:
g [] = v
g (x:xs) = f x (g xs)
si y solo si
g = fold f v
Entonces, la propiedad universal de los pliegues establece que:
g = foldr k v
donde gdebe ser equivalente a las dos ecuaciones, para algunas ky v:
g [] = v
g (x:xs) = k x (g xs)
De nuestros diseños de pliegues anteriores, lo sabemos v == id. Sin embargo, para la segunda ecuación, necesitamos calcular la definición de k:
g (x:xs) = k x (g xs)
<=> g (x:xs) v = k x (g xs) v
<=> g xs (f v x) = k x (g xs) v
<= g' (f v x) = k x g' v
<=> k = \x g' -> (\a -> g' (f v x))
Lo cual, sustituyendo nuestras definiciones calculadas de ky vproduce una definición de foldl como:
foldl :: (a -> b -> a) -> a -> [b] -> a
foldl f v xs =
foldr
(\x g -> (\a -> g (f v x)))
id
xs
v
El recursivo gse reemplaza con el combinador plegable, y el acumulador se convierte en una función construida a través de una cadena de composiciones def en cada elemento de la lista, en orden inverso (por lo que doblamos a la izquierda en lugar de a la derecha).
Esto definitivamente es algo avanzado, por lo que para comprender profundamente esta transformación, la propiedad universal de los pliegues , que hace posible la transformación, recomiendo el tutorial de Hutton, vinculado a continuación.
Referencias
step = curry $ uncurry (&) <<< (flip f) *** (.)