zipwith takewhile higher functions foldl even elem haskell filter

higher - takewhile haskell



Mejorar mi implementación Haskell de filtro (5)

¿Qué tal una lista de comprensión?

myfilter f xs = [x | x <- xs, f x]

Recientemente me he enseñado a mí mismo Haskell, y uno de mis ejercicios fue volver a implementar la función de filter . Sin embargo, de todos los ejercicios que he realizado, mi respuesta para este me parece la más fea y larga. ¿Cómo podría mejorarlo? ¿Hay algún truco de Haskell que aún no sepa?

myfilter :: (a -> Bool) -> [a] -> [a] myfilter f (x:xs) = if f x then x : myfilter f xs else myfilter f xs myfilter _ [] = []

Gracias


A modo de comparación, aquí está la implementación de Wikipedia:

myfilter :: (a -> Bool) -> [a] -> [a] myfilter _ [] = [] myfilter f (x:xs) | f x = x : myfilter f xs | otherwise = myfilter f xs


Al menos, puede SECAR un poco sacando el código myfilter f xs común:

myfilter :: (a -> Bool) -> [a] -> [a] myfilter f (x:xs) = if f x then x : rest else rest where rest = myfilter f xs myfilter _ [] = []


En Haskell, la mayoría de las veces puedes (y debes) usar guardias en lugar de si-entonces-otro:

myfilter :: (a -> Bool) -> [a] -> [a] myfilter f (x:xs) | f x = x : myfilter f xs | otherwise = myfilter f xs myfilter _ [] = []

Esto termina siendo básicamente la misma definición que se usa en la biblioteca estándar .


La forma más sencilla de mejorar tu implementación es usar guardias . En lugar de pattern = value , puede escribir pattern | boolean = value escritura pattern | boolean = value pattern | boolean = value ; esto solo coincidirá cuando boolean sea ​​verdadero. Por lo tanto, podemos obtener

filter1 :: (a -> Bool) -> [a] -> [a] filter1 p (x:xs) | p x = x : filter1 p xs | otherwise = filter1 p xs filter1 _ [] = []

(Tenga en cuenta que, de lo otherwise es solo un sinónimo de True ). Ahora, tenemos filter p xs en dos lugares, por lo que podemos moverlo a una cláusula where ; estos son compartidos por todo lo que comparte un patrón común, incluso si tiene una guardia diferente:

filter2 :: (a -> Bool) -> [a] -> [a] filter2 p (x:xs) | p x = x : xs'' | otherwise = xs'' where xs'' = filter2 p xs filter2 _ [] = []

(Esta implementación es la utilizada por GHCs Prelude ).

Ahora, ninguno de estos son recursivos de la cola. Esto puede ser una desventaja, pero hace que la función sea floja. Si queremos una versión recursiva de cola, podríamos escribir

filter3 :: (a -> Bool) -> [a] -> [a] filter3 p xs = let filter3'' p (x:xs) ys | p x = next $! x:ys | otherwise = next $! ys where next = filter3'' p xs filter3'' _ [] ys = reverse ys in filter3'' p xs []

Tenga en cuenta, sin embargo, que esto fallaría en listas infinitas (aunque todas las demás implementaciones funcionarán), gracias a la reverse , ¡así que lo hacemos estricto con $! . (Creo que lo hice bien, podría haber forzado la variable equivocada. Creo que esta fue la correcta).

Esas implementaciones parecen todas tuyas. Hay, por supuesto, otros. Uno está basado en foldr :

filter4 :: (a -> Bool) -> [a] -> [a] filter4 p = let check x | p x = (x :) | otherwise = id in foldr check []

Aprovechamos el estilo sin puntos aquí; ya que xs sería el último argumento para filter4 y foldr check [] , podemos elide it, y de manera similar con el último argumento de check .

También puede aprovechar la lista de mónadas:

import Control.Monad filter5 :: MonadPlus m => (a -> Bool) -> m a -> m a filter5 p xs = do x <- xs guard $ p x return x

La lista de la mónada representa el no determinismo. Usted elige un elemento x de xs , asegúrese de que satisface p , y luego devuélvalo si lo hace. Todos estos resultados luego se recopilan juntos. Pero tenga en cuenta que esto ahora es más general; esto funciona para cualquier MonadPlus (una mónada que también es un monoide, es decir, que tiene una operación binaria asociativa mplus - ++ para listas - y un elemento de identidad mzero - [] para listas), como [] o Maybe . Por ejemplo, filter5 even $ Just 1 == Nothing , y filter5 even $ Just 2 == Just 2 .

También podemos adaptar la versión basada en foldr para obtener una firma de tipo genérico diferente:

import Control.Monad import qualified Data.Foldable as F import qualified Data.Monoid as M filter6 :: (F.Foldable f, MonadPlus m, M.Monoid (m a)) => (a -> Bool) -> f a -> m a filter6 p = let check x | p x = return x | otherwise = mzero in F.foldMap check

El módulo Data.Foldable proporciona la clase de tipo Foldable , que representa cualquier estructura que se puede fold como una lista (colocando el resultado en un Monoid genérico). Nuestro filter requiere una restricción MonadPlus en el resultado para que podamos escribir return x . La función foldMap requiere una función que convierta todo en elementos de un Monoid , y luego los concatena a todos. La falta de coincidencia entre fa a la izquierda y ma a la derecha significa que podría, por ejemplo, filter6 a Maybe y obtener una lista.

Estoy seguro de que hay (¡muchas!) Otras implementaciones de filter , pero estas son las 6 que podría pensar con relativa rapidez. Ahora, ¿cuál de estos me gusta más? Es un lanzamiento entre el filter2 y el foldr basado en filter4 . Y filter5 es bueno para su firma de tipo genérico. (No creo que haya necesitado una firma de tipo como filter6 ''s). El hecho de que filter2 sea ​​usado por GHC es una ventaja, pero GHC también usa algunas reglas de reescritura funky, así que no es obvio para mí que es superior sin esos. Personalmente, probablemente iría con filter4 (o filter5 si necesitara la genericidad), pero filter2 está definitivamente bien.