为什么我不必声明 x 是 reusable/duplicable 具有仿射语义和函数类型?
Why don't I have to declare that x is reusable/duplicable with affine semantics and function types?
有人告诉我 Rust 在仿射逻辑中有语义——所以有 deletion/weakening 但没有 duplication/contraction。
编译如下:
fn throw_away<A, B>(x: A, _y: B) -> A {
x
}
因为不允许复制,所以下面的不编译:
fn dup<A>(x: A) -> (A, A) {
(x, x)
}
同样,这些都不编译:
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))
}
削弱也有目共睹
fn weaken<A, B, C>(f: fn(A) -> B) -> impl Fn(A, C) -> B {
move |x: A, y: C| f(x)
}
我们 return 编辑了 impl Fn(A, C) -> B
,而不是 return 编辑了 fn(A, C) -> B
。有没有办法 return fn(A, C) -> B
呢?没有也没关系;我只是好奇。
我还希望您可以将 A
提升到 () -> A
。然而,Rust 中的函数可以被复制和使用不止一次。例如,
fn app_twice(f: fn(A) -> A, x: A) -> A {
f(f(x))
}
假设实际上有一个函数lift(x: A) -> fn() -> A
,那么我们可以打破移动语义。例如,这将允许
fn dup_allowed(x: A) -> (A, A) {
let h = lift(x);
(h(), h())
}
因此要将A
提升到fn() -> A
,我们需要知道函数是“linear/affine”或者只能使用一次。 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))
}
以下函数互为逆函数(可能我对Rust的语义了解不够深,无法说它们实际上是互逆的):
fn lift_up<A>(x: A) -> impl FnOnce() -> A {
move || x
}
fn lift_up_r<A>(f: impl FnOnce() -> A) -> A {
f()
}
由于fn dup<A>(x: A) -> (A, A) { (x,x) }
编译不通过,我认为可能是以下问题:
fn dup<A>(x: fn() -> A) -> (A, A) {
(x(), x())
}
Rust 似乎正在为 fn(A) -> B
类型做一些特别的事情。
为什么我不用在上面声明x是reusable/duplicable?
也许发生了一些不同的事情。声明的函数有点特殊 fn f(x: A) -> B { ... }
是 A -> B
的特殊见证。因此,如果f
需要多次使用,可以根据需要多次反驳,但是fn(A) -> B
是完全不同的东西:它不是构造的东西而是假设的东西,必须是使用 fn(A) -> B
s 是可复制的。事实上,我一直认为它更像是一个可以自由复制的实体。这是我的粗略类比:
fn my_fun<A,B>(x :A) -> B { M }
“是” x:A |- M:B
fn(A) -> B
“是”!(A -o B) 因此可自由复制
- 因此
fn() -> A
“是” !(() -o A) = !A 因此 fn () -> A
是 A 上的 (co)free 复制
fn dup_arg<A: Copy>(x: A) -> B { M }
“说”A 有重复或者是一个类群
impl FnOnce (A) -> B
“是” A -o B
但这不可能是对的... impl Fn(A) -> B
是什么?玩了一下,似乎 fn(A) -> B
比 Fn(A) -> B
更严格。我错过了什么?
fn weaken<A, B, C>(f: fn(A) -> B) -> impl Fn(A, C) -> B {
move |x: A, y: C| f(x)
}
Instead of returning fn(A, C) -> B,
we returned impl Fn(A, C) -> B
. Is there a way to return fn(A, C) -> B
instead? It's fine if not; I'm just curious.
不,因为 fn
根据定义不是闭包:它不能包含任何未编译到程序中的状态(在这种情况下,f
的值)。这与您的下一个观察密切相关:因为 a fn
不能关闭任何东西,它通常不能包含任何非 Copy
类型,因此总是可以调用多次,或本身被复制,而不会违反我们正在讨论的属性。
准确地说:所有 fn(..) -> _
类型都实现了 Fn
和 Copy
(以及 FnOnce
)。
Copy
是 marker trait('marker' 意味着它不提供任何方法),它的特殊目的是告诉编译器它可以自由地多次使用时自动复制类型的位。任何实现 Copy
的东西都选择退出移动但不复制系统——但不能因此违反 不同 类型的非复制性。
Fn
是可以通过不可变引用调用的函数的特征(不修改或使用函数本身)。这在原则上与 Copy
是分开的,但实际上非常相似;最终可能出现的差异(其中一些在普通代码中不会发生)是:
- 如果一个函数实现了
Fn
而不是 Copy
或 Clone
,那么你不能 store 这个函数的多个地方但是你想调用多少次都可以。
- 如果一个函数实现了
Copy
而不是 Fn
(仅 FnOnce
),那么这是不可见的,因为它的每次调用(除了最后一次)都会隐式复制它。
- 如果一个函数实现了
Clone
而不是 Fn
或 Copy
,那么你每次调用它时都必须 .clone()
它(最后一次除外)。
And indeed the following functions are inverses of eachother (probably, I don't know rust's semantics well enough to say that they are actually inverse to each other):
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));
就会产生这种效果。忽略这一点,它们是倒数。没有那个警告的一对更好的逆将是将一个值移动到 struct
并退出的函数——这是有效的,除了允许不属于该特定结构类型的输入。
Since fn dup (x:A) -> (A,A) {(x,x)}
does not compile, I thought that the following might be a problem:
fn dup<A> (x : fn() -> A) -> (A,A) {(x(),x()}
But it seems that rust is doing something special for fn(A) -> B types. Finally, my question: why don't I have to declare that x is reusable/duplicable in the above?
当你有一个带有类型变量 fn dup<A>
的泛型函数时,编译器不会对 A
的属性做出任何假设(除了它是 Sized
除非你选择退出该隐式绑定,因为使用非 Sized
值是高度限制性的,通常不是您想要的)。特别是,它不假设 A
实现 Copy
.
另一方面,正如我上面提到的,所有 fn
类型都实现了 Fn
和 Copy
,因此它们总是可以被复制和重用。
编写对一般函数进行操作并且无法按您期望的方式编译的dup
函数的方法是:
fn dup<A, F>(x: F) -> (A,A)
where
F: FnOnce() -> A
{
(x(),x())
}
在这里,我们告诉编译器F
是一种函数类型,通过调用它来消耗它,而不告诉它任何复制F
的方法。因此,它无法通过“错误[E0382]:使用移动值:x
”进行编译。进行此编译的最短方法是添加绑定 F: Copy
,最通用的方法是添加 F: Clone
和显式 .clone()
调用。
Perhaps something different is going on. Declared functions are a bit special fn f(x:A) -> B {...} is a particular witness that A -> B. Thus if f needs to be used multiple times, it can be reproved as many times as needed. But fn(A) -> B is a completely different thing: it is not a constructed thing but a hypothetical thing, and must be using a that fn(A) -> Bs are duplicatable. In fact, I've been thinking that it's more like a freely duplicable entity.
我不是逻辑学家,但我认为前半部分不正确。特别是,(除了关于泛型的一些不相关的考虑之外)没有“声明的函数”具有 fn(A) -> B
类型的任意值所没有的属性。相反,类型 fn(A) -> B
的值可以被复制 ,并且可复制性直接对应于“它可以被反驳”这一事实,因为(直到我们开始引入像 JIT 这样的想法代码生成)每个 value 类型 fn(A) -> B
指的是一段 编译代码 (没有其他数据)——因此引理,编译器已检查并授予程序许可,可以根据需要多次重用它 在 运行 时间。
For what is impl Fn(A) -> B? From playing around a bit, it seems that fn(A) -> B is more strict than Fn(A) -> B. What am I missing?
impl
语法有不同的作用,但在参数位置上它几乎完全是泛型的 shorthand。如果我写
fn foo<A, B>(f: impl Fn(A) -> B) {}
那么相当于
fn foo<A, B, F>(f: F)
where
F: Fn(A) -> B
{}
除了当任何 impl
参数类型存在时不允许调用者指定任何参数(这与您的兴趣无关,但我提到它是为了准确)。因此,我们告诉编译器 F
可以是 任何东西 只要它可以作为可重用函数调用。特别是,我们没有指定 F: Copy
或 F: Clone
。另一方面,fn(A) -> B
是实现了 Fn(A) -> B
和 Copy
的具体类型,因此您可以免费获得它。
在return位置,fn ... -> impl Fn(A) -> B
,impl
表示一个存在类型:你断言存在某种实现 Fn
的类型,该函数将 return。编译器跟踪具体类型以生成代码,但您的程序避免命名它。这在 returning 一个闭包时是必需的,但在 returning 一个不关闭任何东西的函数时是可选的:例如,你可以写
fn foo<A>() -> fn(A) -> A {
|x| x
}
有人告诉我 Rust 在仿射逻辑中有语义——所以有 deletion/weakening 但没有 duplication/contraction。
编译如下:
fn throw_away<A, B>(x: A, _y: B) -> A {
x
}
因为不允许复制,所以下面的不编译:
fn dup<A>(x: A) -> (A, A) {
(x, x)
}
同样,这些都不编译:
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))
}
削弱也有目共睹
fn weaken<A, B, C>(f: fn(A) -> B) -> impl Fn(A, C) -> B {
move |x: A, y: C| f(x)
}
我们 return 编辑了 impl Fn(A, C) -> B
,而不是 return 编辑了 fn(A, C) -> B
。有没有办法 return fn(A, C) -> B
呢?没有也没关系;我只是好奇。
我还希望您可以将 A
提升到 () -> A
。然而,Rust 中的函数可以被复制和使用不止一次。例如,
fn app_twice(f: fn(A) -> A, x: A) -> A {
f(f(x))
}
假设实际上有一个函数lift(x: A) -> fn() -> A
,那么我们可以打破移动语义。例如,这将允许
fn dup_allowed(x: A) -> (A, A) {
let h = lift(x);
(h(), h())
}
因此要将A
提升到fn() -> A
,我们需要知道函数是“linear/affine”或者只能使用一次。 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))
}
以下函数互为逆函数(可能我对Rust的语义了解不够深,无法说它们实际上是互逆的):
fn lift_up<A>(x: A) -> impl FnOnce() -> A {
move || x
}
fn lift_up_r<A>(f: impl FnOnce() -> A) -> A {
f()
}
由于fn dup<A>(x: A) -> (A, A) { (x,x) }
编译不通过,我认为可能是以下问题:
fn dup<A>(x: fn() -> A) -> (A, A) {
(x(), x())
}
Rust 似乎正在为 fn(A) -> B
类型做一些特别的事情。
为什么我不用在上面声明x是reusable/duplicable?
也许发生了一些不同的事情。声明的函数有点特殊 fn f(x: A) -> B { ... }
是 A -> B
的特殊见证。因此,如果f
需要多次使用,可以根据需要多次反驳,但是fn(A) -> B
是完全不同的东西:它不是构造的东西而是假设的东西,必须是使用 fn(A) -> B
s 是可复制的。事实上,我一直认为它更像是一个可以自由复制的实体。这是我的粗略类比:
fn my_fun<A,B>(x :A) -> B { M }
“是” x:A |- M:Bfn(A) -> B
“是”!(A -o B) 因此可自由复制- 因此
fn() -> A
“是” !(() -o A) = !A 因此fn () -> A
是 A 上的 (co)free 复制
fn dup_arg<A: Copy>(x: A) -> B { M }
“说”A 有重复或者是一个类群impl FnOnce (A) -> B
“是” A -o B
但这不可能是对的... impl Fn(A) -> B
是什么?玩了一下,似乎 fn(A) -> B
比 Fn(A) -> B
更严格。我错过了什么?
fn weaken<A, B, C>(f: fn(A) -> B) -> impl Fn(A, C) -> B { move |x: A, y: C| f(x) }
Instead of returning
fn(A, C) -> B,
we returnedimpl Fn(A, C) -> B
. Is there a way to returnfn(A, C) -> B
instead? It's fine if not; I'm just curious.
不,因为 fn
根据定义不是闭包:它不能包含任何未编译到程序中的状态(在这种情况下,f
的值)。这与您的下一个观察密切相关:因为 a fn
不能关闭任何东西,它通常不能包含任何非 Copy
类型,因此总是可以调用多次,或本身被复制,而不会违反我们正在讨论的属性。
准确地说:所有 fn(..) -> _
类型都实现了 Fn
和 Copy
(以及 FnOnce
)。
Copy
是 marker trait('marker' 意味着它不提供任何方法),它的特殊目的是告诉编译器它可以自由地多次使用时自动复制类型的位。任何实现Copy
的东西都选择退出移动但不复制系统——但不能因此违反 不同 类型的非复制性。Fn
是可以通过不可变引用调用的函数的特征(不修改或使用函数本身)。这在原则上与Copy
是分开的,但实际上非常相似;最终可能出现的差异(其中一些在普通代码中不会发生)是:- 如果一个函数实现了
Fn
而不是Copy
或Clone
,那么你不能 store 这个函数的多个地方但是你想调用多少次都可以。 - 如果一个函数实现了
Copy
而不是Fn
(仅FnOnce
),那么这是不可见的,因为它的每次调用(除了最后一次)都会隐式复制它。 - 如果一个函数实现了
Clone
而不是Fn
或Copy
,那么你每次调用它时都必须.clone()
它(最后一次除外)。
- 如果一个函数实现了
And indeed the following functions are inverses of eachother (probably, I don't know rust's semantics well enough to say that they are actually inverse to each other):
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));
就会产生这种效果。忽略这一点,它们是倒数。没有那个警告的一对更好的逆将是将一个值移动到 struct
并退出的函数——这是有效的,除了允许不属于该特定结构类型的输入。
Since
fn dup (x:A) -> (A,A) {(x,x)}
does not compile, I thought that the following might be a problem:fn dup<A> (x : fn() -> A) -> (A,A) {(x(),x()}
But it seems that rust is doing something special for fn(A) -> B types. Finally, my question: why don't I have to declare that x is reusable/duplicable in the above?
当你有一个带有类型变量 fn dup<A>
的泛型函数时,编译器不会对 A
的属性做出任何假设(除了它是 Sized
除非你选择退出该隐式绑定,因为使用非 Sized
值是高度限制性的,通常不是您想要的)。特别是,它不假设 A
实现 Copy
.
另一方面,正如我上面提到的,所有 fn
类型都实现了 Fn
和 Copy
,因此它们总是可以被复制和重用。
编写对一般函数进行操作并且无法按您期望的方式编译的dup
函数的方法是:
fn dup<A, F>(x: F) -> (A,A)
where
F: FnOnce() -> A
{
(x(),x())
}
在这里,我们告诉编译器F
是一种函数类型,通过调用它来消耗它,而不告诉它任何复制F
的方法。因此,它无法通过“错误[E0382]:使用移动值:x
”进行编译。进行此编译的最短方法是添加绑定 F: Copy
,最通用的方法是添加 F: Clone
和显式 .clone()
调用。
Perhaps something different is going on. Declared functions are a bit special fn f(x:A) -> B {...} is a particular witness that A -> B. Thus if f needs to be used multiple times, it can be reproved as many times as needed. But fn(A) -> B is a completely different thing: it is not a constructed thing but a hypothetical thing, and must be using a that fn(A) -> Bs are duplicatable. In fact, I've been thinking that it's more like a freely duplicable entity.
我不是逻辑学家,但我认为前半部分不正确。特别是,(除了关于泛型的一些不相关的考虑之外)没有“声明的函数”具有 fn(A) -> B
类型的任意值所没有的属性。相反,类型 fn(A) -> B
的值可以被复制 ,并且可复制性直接对应于“它可以被反驳”这一事实,因为(直到我们开始引入像 JIT 这样的想法代码生成)每个 value 类型 fn(A) -> B
指的是一段 编译代码 (没有其他数据)——因此引理,编译器已检查并授予程序许可,可以根据需要多次重用它 在 运行 时间。
For what is impl Fn(A) -> B? From playing around a bit, it seems that fn(A) -> B is more strict than Fn(A) -> B. What am I missing?
impl
语法有不同的作用,但在参数位置上它几乎完全是泛型的 shorthand。如果我写
fn foo<A, B>(f: impl Fn(A) -> B) {}
那么相当于
fn foo<A, B, F>(f: F)
where
F: Fn(A) -> B
{}
除了当任何 impl
参数类型存在时不允许调用者指定任何参数(这与您的兴趣无关,但我提到它是为了准确)。因此,我们告诉编译器 F
可以是 任何东西 只要它可以作为可重用函数调用。特别是,我们没有指定 F: Copy
或 F: Clone
。另一方面,fn(A) -> B
是实现了 Fn(A) -> B
和 Copy
的具体类型,因此您可以免费获得它。
在return位置,fn ... -> impl Fn(A) -> B
,impl
表示一个存在类型:你断言存在某种实现 Fn
的类型,该函数将 return。编译器跟踪具体类型以生成代码,但您的程序避免命名它。这在 returning 一个闭包时是必需的,但在 returning 一个不关闭任何东西的函数时是可选的:例如,你可以写
fn foo<A>() -> fn(A) -> A {
|x| x
}