Nested functions are just one way to split up your workload in multiple, smaller parts. Another option is non-nested library functions. The main differences are that functions that aren't nested don't inherit its parent's variable scope, so they can only work with their own input, and functions that are nested aren't available anywhere else and can't be re-used. Let's say you're giving this problem a first stab:
fun digit_meh n = if n < 10 then [n] else n mod 10 :: digit_meh (n div 10)
And you realize it isn't doing exactly as you want:
- digit_meh 1234;
> val it = [4, 3, 2, 1] : int list
You could remove the most significant digit first, but the calculation isn't as trivial as n mod 10
, since it depends on the number of digits.
You could generate this list and then reverse it:
fun digit n = rev (digit_meh n)
But the function digit_meh
isn't particularly useful outside of this function, so it could be hidden using local-in-end or let-in-end:
local
fun digit_meh n = if n < 10 then [n] else n mod 10 :: digit_meh (n div 10)
in
val digit = rev o digit_meh
end
fun digit n =
let fun meh n = if n < 10 then [n] else n mod 10 :: meh (n div 10)
in rev (meh n) end
Do notice that the function meh
's copy of n
shadows digit
's copy of n
.
For clarity you could also name the variables differently.
Or you could look at how rev
is doing its thing and do that. It basically treats its input as a stack and puts the top element in a new stack recursively so that the top becomes the bottom, much like StackOverflow's logo would look like if it jumped out and landed upside down like a slinky spring:
fun rev L =
let fun rev_stack [] result = result
| rev_stack (x::xs) result = rev_stack xs (x::result)
in rev_stack L [] end
Because the result is accumulated in an additional argument, and rev
should only take a single argument, nesting a function with an extra accumulating argument is a really useful trick.
You can mimic this behavior, too:
fun digit N =
let fun digit_stack n result =
if n < 10
then n::result
else digit_stack (n div 10) (n mod 10::result)
in f N [] end
This way, we continue to treat the least significant digit first, but we put it in the stack result
which means it ends up at the bottom / end. So we don't need to call rev
and save that iteration of the list.
In practice, you don't have to hide helper functions using either local-in-end or let-in-end; while it can be useful in the case of let-in-end to inherit a parent function's scope, it is not necessary to hide your functions once you start using modules with opaque signatures (the :>
operator):
signature DIGIT =
sig
val digit : int -> int list
end
structure Digit :> DIGIT =
struct
fun digit_stack n result =
if n < 10
then n::result
else digit_stack (n div 10) (n mod 10::result)
fun digit n = digit_stack n []
end
As this is entered into a REPL, only the relevant function is available outside of the module:
> structure Digit : {val digit : int -> int list}
signature DIGIT = {val digit : int -> int list}
- Digit.digit 1234;
> val it = [1, 2, 3, 4] : int list
insert
andconvert
you defined in "let in end"? – Taiin
and 'end. I wrote something. Hope that is helpful. – Tai