0

I'm trying to use filter (though I can also use map and/or foldr) to find the max element of a list.

I tried filtering out every number less than max but it refuses to accept max as a filter argument.

Here's my code:

 max' :: Ord a => [a] -> a
 max' xs = filter (< max) xs

Here's the error I get:

* Couldn't match type `a' with `a0 -> a0 -> a0'
  `a' is a rigid type variable bound by
    the type signature for:
      max' :: forall a. Ord a => [a] -> a
    at Prog8.hs:50:1-25
  Expected type: [a0 -> a0 -> a0]
    Actual type: [a]
* In the second argument of `filter', namely `xs'
  In the expression: filter (< max) xs
  In an equation for max': max' xs = filter (< max) xs
* Relevant bindings include
    xs :: [a] (bound at Prog8.hs:51:6)
    max' :: [a] -> a (bound at Prog8.hs:51:1)

Is there a way to write max' in a simple filter function (or maybe combine it with map or foldr)?

  • It's easy enough to implement `max` as a fold. (I think any recursive function on a list can be done as a fold.) But you definitely can't do it with just `map` and/or `filter`. (Well I don't see how, someone might prove me wrong though :) ) – Robin Zigmond May 02 '19 at 16:43
  • Could I do it with foldr? I'm trying to stick with foldr, map, and filter. –  May 02 '19 at 16:44
  • Yes, you certainly can. Your accumulator is just the maximum "so far". You probably want `foldr1` rather than `foldr` (it crashes on an empty list, but so does `max`). – Robin Zigmond May 02 '19 at 16:48
  • 1
    Just realised the function that finds the max value of a list is actually `maximum` (`max` takes 2 plain values and returns the greater) – Robin Zigmond May 02 '19 at 16:49
  • 1
    With the type signature you have, the only sensible thing that `max' []` can return is ⊥. If you can restructure your calling code so that this can have either `Ord a => NonEmpty a -> a` or `Ord a => [a] -> Maybe a` instead, then ⊥ can be avoided. – Joseph Sible-Reinstate Monica May 02 '19 at 19:34

2 Answers2

1

An empty list has no maximum element, so you can't write a total function with the type you've provided. A more sensible one is

maximum' :: Ord a => [a] -> Maybe a

The easy way to do this is with foldl':

maximum' = foldl' gom Nothing where
  gom Nothing new = Just new
  gom (Just old) new
    | old < new = Just new
    | otherwise = Just old

But you want foldr. Since foldl' is actually defined in terms of foldr, this is easy!

foldl' f b0 xs = foldr gof id xs b0
  where
    gof x r b = b `seq` r (f b x)

Inlining,

maximum' xs = foldr gob id xs Nothing
  where
    --gob new r b = b `seq` r (gom b new)
    gob new r b = seq b $ r $
      case b of
        Nothing -> Just new
        Just old
          | old < new -> Just new
          | otherwise -> Just old

Doing a little manual strictness analysis, this simplifies to

maximum' xs = foldr gob id xs Nothing
  where
    gob new r b = r $!
      case b of
        Nothing -> Just new
        Just old
          | old < new -> Just new
          | otherwise -> Just old

A small caution: if this is homework and you turn in my solution, your teacher will likely become suspicious. There's a much simpler way that's also much less efficient, but I'll let you search for it.

dfeuer
  • 48,079
  • 5
  • 63
  • 167
-1

So first off, as you discovered, what you wanted is:

max' :: Ord a => [a] -> [a]
max' xs = filter (< maximum xs) xs

If you are absolutely dedicated to pointfree style for some reason, and want to write using only higher-order functions, this will also work:

max' :: Ord a => [a] -> [a]
max' = flip filter <*> ((>) <$> maximum)

don't do that though, it's awful. Just repeat the argument twice.

Daniel Martin
  • 23,083
  • 6
  • 50
  • 70
  • This doesn't typecheck. – dfeuer May 02 '19 at 19:10
  • Sorry, I was taking the type from the question, when what the questioner asked for obviously requires a different type. Fixed. – Daniel Martin May 02 '19 at 19:13
  • 1
    I doubt that was what they really intended. – dfeuer May 02 '19 at 19:36
  • I found this super helpful, not sure why it was down-voted. –  May 03 '19 at 00:31
  • I've been having a downvote kind of day on here; see https://stackoverflow.com/q/55957477/107331 . In this case, I answered the question I thought you were asking "find all elements less than the maximum", not the question you did ask. – Daniel Martin May 03 '19 at 00:35
  • @Francis [*"Whenever you encounter a question, answer or comment that you feel is especially useful, vote it up!"*](https://stackoverflow.com/help/privileges/vote-up) "Awarded at: 15 reputation" – Will Ness May 06 '19 at 11:24
  • @DanielMartin "to filter them *out*" probably means `\xs -> filter (not . (< maximum xs)) xs = \xs -> filter (maximum xs <=) xs = filter =<< ((<=) . maximum)`. :) – Will Ness May 06 '19 at 11:28