I suspect that the issue you ran into with annotating the type of d
was actually just that you weren't surrounding the annotation with parens, this works for me:
fun convert (d : ('a * 'b) list) = ( (map (#1) d) , (map (#2) d) )
However, this isn't great SML style. Using #1
, #2
, #n
and so on is somewhat discouraged since it leads to issues like this, where you've lost some of the usual succintness that type inference gives you.
Instead, you can define some explicit selection functions for pairs:
fun first (a, _) = a
fun second (_, b) = b
fun convert d = (map first d, map second d)
(Note that I've also dropped some superfluous parenthesis from the body of convert
, since function application has higher precedence then tuple construction, and I've also dropped the semi-colon which is only really necessary when sequencing imperative code or when typing code into the REPL)
This is a pretty good style guide for Standard ML, from a course at Harvard (or maybe Tufts). An older version of that doc mentioned this specifically under "Common mistakes to avoid".
Avoid #1 and #2
Some beginners pick up the idea that a good way to get the second element of a pair p
is to write #2 p
.
This style is not idiomatic or readable, and it can confuse the type checker. The proper way to handle pairs is by pattern matching, so
fun first (x, _) = x
fun second (_, y) = y
is preferred, and not
fun bogus_first p = #1 p (* WRONG *)
fun bogus_second p = #2 p
(For reasons I don’t want to discuss, these versions don’t even type-check.)
If your pair or tuple is not an argument to a function, use val
to do the pattern matching:
val (x, y) = lookup_pair mumble
But usually you can include matching in ordinary fun matching.