我对SML
语言有疑问。我了解到SML
确实有变量,但它们没有变化。当值和变量有界时,它们将永久存在并且无法更改。我的问题是它在内存中又如何“在幕后”工作。例如,如果我运行以下程序:
val a = 5;
val a = a + 1;
解释器是否会在内存中为'new'a
分配一个新位置?
这个想法有什么好处?感觉比C
这样的PL效率低。
答案 0 :(得分:0)
变量在SML中确实有所不同,就像变量在数学中一样。也就是说,它们在不同情况下通常代表不同的价值。例如,
fun f x =
let val a = x + 1 in ... end
此处,a
的值随x
的不同而不同。这是数学中“变量”一词的本义。
您不能做的是变异一个变量。在给定范围内,相同变量将始终代表相同值。将不可变性设置为默认值有很多优点,例如,它可以防止整个类的错误,并且对程序进行推理要容易得多,尤其是在涉及嵌套函数时。
您可以仍然有突变,但是在ML中,这是一个独立的概念,更明确。您需要使用参考:
let
val r = ref 1
in
f (!r);
r := 2;
f (!r)
end
最后,您的示例演示了一种完全不同的现象,即声明的 shadowing 。那不是ML独有的,您的程序类似于C中的以下内容:
{
const int a = 5;
{
const int a = a + 1;
}
}
唯一真正的区别是,在C中,在第二个声明的右侧使用a
使其递归地引用(尚未定义)第二个a
,而在SML声明中是默认情况下是非递归的,因此右侧的a
引用第一个声明。
因为它会使程序对人类读者更加混乱,所以不建议在两种语言中都不要在程序中使用阴影。您始终可以通过重命名变量之一来避免这种情况。
答案 1 :(得分:0)
val a = 5 val a = a + 1
解释器是否会在内存中为'new'
a
分配一个新位置?
是的。
SML有两件事,值绑定和可变引用(因为变量在例如C中)。
您描述的是值绑定,它们的值不能在同一范围内变化。
此处a
的值不会变化,它被另一个a
阴影遮盖,并使用旧的{{ 1}}隐藏。因此,如果您使用旧的a
和 shadow a
,则旧的a
的用法不会改变。更具说明性的示例:
a
这个想法有什么好处?
您的示例中有两个想法(值绑定和 shadowing ),所以我想确保回答正确的问题:
值绑定而不是可变引用意味着命名值在生命周期中不会发生变化。这很好,因为这样就可以更轻松地进行代码推理了。要查找绑定的值,可以转到其定义,然后您将知道该绑定从那时以来从未更改过值。
您可以通过向函数输入值来使值绑定有所不同。您可以将其视为数学意义上的变量,而不是可变引用。因此,它们仅在作为函数的输入参数的上下文中有所不同。例如,
val a = 5
fun f x = x + a
val a = 10
val b = f 1 (* = 6, not 11 *)
此处fun factorial 0 = 1
| factorial n = n * factorial (n - 1)
是一个值绑定和一个变量,但不是可变引用。
Shadowing 意味着命名值可以被相同名称的其他命名值隐藏。这并不是一个好功能,因为它可能会引起混乱。就像为事物命名很重要一样,不要给两个名称相同的事物同样重要。
许多功能性编程语言都不允许 shadowing 。
例如,在Erlang中,这是一个错误:
n
在Haskell中,以下是错误:
f(X) ->
A = 5,
A = 6,
X + A.
SML确实具有变量,但它们没有变化。
对,因此值绑定正确,这是SML中最常见的值。 “不变变量”可能会引起混淆,因为“变量”可能意味着“可变参考”和“功能参数”。
同时具有功能性和命令性语言功能的SML也确实具有可变引用:
f :: Int -> Int
f x = x + a
where
a = 5
a = 6
此处val a = ref 5
fun f x = x + !a
val _ = a := 10
val b = f 1 (* 11, not 6 *)
创建引用,ref : 'a -> 'a ref
取消引用。
这些危险与其他语言一样危险;可能更少,因为您不会偶然混合使用! : 'a ref -> 'a
和'a
,因为类型系统会引发错误。从语法上讲,它们有点不切实际,但这应该提醒您,请警惕其用法。 :-)