traté de entender este elegante solución a mí mismo, por lo que trataron de obtener los tipos y evaluación de mí mismo. Por lo tanto, tenemos que escribir una función:
zip xs ys = foldr step done xs ys
Aquí tenemos que derivar step
y done
, cualesquiera que sean. Tipo recuerdo foldr
's, una instancia de listas:
foldr :: (a -> state -> state) -> state -> [a] -> state
Sin embargo nuestra foldr
invocación debe ser instanciada a algo parecido a continuación, porque hay que aceptar no uno, sino dos listas de argumentos:
foldr :: (a -> ? -> ?) -> ? -> [a] -> [b] -> [(a,b)]
Debido ->
es right-associative, esto es equivalente a:
foldr :: (a -> ? -> ?) -> ? -> [a] -> ([b] -> [(a,b)])
Nuestra ([b] -> [(a,b)])
corr esponds a state
variable de tipo en la firma foldr
tipo original, por lo tanto, hay que reemplazar todas las apariciones de state
con él:
foldr :: (a -> ([b] -> [(a,b)]) -> ([b] -> [(a,b)]))
-> ([b] -> [(a,b)])
-> [a]
-> ([b] -> [(a,b)])
Esto significa que los argumentos que pasamos a foldr
deben tener los siguientes tipos:
step :: a -> ([b] -> [(a,b)]) -> [b] -> [(a,b)]
done :: [b] -> [(a,b)]
xs :: [a]
ys :: [b]
Recordemos que foldr (+) 0 [1,2,3]
se expande a:
1 + (2 + (3 + 0))
Por lo tanto, si xs = [1,2,3]
y ys = [4,5,6,7]
, nuestra invocación foldr
se expandiría a:
1 `step` (2 `step` (3 `step` done)) $ [4,5,6,7]
Esto significa que nuestra 1 `step` (2 `step` (3 `step` done))
constructo debe crear una función recursiva que pasaría por [4,5,6,7]
y cerrar la cremallera de los elementos. (Tenga en cuenta que si una de las listas originales es más larga, los valores sobrantes se descartan). IOW, nuestra construcción debe tener el tipo [b] -> [(a,b)]
.
3 `step` done
es nuestro caso base, donde done
es un valor inicial, al igual que en 0
foldr (+) 0 [1..3]
. No deseamos comprimir nada después de 3, porque 3 es el valor final de xs
, por lo que debemos finalizar la recursión. ¿Cómo terminas la recursión sobre la lista en el caso base? Devuelve la lista vacía []
. Pero recordar done
tipo de firma:
done :: [b] -> [(a,b)]
Por lo tanto, no se puede devolver sólo []
, hay que volver a una función que haga caso omiso de lo que recibe. Por lo tanto utilizar const
:
done = const [] -- this is equivalent to done = \_ -> []
Ahora vamos a empezar a averiguar lo que debería haber step
. Combina un valor del tipo a
con una función del tipo [b] -> [(a,b)]
y devuelve una función del tipo [b] -> [(a,b)]
.
En 3 `step` done
, sabemos que el valor del resultado que más tarde pasaría a nuestra lista de cremallera debe ser (3,6)
(sabiendo de originales xs
y ys
). Por lo tanto 3 `step` done
debe evaluar en:
\(y:ys) -> (3,y) : done ys
Recuerde, hay que volver a una función, dentro de la cual de alguna manera cerrar la cremallera de los elementos, el código anterior es lo que tiene sentido y typechecks.
Ahora que asumimos cómo debe evaluarse exactamente step
, continuemos la evaluación. Así es como todas las medidas de reducción en nuestra evaluación foldr
se parecen:
3 `step` done -- becomes
(\(y:ys) -> (3,y) : done ys)
2 `step` (\(y:ys) -> (3,y) : done ys) -- becomes
(\(y:ys) -> (2,y) : (\(y:ys) -> (3,y) : done ys) ys)
1 `step` (\(y:ys) -> (2,y) : (\(y:ys) -> (3,y) : done ys) ys) -- becomes
(\(y:ys) -> (1,y) : (\(y:ys) -> (2,y) : (\(y:ys) -> (3,y) : done ys) ys) ys)
La evaluación da lugar a esta realización de la etapa (nótese que cuenta ys
quedarse sin elementos temprana devolviendo una lista vacía):
step x f = \[] -> []
step x f = \(y:ys) -> (x,y) : f ys
Por lo tanto, la función completa zip
se implementa de la siguiente manera:
zip :: [a] -> [b] -> [(a,b)]
zip xs ys = foldr step done xs ys
where done = const []
step x f [] = []
step x f (y:ys) = (x,y) : f ys
PD: Si está inspirado en la elegancia de los pliegues, lea Writing foldl using foldr y luego A tutorial on the universality and expressiveness of fold de Graham Hutton.
¿Cómo funciona esto? ¿No foldr solo toma 3 argumentos? – Claudiu
eres malvado ... ¿no estás diciendo que es diong (foldr step done xs) y luego lo aplicas a ys? – Claudiu
Es el mismo algoritmo que mattiast (quien publicó 4 segundos más rápido). A la derecha, (foldr step done xs) devuelve una función que consume ys; de modo que bajamos por la lista xs construyendo una composición anidada de funciones que se aplicarán a la parte correspondiente de ys. –