Understanding "let" & "in" in ML programming

1.2k views Asked by At

My teacher recently went over a function in ML that uses "let" & "in" but the body of the function is confusing to me as I dont understand how they work together to produce the result. The function takes a list of vegetables in your garden and replaces an original vegetable with a given substitute, so that list will print out the substitute in every location where the original element is in.This is the function

image to function code

fun replaceVegetable(orig, subt, Garden([]) = Garden([])

  | replaceVegetable(orig, subt, Garden([first::rest]) =

      let val Garden(newRest) = 

          replaceVegetable(orig, subst, Garden(rest));

      in Garden((if first = orig then subst else first)::newRest)

      end

  | replaceVegetable(orig, subst, x) = x;

Im not worried about the last pattern "replaceVegetable(orig, subst, x) = x;", im mainly concerned about understanding the second pattern. I think I understand that Garden(newRest) is a local variable to the function and that whatever replaceVegetable(orig, subst, Garden(rest)) produces will be stored in that local variable. I dont exactly know what happens on "in Garden((if first = orig then subst else first)::newRest)" is this applying recursion so it can run through the list I give it to see where it has to replace the original with the substitute? If so, I can't exactly see how its doing that as the function as a whole is confusing for me to look at.

1

There are 1 answers

0
molbdnilo On BEST ANSWER

let, in, and end go together; in Garden((if first ... is not a "unit of language" and doesn't mean anything.

In the simpler form,

let val x = y in e end

means "in the expression 'e', 'x' has the same value as 'y'.

If the function took just a plain list, it might be easier to understand:

fun replaceVegetable(orig, subst, []) = []
  | replaceVegetable(orig, subst, first::rest) =
    let val newRest  = 
            replaceVegetable(orig, subst, rest)
    in (if first = orig then subst else first)::newRest
    end
  | replaceVegetable(orig, subst, x) = x;

The second case here is exactly the same as

| replaceVegetable(orig, subst, first::rest) =
      (if first = orig then subst else first)::(replaceVegetable(orig, subst, rest))

Your function also has a pattern-matching binding instead of a plain variable binding.

let val Garden newRest = replaceVegetable(...)
in ...
end

matches on the result of the recursion and binds the "wrapped" list to newRest.
It means exactly the same as

case replaceVegetable (...) of
     Garden newRest => ...