“本地"与“本地"之间的区别是:和“让"在SML中 [英] Difference between "local" and "let" in SML
问题描述
对于SML中"local"和"let"关键字之间的区别是什么,我找不到适合初学者的答案.有人可以提供一个简单的例子,并解释一下何时使用另一个例子吗?
I couldn't find a beginner friendly answer to what the difference between the "local" and "let" keywords in SML is. Could someone provide a simple example please and explain when one is used over the other?
推荐答案
(TL; DR)
- 只有一个临时绑定时使用
case ... of ...
. - 将
let ... in ... end
用于非常特定的帮助程序功能. - 请勿使用
local ... in ... end
.请改用不透明的模块.
- Use
case ... of ...
when you only have one temporary binding. - Use
let ... in ... end
for very specific helper functions. - Never use
local ... in ... end
. Use opaque modules instead.
在 sepp2k的很好的答案中添加了一些用例的想法:
-
(摘要)
local ... in ... end
是一个声明,而let ... in ... end
是一个表达式,因此有效地限制了它们的使用位置:允许在何处声明(例如,在顶层)或在模块内部)和内部值声明(val
和fun
).Adding some thoughts on use-cases to sepp2k's fine answer:
(Summary)
local ... in ... end
is a declaration andlet ... in ... end
is an expression, so that effectively limits where they can be used: Where declarations are allowed (e.g. at the top level or inside a module), and inside value declarations (val
andfun
), respectively.那又如何呢?通常似乎两者都可以使用.例如, Rosetta Stone QuickSort代码可以使用任一种结构,因为有帮助者函数仅使用一次:
But so what? It often seems that either can be used. The Rosetta Stone QuickSort code, for example, could be structured using either, since the helper functions are only used once:
(* 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 之间共享非本地声明.例如 local ... in ... end
is mainly used when you have one or more temporary declarations (e.g. helper functions) that you want to hide after they're used, but they should be shared between multiple non-local declarations. 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
.If there weren't multiple, you could have used a
let ... in ... end
instead.您总是可以避免使用
local ... in ... end
来支持不透明的模块(请参见下文).You can always avoid using
local ... in ... end
in favor of opaque modules (see below).let ... in ... end
主要用于要在函数中一次或多次计算临时结果或解构产品类型(元组,记录)的值的情况.例如let ... in ... end
is mainly used when you want to compute temporary results, or deconstruct values of product types (tuples, records), one or more times inside a function. 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
的一些优点:- 每个函数调用(即使多次使用)也会一次计算绑定.
- 绑定可以同时被解构(这里为
left
和right
). - 声明的范围是有限的. (与
local ... in ... end
相同.) - 内部函数可以使用外部函数的参数,也可以使用外部函数本身.
- 相互依赖的多个绑定可以整齐地排列.
- A binding is computed once per function call (even when used multiple times).
- A binding can simultaneously be deconstructed (into
left
andright
here). - The declaration's scope is limited. (Same argument as for
local ... in ... end
.) - Inner functions may use the arguments of the outer function, or the outer function itself.
- Multiple bindings that depend on each other may neatly be lined up.
以此类推...实际上,let表达式非常好.
And so on... Really, let-expressions are quite nice.一次使用辅助功能时,最好将其嵌套在
let ... in ... end
中.When a helper function is used once, you might as well nest it inside a
let ... in ... end
.特别是在使用其他原因的情况下也是如此.
Especially if other reasons for using one applies, too.
-
(
case ... of ...
也很棒.)
(
case ... of ...
is awesome, too.)
只有一个
let ... in ... end
时,您可以写例如When you have only one
let ... in ... end
you can instead write e.g.fun quicksort [] = [] | quicksort (x::xs) = case List.partition (fn y => y < x) xs of (left, right) => quicksort left @ [x] @ quicksort right
这些是等效的.您可能会喜欢其中一种的风格.但是,
case ... of ...
有一个优点,它也适用于 求和类型 ('a option
,'a list
等),例如These are equivalent. You might like the style of one or the other. The
case ... of ...
has one advantage, though, being that it also work for sum types ('a option
,'a list
, etc.), e.g.(* 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 ...
的一个缺点:模式块不会停止,因此嵌套它们通常需要括号.您也可以通过不同的方式将两者结合起来,例如The one disadvantage of
case ... of ...
: The pattern block does not stop, so nesting them often requires parentheses. You can also combine the two in different ways, e.g.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
而不是不是无关.
This isn't so much about not using
local ... in ... end
, though.隐藏在其他地方不会使用的声明是明智的.例如
Hiding declarations that won't be used elsewhere is sensible. 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
以此类推.基本上,
- 如果要重复使用声明(例如函数),请不要隐藏它.
- 如果不是,则
local ... in ... end
在let ... in ... end
上的点为空.
- If a declaration (e.g. function) will be re-used, don't hide it.
- If not, the point of
local ... in ... end
overlet ... in ... end
is void.
-
(
local ... in ... end
没有用.) (
local ... in ... end
is useless.)您永远不想使用
local ... in ... end
.由于它的工作是将一组帮助程序声明隔离到您的主要声明的子集中,因此这迫使您根据它们所依赖的内容(而不是更期望的顺序)对这些主要声明进行分组.You never want to use
local ... in ... end
. Since its job is to isolate one set of helper declarations to a subset of your main declarations, this forces you to group those main declarations according to what they depend on, rather than perhaps a more desired order.一个更好的选择是简单地编写一个结构,给它一个签名并使该签名不透明.这样,所有内部声明都可以在整个模块中自由使用,而无需导出.
A better alternative is simply to write a structure, give it a signature and make that signature opaque. That way, all internal declarations can be used freely throughout the module without being exported.
在j4cbo的 Stilts网站上的SML上,此示例framework 是StaticServer模块:即使结构同时包含两个声明
structure U = WebUtil
和val content_type = ...
,它也仅导出val server : ...
.One example of this in j4cbo's SML on Stilts web-framework is the module StaticServer: It exports only
val server : ...
, even though the structure also holds the two declarationsstructure U = WebUtil
andval 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
这篇关于“本地"与“本地"之间的区别是:和“让"在SML中的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!