我正在尝试弄清楚移动语义是如何影响引用透明性的。
引用透明性(RT)允许我们将任何表达式替换为其结果,而不改变程序的含义(摘自Scala函数式编程)。例如,我可以在程序中的任何位置将1+1
替换为2
,而什么都不应该改变。这个Python程序具有引用透明性:
@dataclass
class Bucket:
things: List[str]
leaves = ["leaves"]
def bucket_with_sand(things: List[str]) -> Bucket:
return Bucket(things + ["sand"])
bucket_with_sand(leaves) # can be replaced with Bucket(["leaves", "sand"]) with no change to the program
而这个函数会直接在原参数上进行修改。
def bucket_with_sand(things: List[str]) -> Bucket:
things += ["sand"]
return Bucket(things)
所以将函数调用替换为其结果将改变其含义。它不再具有引用透明性。在像Rust的移动语义这样的语言中,我们可以通过移动leaves
(并依赖于Vec
不是Copy
)来避免此问题:
struct Bucket {
things: Vec<&str>,
}
let leaves = vec!["leaves"];
fn bucket_with_sand(things: Vec<&str>) -> Bucket {
things.push("sand");
Bucket { things }
}
bucket_with_sand(leaves); // mutates `things`
// doesn't matter that `leaves` has been mutated here as it's now out of scope
这似乎再次表现为引用透明。这是正确的吗?这样的移动是否放松了关于RT设计的传统限制?还是这些移动不是引用透明的?我特别想知道是否有更广泛的RT影响我没有看到。