How would you define map and filter using foldr in Haskell?

后端 未结 8 2398
一个人的身影
一个人的身影 2020-12-05 14:58

I\'m doing a bit of self study on functional languages (currently using Haskell). I came across a Haskell based assignment which requires defining map and filter in terms of

相关标签:
8条回答
  • 2020-12-05 15:30

    A different way to think about it - foldr exists because the following recursive pattern is used often:

    -- Example 1: Sum up numbers
    summa :: Num a => [a] -> a
    summa []     = 0
    summa (x:xs) = x + suma xs
    

    Taking the product of numbers or even reversing a list looks structurally very similar to the previous recursive function:

    -- Example 2: Reverse numbers
    reverso :: [a] -> [a]
    reverso []      = []
    reverso (x:xs)  = x `op` reverso xs
      where
        op = (\curr acc -> acc ++ [curr])
    

    The structure in the above examples only differs in the initial value (0 for summa and [] for reverso) along with the operator between the first value and the recursive call (+ for summa and (\q qs -> qs ++ [q]) for reverso). So the function structure for the above examples can be generally seen as

    -- Generic function structure
    foo :: (a -> [a] -> [a]) -> [a] -> [a] -> [a]
    foo op init_val []      = init_val
    foo op init_val (x:xs)  = x `op` foo op init_val xs
    

    To see that this "generic" foo works, we could now rewrite reverso by using foo and passing it the operator, initial value, and the list itself:

    -- Test: reverso using foo
    foo (\curr acc -> acc ++ [curr]) [] [1,2,3,4]
    

    Let's give foo a more generic type signature so that it works for other problems as well:

    foo :: (a -> b -> b) -> b -> [a] -> b
    

    Now, getting back to your question - we could write filter like so:

    -- Example 3: filter
    filtero :: (a -> Bool) -> [a] -> [a]
    filtero p []     = []
    filtero p (x:xs) = x `filterLogic` (filtero p xs)
      where
         filterLogic = (\curr acc -> if (p curr) then curr:acc else acc)
    

    This again has a very similar structure to summa and reverso. Hence, we should be able to use foo to rewrite it. Let's say we want to filter the even numbers from the list [1,2,3,4]. Then again we pass foo the operator (in this case filterLogic), initial value, and the list itself. filterLogic in this example takes a p function, called a predicate, which we'll have to define for the call:

    let p = even in foo (\curr acc -> if (p curr) then curr:acc else acc) [] [1,2,3,4] 
    

    foo in Haskell is called foldr. So, we've rewritten filter using foldr.

    let p = even in foldr (\curr acc -> if (p curr) then curr:acc else acc) [] [1,2,3,4] 
    

    So, filter can be written with foldr as we've seen:

    -- Solution 1: filter using foldr
    filtero' :: (a -> Bool) -> [a] -> [a]
    filtero' p xs = foldr (\curr acc -> if (p curr) then curr:acc else acc) [] xs 
    

    As for map, we could also write it as

    -- Example 4: map
    mapo :: (a -> b) -> [a] -> [b]
    mapo f []   = []
    mapo f (x:xs) = x `op` (mapo f xs)
      where
        op = (\curr acc -> (f curr) : acc)
    

    which therefore can be rewritten using foldr. For example, to multiply every number in a list by two:

    let f = (* 2) in foldr (\curr acc -> (f curr) : acc) [] [1,2,3,4]
    

    So, map can be written with foldr as we've seen:

    -- Solution 2: map using foldr
    mapo' :: (a -> b) -> [a] -> [b]
    mapo' f xs = foldr (\curr acc -> (f curr) : acc) [] xs
    
    0 讨论(0)
  • 2020-12-05 15:32

    Your solution almost works .) The problem is that you've got two differend bindings for x in both your functions (Inside the patternmatching and inside your lambda expression), therefore you loose track of the first Element.

    map'            :: (a -> b) -> [a] -> [b]
    map' f []       = []
    map' f (x:xs)   = foldr (\x xs -> (f x):xs) [] (x:xs)
    
    filter'             :: (a -> Bool) -> [a] -> [a]
    filter' p []        = []
    filter' p (x:xs)    = foldr (\x xs -> if p x then x:xs else xs ) [] (x:xs)
    

    This should to the trick :). Also: you can write your functions pointfree style easily.

    0 讨论(0)
  • 2020-12-05 15:34
    *Main> :{
    *Main| map' :: (a -> b) -> [a] -> [b]
    *Main| map' = \f -> \ys -> (foldr (\x -> \acc -> f x:acc) [] ys)
    *Main| :}
    *Main> map' (^2) [1..10]
    [1,4,9,16,25,36,49,64,81,100]
    
    *Main> :{
    *Main| filter' :: (a -> Bool) -> [a] -> [a]
    *Main| filter' = \p -> \ys -> (foldr (\x -> \acc -> if p x then x:acc else acc) [] ys)
    *Main| :}
    *Main> filter' (>10) [1..100]
    

    In the above snippets acc refers to accumulator and x refers to the last element.

    0 讨论(0)
  • 2020-12-05 15:46

    For your first question, foldr already has a case for the empty list, so you need not and should not provide a case for it in your own map.

    map' f = foldr (\x xs -> f x : xs) []
    

    The same holds for filter'

    filter' p = foldr (\x xs -> if p x then x : xs else xs) []
    

    Nothing is wrong with your lambda expressions, but there is something wrong with your definitions of filter' and map'. In the cons case (x:xs) you eat the head (x) away and then pass the tail to foldr. The foldr function can never see the first element you already ate. :)

    Alse note that:

    filter' p = foldr (\x xs -> if p x then x : xs else xs) []
    

    is equivalent (η-equivalent) to:

    filter' p xs = foldr (\x xs -> if p x then x : xs else xs) [] xs
    
    0 讨论(0)
  • 2020-12-05 15:46

    I would define map using foldr and function composition as follows:

    map :: (a -> b) -> [a] -> [b]
    map f = foldr ((:).f) []
    

    And for the case of filter:

    filter :: (a -> Bool) -> [a] -> [a]
    filter p = foldr (\x xs -> if p x then x:xs else xs) []
    

    Note that it is not necessary to pass the list itself when defining functions over lists using foldr or foldl. The problem with your solution is that you drop the head of the list and then apply the map over the list and this is why the head of the list is missing when the result is shown.

    0 讨论(0)
  • 2020-12-05 15:50

    I wish I could just comment, but alas, I don't have enough karma.

    The other answers are all good ones, but I think the biggest confusion seems to be stemming from your use of x and xs.

    If you rewrote it as

    map'            :: (a -> b) -> [a] -> [b]
    map' f []       = []
    map' f (x:xs)   = foldr (\y ys -> (f y):ys) [] xs
    

    you would clearly see that x is not even mentioned on the right-hand side, so there's no way that it could be in the solution.

    Cheers

    0 讨论(0)
提交回复
热议问题