Jon*_*her 9 logic type-theory formal-semantics move-semantics rust
有人告诉我 Rust 在仿射逻辑中具有语义——所以有删除/弱化但没有重复/收缩。
编译如下:
fn throw_away<A, B>(x: A, _y: B) -> A {
x
}
Run Code Online (Sandbox Code Playgroud)
由于不允许重复,因此无法编译以下内容:
fn dup<A>(x: A) -> (A, A) {
(x, x)
}
Run Code Online (Sandbox Code Playgroud)
同样,这些都不编译:
fn throw_away3<A, B>(x: A, f: fn(A) -> B) -> A {
x;
f(x)
}
fn throw_away4<A, B>(x: A, f: fn(A) -> B) -> A {
throw_away(x, f(x))
}
Run Code Online (Sandbox Code Playgroud)
弱化也是有目共睹的
fn weaken<A, B, C>(f: fn(A) -> B) -> impl Fn(A, C) -> B {
move |x: A, y: C| f(x)
}
Run Code Online (Sandbox Code Playgroud)
fn(A, C) -> B我们没有返回,而是返回了impl Fn(A, C) -> B。有没有办法返回fn(A, C) -> B?没有也没关系;我只是好奇。
我期望的另一件事是您可以提升A到() -> A. 然而,Rust 中的函数可以被复制和使用多次。例如,
fn app_twice(f: fn(A) -> A, x: A) -> A {
f(f(x))
}
Run Code Online (Sandbox Code Playgroud)
假设实际上有一个函数lift(x: A) -> fn() -> A,那么我们可以打破移动语义。例如,这将允许
fn dup_allowed(x: A) -> (A, A) {
let h = lift(x);
(h(), h())
}
Run Code Online (Sandbox Code Playgroud)
因此要提升A到fn() -> A,我们需要知道该函数是“线性/仿射”或只能使用一次。Rust 为此提供了一种类型:FnOnce() -> A. 在下面,第一个编译,第二个不编译。
fn app_once(f: impl FnOnce(A) -> A, x: A) -> A {
f(x)
}
fn app_twice2(f: impl FnOnce(A) -> A, x: A) -> A {
f(f(x))
}
Run Code Online (Sandbox Code Playgroud)
以下函数是互逆的(可能,我不太了解 Rust 的语义,无法说它们实际上互逆):
fn lift_up<A>(x: A) -> impl FnOnce() -> A {
move || x
}
fn lift_up_r<A>(f: impl FnOnce() -> A) -> A {
f()
}
Run Code Online (Sandbox Code Playgroud)
由于fn dup<A>(x: A) -> (A, A) { (x,x) }不编译,我认为以下可能是一个问题:
fn dup<A>(x: fn() -> A) -> (A, A) {
(x(), x())
}
Run Code Online (Sandbox Code Playgroud)
似乎 Rust 正在为fn(A) -> B类型做一些特别的事情。
为什么我不必在上面声明 x 是可重用/可复制的?
也许正在发生一些不同的事情。声明的函数有点特殊fn f(x: A) -> B { ... }是一个特殊的见证A -> B。因此,如果f需要多次使用,可以根据需要多次责备,但这fn(A) -> B是完全不同的东西:它不是构建的东西而是假设的东西,并且必须使用fn(A) -> B可复制的东西。事实上,我一直认为它更像是一个可以自由复制的实体。这是我粗略的类比:
fn my_fun<A,B>(x :A) -> B { M } "是" x:A |- M:Bfn(A) -> B "is" !(A -o B) 因此可以自由复制fn() -> A"is" !(() -o A) = !A 因此fn () -> A是A上的 (co)free 重复fn dup_arg<A: Copy>(x: A) -> B { M } “说”A 有重复或者是一个 comonoidimpl FnOnce (A) -> B “是”A -o B但这不可能是对的......为了什么impl Fn(A) -> B?从玩了一下,似乎fn(A) -> B比Fn(A) -> B. 我错过了什么?
Run Code Online (Sandbox Code Playgroud)fn weaken<A, B, C>(f: fn(A) -> B) -> impl Fn(A, C) -> B { move |x: A, y: C| f(x) }
fn(A, C) -> B,我们没有返回,而是返回impl Fn(A, C) -> B。有没有办法返回fn(A, C) -> B?没有也没关系;我只是好奇。
不,因为 afn根据定义不是闭包:它不能包含任何未编译到程序中的状态(在这种情况下,是 的值f)。这与你的下一个观察密切相关:因为afn不能关闭任何东西,它几乎不能包含任何非Copy类型,因此总是可以多次调用,或者自身被复制,而不会违反我们正在讨论的属性。
准确地说:所有fn(..) -> _类型都实现Fn和Copy(以及FnOnce)。
Copy是标记特性('marker' 意味着它不提供任何方法),它的特殊目的是告诉编译器,只要它被多次使用,它就可以自动复制类型的位。任何实现Copy都选择退出移动但不复制系统——但不能因此违反不同类型的非复制性。Fn是可以通过不可变引用调用的函数的特征(不修改或消耗函数本身)。这原则上与 分开Copy,但实际上非常相似;人们最终可能会遇到的差异(其中一些不能在普通代码中发生)是:
Fn但不是Copyor Clone,那么您不能将该函数存储在多个位置,但您可以根据需要多次调用它。Copy但不是Fn(仅FnOnce),那么这是不可见的,因为它的每次调用(除了最后一次)都隐式复制它。Clone但没有实现Fnor Copy,那么.clone()每次调用它时都必须使用它(最后一次除外)。事实上,以下函数是互逆的(可能,我不太了解 rust 的语义,无法说它们实际上是互逆的):
Run Code Online (Sandbox Code Playgroud)fn lift_up<A> (x:A) -> impl FnOnce () -> A {move | | x} fn lift_up_r<A> (f : impl FnOnce () -> A) -> A {f()}
lift_up_r接受lift_up没有产生的函数;例如,如果f有副作用、恐慌或挂起,则let f = lift_up(lift_up_r(f));具有该效果。忽略这一点,它们是相反的。更好的一对反函数是将值移入 astruct并移出的函数——这是有效的,除了允许不属于该特定结构类型的输入。
自从
fn dup (x:A) -> (A,A) {(x,x)}不编译,我认为以下可能是一个问题:Run Code Online (Sandbox Code Playgroud)fn dup<A> (x : fn() -> A) -> (A,A) {(x(),x()}但似乎 rust 对 fn(A) -> B 类型做了一些特别的事情。最后,我的问题是:为什么我不必在上面声明 x 是可重用/可复制的?
当您有一个带有类型变量 的泛型函数时fn dup<A>,编译器不会对 的属性做任何假设A(Sized除非您选择退出隐式绑定,因为使用非Sized值是高度限制性的,通常不是您想要的)。特别是,它不假设A实现Copy.
另一方面,正如我上面提到的,所有fn类型都实现Fnand Copy,所以它们总是可以被复制和重用。
编写一个dup对通用函数进行操作但无法以您期望的方式编译的函数的方法是:
fn dup<A, F>(x: F) -> (A,A)
where
F: FnOnce() -> A
{
(x(),x())
}
Run Code Online (Sandbox Code Playgroud)
在这里,我们告诉编译器这F是一种通过调用它来消耗的函数,而不是告诉它任何复制F. 因此,它无法编译为“错误[E0382]:使用移动的值:x”。进行此编译的最短方法是添加 bound F: Copy,最通用的方法是添加F: Clone和显式.clone()调用。
也许正在发生一些不同的事情。声明的函数有点特殊 fn f(x:A) -> B {...} 是 A -> B 的特定见证。因此如果 f 需要多次使用,可以根据需要多次谴责. 但是 fn(A) -> B 是完全不同的东西:它不是构造的东西而是假设的东西,并且必须使用 a fn(A) -> Bs 是可复制的。事实上,我一直认为它更像是一个可以自由复制的实体。
我不是逻辑学家,但我认为前半部分是不正确的。特别是,(除了一些与泛型无关的考虑之外)没有“声明的函数”具有类型的任意值fn(A) -> B不具有的属性。相反, type 的值fn(A) -> B 可以被复制,而这种可复制性直接对应于“它可以被责备”这一事实,因为(直到我们开始引入 JIT 代码生成之类的想法之前)类型的每个值都fn(A) -> B指代一段已编译的代码(并且没有其他数据)——因此编译器已经检查并授予程序许可以根据需要多次重用它的引理在运行时。
什么是 impl Fn(A) -> B?从玩了一下,似乎 fn(A) -> B 比 Fn(A) -> B 更严格。我错过了什么?
该impl语法提供不同的角色,但在论证位置是几乎完全仿制药的简写。如果我写
fn foo<A, B>(f: impl Fn(A) -> B) {}
Run Code Online (Sandbox Code Playgroud)
那么这相当于
fn foo<A, B, F>(f: F)
where
F: Fn(A) -> B
{}
Run Code Online (Sandbox Code Playgroud)
除了当impl存在任何参数类型时不允许调用者指定任何参数(这与您的兴趣无关,但为了准确起见,我提到了它)。因此,我们告诉编译器F可以是任何东西,只要它可以作为可重用的函数调用。特别是,我们没有指定F: Copy或F: Clone。fn(A) -> B,另一方面,是一个具体的类型,它实现Fn(A) -> B 和 Copy,因此您可以免费获得它。
在返回位置fn ... -> impl Fn(A) -> B,则impl表示存在类型:你声称存在着某种类型实现Fn该函数将返回。编译器跟踪具体类型以生成代码,但您的程序避免命名它。这在返回闭包时是必需的,但在返回不关闭任何内容的函数时是可选的:例如,您可以编写
fn foo<A>() -> fn(A) -> A {
|x| x
}
Run Code Online (Sandbox Code Playgroud)