Heisenberg
Heisenberg

Reputation: 8806

Why do I use `let` and not just `val` to declare a variable inside a function in SML?

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?

Upvotes: 1

Views: 2740

Answers (1)

sshine
sshine

Reputation: 16105

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.

Upvotes: 4

Related Questions