SML中"local"和"let"的区别

Joh*_*han 9 local sml let

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

Sim*_*ine 8

(TL; DR)

  1. 使用case ... of ...时,你只能有一个临时的结合.
  2. 使用let ... in ... end非常具体的辅助功能.
  3. 切勿使用local ... in ... end.请改用不透明模块.

sepp2k的正确答案中添加一些关于用例的想法:

  • (总结) local ... in ... end是声明和let ... in ... end是一个表达式,以便有效地限制在那里它们可以被用于:当声明被允许(例如在顶层或内部的模块),以及值声明内(valfun),分别.

    但那又怎么样?似乎任何一种都可以使用.的Rosetta石头快速排序代码,例如,可以使用任一构造中,由于辅助函数只使用一次:

    (* 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
    
    Run Code Online (Sandbox Code Playgroud)

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

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

    (* 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
    
    Run Code Online (Sandbox Code Playgroud)

    如果没有多个,你可以用一个let ... in ... end代替.

    您可以随时避免使用local ... in ... end有利于不透明模块(见下文).

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

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

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

    1. 每个函数调用计算一次绑定(即使多次使用).
    2. 可以同时解构绑定(进入left和进入right).
    3. 声明的范围有限.(与...相同的论点local ... in ... end.)
    4. 内部函数可以使用外部函数的参数或外部函数本身.
    5. 可以整齐地排列彼此依赖的多个绑定.


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

    当一个辅助函数被使用一次时,你也可以将它嵌入一个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
    
    Run Code Online (Sandbox Code Playgroud)

    这些是等价的.你可能喜欢这种或那种的风格.该case ... of ...有一个好处,虽然是它也为工作和类型('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
    
    Run Code Online (Sandbox Code Playgroud)

    缺点是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
    
    Run Code Online (Sandbox Code Playgroud)

    不过,这不是关于使用的local ... in ... end.

  2. 隐藏在其他地方不会使用的声明是明智的.例如

    (* 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
    
    Run Code Online (Sandbox Code Playgroud)

    等等.基本上,

    1. 如果将重用声明(例如函数),请不要隐藏它.
    2. 如果没有,local ... in ... end结束点let ... in ... end是无效的.
  3. (local ... in ... end没用.)

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

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

    j4cbo在Stilts Web框架上SML中的一个例子是模块StaticServer:它只导出val server : ...,即使结构也包含两个声明structure U = WebUtilval content_type = ....

    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
    
    Run Code Online (Sandbox Code Playgroud)

  • 只是为了增加一点混乱,“let”在标准 ML 的模块语言级别也可用:https://gist.github.com/igstan/a5eee5b03db06e274a0331ab1e019be7。在 SML/NJ 中,“local”也可在模块语言中使用:https://gist.github.com/igstan/d014f0f2737571da69feda323d326052 (2认同)

And*_*erg 5

简短的回答是:local是一个声明,let是一个表达式。因此,它们被用在不同的语法上下文中,并且需要在和local之间进行声明,而需要在其中有一个表达式。没有比这更深的了。inendlet

正如 @SimonShine 提到的,local通常不鼓励使用模块。