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

后端 未结 8 2502
一个人的身影
一个人的身影 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
    

提交回复
热议问题