The following shows an implementation of a function named printSquares. As you can see from its signature (string -> int -> int -> unit), this function takes a string as its first argument (message) and two numbers (num1 and num2) as the second and third arguments. The function prints squares of the last two arguments using the first argument to format the output. It doesn’t return any value, so the return type of the function is unit.
let printSquares message num1 num2 =
let printSquareUtlity num =
let squared = num * num
printfn "%s %d: %d" message num squared
printSquareUtlity(num1)
printSquareUtlity(num2)
The body of the printSquares function ❶ contains a nested declaration of the function printSquareUtility. This utility function takes a number as an argument, calculates its square, and prints it together with the original number. Its body ❷ contains one more nested let binding, which declares an ordinary value called squared ❸. This value is assigned the square of the argument, to make the code more readable. The utility function ends with a printfn call that prints the message, the original number, and the squared number. The first argument specifies the format and types of the arguments (%s stands for a string, and %d stands for an integer).
One more important aspect about nested declarations can be demonstrated with this example. We’ve mentioned that the parameters of a function are in scope (meaning that they can be accessed) anywhere in the body of a function. For example, the parameter message can be used anywhere in the range ❶. This also means that it can be used in the nested function declaration, and this is exactly what we do inside printSquareUtility on the fourth line ❹ when we output the numbers using the message value. The nested declarations are, of course, accessible only inside the scope where they’re declared—you can’t use printSquareUtility directly from other parts of the program, for example. Correct scoping of values also guarantees that the message parameter will always have a value.
One last aspect of value declarations in F# is that they can be used for declaring mutable values. Even though we usually work with immutable values in functional programs, it’s sometimes useful to be able to create a mutable value as well.