首页 文章

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

提问于
浏览
5

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

2 回答

  • 5

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

    正如@SimonShine所提到的, local 经常被劝阻使用模块 .

  • 4

    (TL; DR)

    • 当您只有一个临时绑定时使用 case ... of ... .

    • 使用 let ... in ... end 用于非常特定的辅助函数 .

    • 永远不要使用 local ... in ... end . 请改用不透明模块 .

    添加一些关于用例的想法到sepp2k的答案:

    • (摘要) 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 主要用于您在使用后要隐藏的一个或多个临时声明(例如辅助函数),但它们应在多个非本地声明之间共享 . 例如 .
    (* 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 来支持不透明模块(参见下文) .

    • 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
    

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

    • 每个函数调用计算一次绑定(即使多次使用) .

    • 可以同时解构绑定(此处为 leftright ) .

    • 声明的范围有限 . (与 local ... in ... end 相同的参数 . )

    • 内部函数可以使用外部函数的参数或外部函数本身 .

    • 可以整齐地排列相互依赖的多个绑定 .

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

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

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

    一些其他意见

    • 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 .

    • 隐藏在其他地方不会使用的声明是明智的 . 例如 .
    (* 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
    

    等等 . 基本上,

    • 如果要重复使用声明(例如函数),请不要隐藏它 .

    • 如果不是,则 local ... in ... end 超过 let ... in ... end 的点无效 .

    • local ... in ... end 没用 . )

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

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

    j4cbo的SML on Stilts web-framework中的一个示例是模块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
    

相关问题