1

I'm programming in SML, trying to take a string and make all the characters capitalized. I'm new to SML and functional programming in general, and I can't quite get the types to match. My code looks like this:

fun allCaps (str) =
  let val ex = explode(str)
    in
      let fun toCaps (acc, nil: char list) = acc
            | toCaps (acc, h::t: char list) = toCaps ((acc::t), [Char.toUpper(h)])
      in
        toCaps(ex, []:char list)
      end
    end;

The interpreter is giving me the error

Error: operator and operand don't agree [tycon mismatch]
  operator domain: char * char list
  operand:         char list * char list
in expression:
  toCaps (acc :: t,Char.toUpper h :: nil)
...
  toCaps (nil: char list,ex)

which makes no sense to me, because it looks very explicit in the function that it is dealing with lists the entire time. Regardless, how do I initialize an empty char type to get the types matching?

sshine
  • 15,635
  • 1
  • 41
  • 66
Allen
  • 29
  • 1
  • 2
  • 10

1 Answers1

2

trying to take a string and make all the characters capitalized

To convert a string to uppercase,

- val allCaps = String.map Char.toUpper;
- allCaps "Hello World!";
> val it = "HELLO WORLD!" : string

For some general feedback on your code,

  • (Logical error) toCaps takes two arguments, (1) the exploded string, and (2) an empty list. But you call the exploded string acc and pattern match against nil/h::t on the empty list; you probably want it the other way around.

  • (Type error) You write toCaps ((acc::t), ...), which means putting acc, a list, in front of t, another list. But acc is itself a list of the same kind as t; lists can only contain elements of the same kind, so they can't contain elements of their own type.

  • You don't need to nest let-expressions; one let-expression can have multiple declarations:

    fun allCaps s =
      let val L = explode s
          fun toCaps ...
      in ... end
    
  • You don't need type annotations unless it improves clarity; the compiler will infer the type.

Converting a string to a list of chars, recursing over that list, and converting the list back to a string, is inefficient but a good learning exercise in list recursion. Here's a revised version of your code:

fun allCaps s =
  let fun upper (c::cs) = Char.toUpper c :: upper cs
        | upper [] = []
  in implode (upper (explode s)) end

This function isn't tail-recursive; for very long strings, upper's calls to itself might eventually exhaust stack memory. You can avoid this by only making tail-calls and by keeping the accumulated result in heap memory by using a function argument as temporary storage:

fun allCaps s =
  let fun upper (c::cs, acc) = upper (cs, Char.toUpper c :: acc)
        | upper ([], acc) = rev acc
  in implode (upper (explode s, [])) end

The drawback is that when you push the first character from c::cs into the front of acc, they end up in reverse order and you need to reverse the result again before imploding it.

Either way, the string-only solution presented at the top uses less memory because it only needs to create a single string the same size as the input and loop over the indices of the input string.

sshine
  • 15,635
  • 1
  • 41
  • 66
  • Thank you so much! There is almost no information on sml, and there is even less on functions that handle lists this way. I was on the right path after posing this question by trying to reverse a reversed list, but had a lot of type issues there as well. This comment can't tell you how grateful I am. – Allen Apr 06 '18 at 08:35
  • @Allen: You're absolutely right. Most people who learn SML do so through university where textbooks are supplied with lectures and classes. Learning SML online is considerably harder. – sshine Apr 06 '18 at 08:38
  • 1
    @Allen Learning materials: Paulson's [ML for the Working Programmer](https://www.cl.cam.ac.uk/~lp15/MLbook/pub-details.html) is now available online in PDF form, and [this course](https://courses.cs.washington.edu/courses/cse341/17sp/) is supposed to be good (and includes lecture videos). – molbdnilo Apr 06 '18 at 12:12