什么是类型状态?

发布于 2024-09-08 22:18:37 字数 64 浏览 9 评论 0原文

TypeState对于语言设计来说指的是什么?我在一些关于 mozilla 的新语言 Rust 的讨论中看到了它。

What does TypeState refer to in respect to language design? I saw it mentioned in some discussions regarding a new language by mozilla called Rust.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

夜无邪 2024-09-15 22:18:37

注意: Typestate 已从 Rust 中删除,仅留下有限版本(跟踪未初始化的和从变量移动的)。请参阅最后我的注释。

TypeState 背后的动机是类型是不可变的,但是它们的某些属性是动态的(基于每个变量)。

因此,我们的想法是创建关于类型的简单谓词,并使用编译器出于许多其他原因执行的控制流分析,以使用这些谓词静态修饰类型。

这些谓词实际上并没有由编译器本身检查,这可能太繁重,相反编译器会简单地根据图形进行推理。

作为一个简单的示例,您创建一个谓词 even,如果数字为偶数,则返回 true

现在,您创建两个函数:

  • halve,它仅作用于 even 数字
  • double,它接受任何数字,并返回 even 号码。

请注意,number 类型未更改,您不会创建 evennumber 类型并复制之前作用于 number 的所有函数。您只需将 number 与名为 even 的谓词组合起来即可。

现在,让我们构建一些图表:

a: number -> halve(a)  #! error: `a` is not `even`

a: number, even -> halve(a)  # ok

a: number -> b = double(a) -> b: number, even

很​​简单,不是吗?

当然,当您有多个可能的路径时,它会变得有点复杂:

a: number -> a = double(a) -> a: number, even -> halve(a) #! error: `a` is not `even`
          \___________________________________/

这表明您根据谓词集进行推理:

  • 当连接两条路径时,新的谓词集是这两条路径给出的谓词集的交集

这可以通过函数的通用规则来增强:

  • 要调用函数,调用函数后必须满足其所需的谓词集
  • ,只有它建立的谓词集才被满足(注意:按值获取的参数不被满足)因此

,Rust 中的 TypeState 构建块:

  • check:检查谓词是否成立,如果它没有失败< /code>,否则将谓词添加到谓词集中

请注意,由于 Rust 要求谓词是纯函数,因此如果可以证明谓词有效,则可以消除冗余的 check 调用此时已经成立。


Typestate 缺乏的很简单:可组合性。

如果你仔细阅读说明,你会注意到这一点:

  • 调用函数后,仅满足其建立的谓词集(注意:按值获取的参数不受影响)

这意味着类型的谓词本身是无用的,实用程序来自注释函数。因此,在现有代码库中引入新谓词是一件无聊的事情,因为需要审查和调整现有函数以迎合解释它们是否需要/保留不变量。

当新的谓词出现时,这可能会导致函数以指数速度重复:不幸的是,谓词是不可组合的。他们本来要解决的设计问题(类型的激增,因此功能的激增)似乎没有得到解决。

Note: Typestate was dropped from Rust, only a limited version (tracking uninitialized and moved from variables) is left. See my note at the end.

The motivation behind TypeState is that types are immutable, however some of their properties are dynamic, on a per variable basis.

The idea is therefore to create simple predicates about a type, and use the Control-Flow analysis that the compiler execute for many other reasons to statically decorate the type with those predicates.

Those predicates are not actually checked by the compiler itself, it could be too onerous, instead the compiler will simply reasons in terms of graph.

As a simple example, you create a predicate even, which returns true if a number is even.

Now, you create two functions:

  • halve, which only acts on even numbers
  • double, which take any number, and return an even number.

Note that the type number is not changed, you do not create a evennumber type and duplicate all those functions that previously acted on number. You just compose number with a predicate called even.

Now, let's build some graphs:

a: number -> halve(a)  #! error: `a` is not `even`

a: number, even -> halve(a)  # ok

a: number -> b = double(a) -> b: number, even

Simple, isn't it ?

Of course it gets a bit more complicated when you have several possible paths:

a: number -> a = double(a) -> a: number, even -> halve(a) #! error: `a` is not `even`
          \___________________________________/

This shows that you reason in terms of sets of predicates:

  • when joining two paths, the new set of predicates is the intersection of the sets of predicates given by those two paths

This can be augmented by the generic rule of a function:

  • to call a function, the set of predicates it requires must be satisfied
  • after a function is called, only the set of predicates it established is satisfied (note: arguments taken by value are not affected)

And thus the building block of TypeState in Rust:

  • check: checks that the predicate holds, if it does not fail, otherwise adds the predicate to set of predicates

Note that since Rust requires that predicates are pure functions, it can eliminate redundant check calls if it can prove that the predicate already holds at this point.


What Typestate lack is simple: composability.

If you read the description carefully, you will note this:

  • after a function is called, only the set of predicates it established is satisfied (note: arguments taken by value are not affected)

This means that predicates for a types are useless in themselves, the utility comes from annotating functions. Therefore, introducing a new predicate in an existing codebase is a bore, as the existing functions need be reviewed and tweaked to cater to explain whether or not they need/preserve the invariant.

And this may lead to duplicating functions at an exponential rate when new predicates pop up: predicates are not, unfortunately, composable. The very design issue they were meant to address (proliferation of types, thus functions), does not seem to be addressed.

司马昭之心 2024-09-15 22:18:37

它基本上是类型的扩展,您不仅检查一般情况下是否允许某些操作,而且还检查特定上下文中是否允许某些操作。所有这些都在编译时完成。

原始论文实际上非常可读。

It's basically an extension of types, where you don't just check whether some operation is allowed in general, but in this specific context. All that at compile time.

The original paper is actually quite readable.

雪花飘飘的天空 2024-09-15 22:18:37

有一个为 Java 编写的类型状态检查器,Adam Warski 的说明页提供了一些有用的信息。我只是自己弄清楚这些材料,但如果您熟悉 QuickCheck for Haskell,QuickCheck 在单子状态中的应用似乎很相似:对状态进行分类并解释当它们通过接口发生突变时它们如何变化。

There's a typestate checker written for Java, and Adam Warski's explanatory page gives some useful information. I'm only just figuring this material out myself, but if you are familiar with QuickCheck for Haskell, the application of QuickCheck to monadic state seems similar: categorise the states and explain how they change when they are mutated through the interface.

暗地喜欢 2024-09-15 22:18:37

Typestate解释为:

  • 利用类型系统对状态变化进行编码
  • 通过为每个状态创建类型来实现
    • 使用移动语义使状态无效
    • 从上一个状态返回下一个状态
    • 可以选择删除状态(关闭文件、连接...)
  • 逻辑的编译时执行
struct Data;
struct Signed;

impl Data {
  fn sign(self) -> Signed {
    Signed
  }
}

let data = Data;
let singed = data.sign();

data.sign() // Compile error

Typestate is explained as:

  • leverage type system to encode state changes
  • Implemented by creating a type for each state
    • Use move semantics to invalidate a state
    • Return the next state from the previous state
    • Optionally drop the state(close file, connections,...)
  • Compile time enforcement of logic
struct Data;
struct Signed;

impl Data {
  fn sign(self) -> Signed {
    Signed
  }
}

let data = Data;
let singed = data.sign();

data.sign() // Compile error
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文