SML Create function receives list of tuples and return list with sum each pair

Viewed 687

I'm studying Standard ML and one of the exercices I have to do is to write a function called opPairs that receives a list of tuples of type int, and returns a list with the sum of each pair. Example:

input: opPairs [(1, 2), (3, 4)]
output: val it = [3, 7]

These were my attempts, which are not compiling:

ATTEMPT 1

type T0 = int * int;
fun opPairs ((h:TO)::t) = let val aux =(#1 h + #2 h) in
                              aux::(opPairs(t))
                         end;

The error message is:
Error: unbound type constructor: TO
Error: operator and operand don't agree [type mismatch]
  operator domain: {1:'Y; 'Z}
  operand:         [E]
  in expression:
    (fn {1=1,...} => 1) h

ATTEMPT 2

fun opPairs2 l = map (fn x => #1 x + #2 x ) l;

The error message is: Error: unresolved flex record (need to know the names of ALL the fields
 in this context)
  type: {1:[+ ty], 2:[+ ty]; 'Z}
2 Answers

The first attempt has a typo: type T0 is defined, where 0 is zero, but then type TO is referenced in the pattern, where O is the letter O. This gets rid of the "operand and operator do not agree" error, but there is a further problem. The pattern ((h:T0)::t) does not match an empty list, so there is a "match nonexhaustive" warning with the corrected type identifier. This manifests as an exception when the function is used, because the code needs to match an empty list when it reaches the end of the input.

The second attempt needs to use a type for the tuples. This is because the tuple accessor #n needs to know the type of the tuple it accesses. To fix this problem, provide the type of the tuple argument to the anonymous function:

fun opPairs2 l = map (fn x:T0 => #1 x + #2 x) l;

But, really it is bad practice to use #1, #2, etc. to access tuple fields; use pattern matching instead. Here is a cleaner approach, more like the first attempt, but taking full advantage of pattern matching:

fun opPairs nil = nil
  | opPairs ((a, b)::cs) = (a + b)::(opPairs cs);

Here, opPairs returns an empty list when the input is an empty list, otherwise pattern matching provides the field values a and b to be added and consed recursively onto the output. When the last tuple is reached, cs is the empty list, and opPairs cs is then also the empty list: the individual tuple sums are then consed onto this empty list to create the output list.

To extend on exnihilo's answer, once you have achieved familiarity with the type of solution that uses explicit recursion and pattern matching (opPairs ((a, b)::cs) = ...), you can begin to generalise the solution using list combinators:

val opPairs = map op+
Related