SML中“local”和“let”的区别

时间:2016-09-19 16:07:14

标签: local sml let

我找不到一个初学者友好的答案,解决SML中“local”和“let”关键字之间的区别。有人可以提供一个简单的例子,并解释何时使用另一个?

2 个答案:

答案 0 :(得分:6)

(TL; DR)

  1. 只有一个临时绑定时使用case ... of ...
  2. 使用let ... in ... end表示非常具体的帮助函数。
  3. 永远不要使用local ... in ... end。改为使用不透明模块。
  4. 将用例的一些想法添加到sepp2k's fine answer

    • (摘要) local ... in ... end是一个声明,let ... in ... end是一个表达式,因此可以有效地限制它们的使用位置:允许声明的位置(例如在顶级或模块内部)和内部值声明(valfun)。

      但是那又怎样?似乎任何一种都可以使用。例如,Rosetta Stone QuickSort code可以使用其中任何一个来构造,因为辅助函数只使用一次:

      (* First using local ... in ... end *)
      local
          fun par_helper([], x, l, r) = (l, r)
            | par_helper(h::t, x, l, r) =
                if h <= x
                  then par_helper(t, x, l @ [h], r)
                  else par_helper(t, x, l, r @ [h])
      
          fun par(l, x) = par_helper(l, x, [], [])
      in
        fun quicksort [] = []
          | quicksort (h::t) =
              let
                val (left, right) = par(t, h)
              in
                quicksort left @ [h] @ quicksort right
              end
      end
      
      (* Second using let ... in ... end *)
      fun quicksort [] = []
        | quicksort (h::t) =
            let
              fun par_helper([], x, l, r) = (l, r)
                | par_helper(h::t, x, l, r) = 
                    if h <= x
                      then par_helper(t, x, l @ [h], r)
                      else par_helper(t, x, l, r @ [h])
      
              fun par(l, x) = par_helper(l, x, [], [])
      
              val (left, right) = par(t, h)
            in
              quicksort left @ [h] @ quicksort right
            end
      

    因此,让我们关注何时使用其中一种特别有用。

    • local ... in ... end主要用于您在使用后要隐藏的一个或多个临时声明(例如辅助函数),但它们应在 multiple之间共享非本地声明。 E.g。

      (* Helper function shared across multiple functions *)
      local
          fun par_helper ... = ...
      
          fun par(l, x) = par_helper(l, x, [], [])
      in
        fun quicksort [] = []
          | quicksort (h::t) = ... par(t, h) ...
      
        fun median ... = ... par(t, h) ...
      end
      

      如果没有多个,您可以改为使用let ... in ... end

      您始终可以避免使用local ... in ... end来支持 opaque modules (见下文)。

    • let ... in ... end主要用于在函数内部计算临时结果或解构产品类型(元组,记录)的值一次或多次时。 E.g。

      fun quicksort [] = []
        | quicksort (x::xs) =
          let
            val (left, right) = List.partition (fn y => y < x) xs
          in
            quicksort left @ [x] @ quicksort right
          end
      

      以下是let ... in ... end的一些好处:

      1. 每个函数调用一次计算绑定(即使多次使用)。
      2. 可以同时解构绑定(此处为leftright)。
      3. 声明的范围有限。 (与local ... in ... end相同的参数。)
      4. 内部函数可以使用外部函数的参数,也可以使用外部函数本身。
      5. 可以巧妙地排列相互依赖的多个绑定。

      6. 等等......真的,让表达式非常好。

        当使用辅助函数一次时,您也可以将其嵌套在let ... in ... end内。

        特别是如果使用其他原因也适用。

    一些其他意见

    1. case ... of ...太棒了。)

      如果您只有一个let ... in ... end,则可以改为编写。

      fun quicksort [] = []
        | quicksort (x::xs) =
          case List.partition (fn y => y < x) xs of
            (left, right) => quicksort left @ [x] @ quicksort right
      

      这些是等效的。你可能喜欢这种或那种的风格。 case ... of ...有一个优点,即它也适用于sum types'a option'a list等),例如。

      (* Using case ... of ... *)
      fun maxList [] = NONE
        | maxList (x::xs) =
          case maxList xs of
               NONE => SOME x
             | SOME y => SOME (Int.max (x, y))
      
      (* Using let ... in ... end and a helper function *)
      fun maxList [] = NONE
        | maxList (x::xs) =
          let
            val y_opt = maxList xs
          in
            Option.map (fn y => Int.max (x, y)) y_opt
          end
      

      case ... of ...的一个缺点:模式块不会停止,因此嵌套它们通常需要括号。您也可以用不同的方式将两者结合起来,例如

      fun move p1 (GameState old_p) gameMap =
          let val p' = addp p1 old_p in
            case getMapPos p' gameMap of
                Grass => GameState p'
              | _     => GameState old_p
          end
      

      这与使用local ... in ... end 并非如此。

    2. 隐藏不在其他地方使用的声明是明智的。 E.g。

      (* if they're overly specific *)
      fun handvalue hand =
          let
            fun handvalue' [] = 0
              | handvalue' (c::cs) = cardvalue c + handvalue' cs
            val hv = handvalue' hand
          in
            if hv > 21 andalso hasAce hand
            then handvalue (removeAce hand) + 1
            else hv
          end
      
      (* to cover over multiple arguments, e.g. to achieve tail-recursion, *)
      (* or because the inner function has dependencies anyways (here: x). *)
      fun par(ys, x) =
          let fun par_helper([], l, r) = (l, r)
                | par_helper(h::t, l, r) =
                    if h <= x
                      then par_helper(t, l @ [h], r)
                      else par_helper(t, l, r @ [h])
          in par_helper(ys, [], []) end
      

      等等。基本上,

      1. 如果要重复使用声明(例如函数),请不要隐藏它。
      2. 如果没有,则local ... in ... end超过let ... in ... end的点无效。
    3. local ... in ... end没用。)

      您永远不想使用local ... in ... end。由于它的工作是将一组辅助声明隔离到主声明的子集,这迫使您根据它们所依赖的内容对这些主声明进行分组,而不是更理想的顺序。

      更好的选择是简单地编写一个结构,给它一个签名并使该签名不透明。这样,所有内部声明都可以在整个模块中自由使用而无需导出。

      j4cbo的SML on Stilts web-framework中的一个示例是模块StaticServer:它仅导出val server : ...,即使该结构还包含两个声明structure U = WebUtilval content_type = ...。 / p>

      structure StaticServer :> sig
      
        val server: { basepath: string,
                      expires: LargeInt.int option,
                      headers: Web.header list } -> Web.app
      
      end = struct
      
        structure U = WebUtil
      
        val content_type = fn
              "png" => "image/png"
            | "gif" => "image/gif"
            | "jpg" => "image/jpeg"
            | "css" => "text/css"
            | "js" => "text/javascript"
            | "html" => "text/html"
            | _ => "text/plain" 
      
        fun server { basepath, expires, headers } (req: Web.request) = ...
      end
      

答案 1 :(得分:4)

简短的回答是:local是一个声明,let是一个表达式。因此,它们用于不同的语法上下文,local需要inend之间的声明,而let需要在那里表达。它没有那么深刻。

正如@SimonShine所提到的,local经常不赞成使用模块。