Search code examples
smlsmlnj

Define nested functions in standard ml


I am new in sml. I tried to convert int to int list. For example, assume that there is an input 1234, then output is a list like [1,2,3,4]. And my question is, how can I type nested functions in sml? let in end? There is my code.

 fun digit (a : int): int =

    let
            fun size (a) = if a < 0 then nil
                    else x = Int.toString x then digit s = size(x)

            fun insert (num, nil) = [num]
                    |       insert (num,xs) = x :: insert ()

            fun convert (a, s) = if s < 0 then nil
                            else insert (a / (10*(s - 1)), xs)
                                    then convert(a - (10*(s - 1), s - 1)

    in

    end

Solution

  • 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