1

I need to find the minimum value of a list using foldr.

Here’s the code I wrote:

fun minlist nil = nil
  | minlist (x::xs) = List.foldr (fn (y,z) => if y < z then y else z) x xs;

However I’m getting an error: “Overloaded > cannot be applied to argument(s) of type ‘a list”

I’ve been stuck for a while. Any help is appreciated

2 Answers2

1

Your first clause says that the minimum value of the empty list is a list.
Thus, (fn (y,z) => if y < z then y else z) produces a list, and y and z must also be lists.

There is no sensible value you can produce for an empty list, so you should either remove that case and live with the compilation warning, or raise an exception.

molbdnilo
  • 64,751
  • 3
  • 43
  • 82
0

Finding the minimum of two values

Your expression if y < z then y else z has a built-in name Int.min (y, z).

Dealing with empty lists

You handle the empty list as minlist nil = nil, which means that "the smallest int of an empty int list is the empty list". But the empty list is not an int and so cannot be an element in an int list, or the return value for a function that otherwise returns smallest ints.

As molbdnilo also says, you could either live with a compilation warning (and risk having a Match exception raised at runtime if you ever feed the function an empty list), or raise a specific exception such as Empty when given the empty list. Neither are good, but the latter at least makes the problem clear.

Writing this without foldr it might look like:

fun minimum [] = raise Empty
  | minimum [x] = x
  | minimum (x::xs) = Int.min (x, minimum xs)

Converting a recursive function to a fold

Given some recursive function foo that depends on some function bar and some default value acc:

fun foo [] = acc
  | foo (x::xs) = bar (x, foo xs)

you may notice the similarities between minimum and foo:

  • acc is x, some minimum value
  • bar is Int.min.

This is an attempt to generalise the recursion scheme of minimum.

Given the function foldr:

fun foldr f e []      = e
  | foldr f e (x::xs) = f (x, foldr f e xs);

you may notice the same similarities:

  • f is bar, but made into a parameter
  • e is acc, but made into a parameter

The only thing from minimum that does not fit this general recursion scheme is handling the empty list. So you still have to do that separate from foldr:

fun minimum [] = ...
  | minimum (x::xs) = foldr ...

But the rest is similar.

Error-aware return types

A third option would be to change the type signature of the function into

val minimum : int list -> int option

which your current exercise perhaps disallows.

Writing this without foldr it might look like:

fun minimum [] = NONE
  | minimum [x] = SOME x
  | minimum (x::xs) =
    case minimum xs of
         NONE => SOME x
       | SOME y => SOME (Int.min (x, y))

or better yet:

fun minimum [] = NONE
  | minimum [x] = SOME x
  | minimum (x::xs) = Option.map (fn y => Int.min (x, y)) (minimum xs)

Converting this function to use foldr is the same process, but with a different f.

Tail recursion

The minimum function without folding (repeated from above):

fun minimum [] = raise Empty
  | minimum [x] = x
  | minimum (x::xs) = Int.min (x, minimum xs)

has a problem being that it mainly uses stack memory.

This can be illustated by evaluating the function by hand:

   minimum [1,2,3,4,5]
~> Int.min (1, minimum [2,3,4,5])
~> Int.min (1, Int.min (2, minimum [3,4,5]))
~> Int.min (1, Int.min (2, Int.min (3, minimum [4,5])))
~> Int.min (1, Int.min (2, Int.min (3, Int.min (4, minimum [5]))))
~> Int.min (1, Int.min (2, Int.min (3, Int.min (4, 5))))
~> Int.min (1, Int.min (2, Int.min (3, 4)))
~> Int.min (1, Int.min (2, 3))
~> Int.min (1, 2)
~> 1

Since the outer Int.min cannot be calculated before the recursive call has returned, the amount of stack memory used to compute the function grows proportional to the length of the list.

You can avoid this by using an accumulating argument:

fun minimum [] = raise Empty
  | minimum (y::ys) =
    let fun helper [] acc = acc
          | helper (x::xs) acc = helper xs (Int.min (x, acc))
    in helper ys y end

Evaluating this function by hand:

   minimum [1,2,3,4,5]
~> helper [2,3,4,5] 1
~> helper [3,4,5] (Int.min (2, 1))
~> helper [3,4,5] 1
~> helper [4,5] (Int.min (3, 1))
~> helper [4,5] 1
~> helper [5] (Int.min (4, 1))
~> helper [5] 1
~> helper [] (Int.min (5, 1))
~> helper [] 1
~> 1

Since Int.min is commutative, you might as well solve this exercise with foldl instead of foldr in the exact same way as you would have above, and you'd have a tail-recursive variant that uses less stack space.

sshine
  • 15,635
  • 1
  • 41
  • 66