f# System.Char.ToUpper
Asked Answered
L

1

6

I have an exercise that asks for a function that converts all characters of a string to uppercase using

System.Char.ToUpper

So first I changed the string to a char array and changed the array into a list of chars

let x = s.ToCharArray()
List.ofArray x

Next, I thought I would use List.iter to iterate through my list and use the System.Char.ToUpper function on each character.

List.iter (fun z -> (System.Char.ToUpper(z)))

This is not working however. I get an error 'The expression was supposed to have unit but here has char.' What am I doing wrong? Is it a flaw in logic or syntax?

Lamina answered 18/2, 2017 at 4:17 Comment(0)
T
10

This needs some unpacking.

First, your core mistake: System.Char.ToUpper is a function. It takes a char and returns another char. It doesn't somehow "update" its argument to a new value.

let x = 'a'
let y = System.Char.ToUpper x  // y = 'A', x = 'a'.

In the above code, I give name y to the result of the function. The value of y is 'A', but the value of x is still 'a'. After calling the function, x hasn't changed.

From this mistake, all the rest follows.

Second, List.iter is a function that, for every element of a list, makes something "happen". It doesn't replace each element of a list with something new, nor does it create a new list. It just makes something happen for every element. The simplest example of such "something" is printing out to console:

List.iter (fun x -> printfn "%i" x) [1; 2; 3]  // Prints "1", then "2", then "3"

Notice that this function takes two arguments: the function that represents the something that need to happen, and the list from which to take the elements. In your question, you seem to be missing the second argument. How would List.iter know which list to use?

The first argument of List.iter needs to be a function that returns unit. This is a special type in F# that basically means "no value". When a function returns no value, it means that the only reason for calling it was to make something external happen (known in functional programming as "side-effect"). This is why List.iter requires the function to return unit - it's extra protection from accidentally supplying wrong function, just as you did, actually: the function you provided returns char. This is why you receive the error that you receive.

Third, just like with ToUpper, calling List.ofArray doesn't somehow "update" x to be a list. Instead, it returns a list. If you don't give that returned list a name, it will just be lost. Which means that the way you're calling List.ofArray is futile.

What you actually need is to (1) take the sequence of characters in your string, then (2) convert it to a new sequence where each character is upper case, then (3) glue those characters back together to get a new string.

Step (1) is a no-op, because .NET strings are already sequences of chars (i.e. they implement IEnumerable<char>). Step (2) is accomplished via a common operation called Seq.map. It's an operation that converts a sequence to a new sequence by applying given function to every element. The "given function" in this case will be System.Char.ToUpper. Step (3) can be accomplished via String.concat, but you'd need to convert each char to a string first, because String.concat takes a sequence of strings, not chars.

let chars = s
let upperChars = Seq.map System.Char.ToUpper chars
let strChars = Seq.map string upperChars
let result = String.concat "" strChars

Or this can be done in a shorter way, without giving each step's result a separate name, but by piping each result straight into the next operation:

let result = 
  s
  |> Seq.map System.Char.ToUpper
  |> Seq.map string
  |> String.concat ""

And finally, there is actually a much shorter way to do it, but it's so ridiculously obvious, it feels like cheating.

The thing is, because strings are sequences, it kinda makes sense for them to have all the sequence operations. And guess what? They do! Specifically, there is a function String.map, which does the same thing as Seq.map, but for strings:

let result = String.map System.Char.ToUpper s
Tacheometer answered 18/2, 2017 at 4:45 Comment(2)
Thank you for taking the time to explain all of that to me. I am severely new to f# as I'm sure you could tell.Lamina
If you're new, here is a wonderful resource to start: fsharpforfunandprofit.comTacheometer

© 2022 - 2024 — McMap. All rights reserved.