1

In SML, I've been taught the idiomatic way to define a variable local to the function as:

fun correct_fun() =
    let val x = 1
    in x + 2
    end

Why do I have to use let, and not just val like so:

fun incorrect_fun() =
    val x = 1
    x + 2

The incorrect_fun() throws an error but I don't understand why. Why can't val be used inside a function without let?

Heisenberg
  • 8,386
  • 12
  • 53
  • 102

1 Answers1

4

Why can't val be used inside a function without let?

Because val is a kind of declaration, let is a kind of expression, and a function body is an expression.

The syntactic structure of a let-expression is letdecinexpend. So in using a let-expression as the function body, the exp within the let is equivalent to the function body, but with an extended, local scope of whatever dec adds.

The let-expression lets you use any kind of declaration, not just val declarations.

For example, you can use exception handling as a control-flow mechanism used for backtracking, and you can nest helper functions that are only used locally and possibly takes multiple arguments for storing a temporary result, but you may not want to expose the exception or the helper functions. So for the Eight Queens puzzle, you might refine this solution (from supplemental notes on functional programming, pp. 140-143, by Niels Andersen):

fun concatMap f xs = String.concat (List.map f xs)
fun concatTab f n = String.concat (List.tabulate (n, f))

fun dots n = concatTab (fn _ => ". ") n
fun show ys = concatMap (fn y => dots (y - 1) ^ "* " ^ dots (8 - y) ^ "\n") ys

fun queen dims =
    let exception Queen
        fun beats ((x,y),(x1,y1)) = (* x = x1 *)
                         (* orelse *)  y = y1
                            orelse x + y = x1 + y1
                            orelse x - y = x1 - y1

        fun safe ((x, y), _, []) = true
          | safe ((x, y), x1, y1::ys) =
            not (beats ((x, y), (x1, y1))) andalso safe ((x, y), x1 + 1, ys)

        fun queen' ((0, _), ys) = ys
          | queen' ((_, 0), _) = raise Queen
          | queen' ((x, y), ys)  =
            if safe ((x, y), x + 1, ys)
              then queen' ((x - 1, 8), y :: ys)
                   handle Queen => queen' ((x, y - 1), ys)
              else queen' ((x, y - 1), ys)

    in queen' (dims, []) end

Demonstrating it;

- print (show (queen ((8,8))));
. . . . * . . . 
. . . . . . * . 
. * . . . . . . 
. . . . . * . . 
. . * . . . . . 
* . . . . . . . 
. . . * . . . . 
. . . . . . . * 

When you're using let-expressions to mainly declare temporary values, you could also consider going with a case-of. See the Q&A's for Difference between "local" and "let" in SML and nested local declarations in ML of NJ for that.

sshine
  • 15,635
  • 1
  • 41
  • 66
  • So `val` cannot be used inside a function body because the function body must be an expression? On the other hand, using `val` "outside" (i.e. when I write `val x = 1` in a script) is fine because "outside" allows declaration? (These things sound like basics of functional programming that I didn't know!) – Heisenberg May 04 '18 at 15:18
  • 1
    @Heisenberg: When by "outside the function" you mean either before or after the function declaration (e.g. at the top-level, or inside the first part of a `let`-expression, or a number of other places), then yes, you can have declarations of all kinds there. This isn't a property of functional programming, but specifically a property of the syntax of Standard ML. In the [lambda calculus](https://en.wikipedia.org/wiki/Lambda_calculus), which is the theoretical basis for functional programming, you only have lambda expressions, no declarations of any kind. – sshine May 07 '18 at 14:09