summaryrefslogtreecommitdiff
path: root/2025/03/25/Scala-3-Capture-Checking/index.html
diff options
context:
space:
mode:
Diffstat (limited to '2025/03/25/Scala-3-Capture-Checking/index.html')
-rw-r--r--2025/03/25/Scala-3-Capture-Checking/index.html16
1 files changed, 5 insertions, 11 deletions
diff --git a/2025/03/25/Scala-3-Capture-Checking/index.html b/2025/03/25/Scala-3-Capture-Checking/index.html
index b7c12801..b3dcf199 100644
--- a/2025/03/25/Scala-3-Capture-Checking/index.html
+++ b/2025/03/25/Scala-3-Capture-Checking/index.html
@@ -213,11 +213,8 @@
<li>不安全的资源管理: 例如,传统的 <code>try-with-resources</code> 模式旨在确保资源在使用后被正确关闭。Capture Checking 通过跟踪与资源相关的 Capabilities,可以防止在资源关闭或失效后继续使用它的情况。文档中提到的 <code>usingLogFile</code> 示例就展示了这一点,其中一个闭包尝试写入一个已经关闭的文件,而Capture Checking可以捕获这种不安全的操作:</li>
</ul>
<figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">usingLogFile</span></span>[<span class="type">T</span>](op: <span class="type">FileOutputStream</span> =&gt; <span class="type">T</span>): <span class="type">T</span> =</span><br><span class="line"> <span class="keyword">val</span> logFile = <span class="type">FileOutputStream</span>(<span class="string">&quot;log&quot;</span>)</span><br><span class="line"> <span class="keyword">val</span> result = op(logFile)</span><br><span class="line"> logFile.close()</span><br><span class="line"> result</span><br></pre></td></tr></table></figure>
-
<figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">usingLogFile</span></span>[<span class="type">T</span>](op: <span class="type">FileOutputStream</span>^ =&gt; <span class="type">T</span>): <span class="type">T</span> =</span><br><span class="line"> <span class="comment">// same body as before</span></span><br></pre></td></tr></table></figure>
-
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">| val later = usingLogFile &#123; f =&gt; () =&gt; f.write(0) &#125;</span><br><span class="line">| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^</span><br><span class="line">|The expression&#x27;s type () =&gt; Unit is not allowed to capture the root capability `cap`.</span><br><span class="line">|This usually means that a capability persists longer than its allowed lifetime.</span><br></pre></td></tr></table></figure>
-
<ul>
<li>Effect Polymorphism: Capture Checking 提供了一种更灵活和精确地推理和控制副作用的机制。它可以被视为一种 Effect system,允许类型系统跟踪和控制代码可能产生的副作用。</li>
<li>“函数的颜色”问题: 在异步编程中,区分同步和异步操作一直是一个挑战。Capture Checking 有可能帮助区分和管理同步与异步计算,这通过跟踪与异步操作相关的 Capabilities 来实现。</li>
@@ -231,15 +228,13 @@
<p>开发者还可以指定函数可以捕获的特定 Capability,语法为 <code>A -&gt;&#123;c, d&#125; B</code>,表示该函数可以捕获 Capability <code>c</code> 和 <code>d</code>。这种语法允许对函数可以使用的Capability 进行精确控制,从而提高了资源管理的细粒度。通过显式列出捕获的 Capability,编译器可以验证函数是否遵守这些约束,并防止其意外访问其他资源。</p>
<p>捕获注解 <code>^</code> 的优先级高于 <code>-&gt;</code> 。理解运算符的优先级对于正确解释和编写带有捕获注解的函数类型至关重要。不正确的解析可能导致意想不到的行为或类型错误。例如,<code>A ^ C -&gt; B</code> 表示一个从捕获的 <code>A</code> 到 <code>B</code> 的纯函数。</p>
<figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">f</span></span>(x: -&gt;&#123;c&#125; <span class="type">Int</span>): <span class="type">Int</span></span><br></pre></td></tr></table></figure>
-
<p>Capture Checking 也适用于上下文函数。不纯的上下文函数使用 <code>?=&gt;</code>,行为类似于 <code>=&gt;</code>,可以捕获任意Capability。纯的上下文函数使用 <code>?-&gt;</code>,行为类似于 <code>-&gt;</code>,不能捕获任何Capability。这表明,Capture Checking扩展到了上下文函数,允许控制在它们的隐式参数作用域内捕获的 Capability。</p>
<p>值得注意的是,方法本身并不是值,因此它们不直接捕获Capability。相反,它们对 Capability 的引用会被计入封闭对象的捕获集中。这种区分很重要,因为方法的捕获行为与它们所属对象的状态和Capability相关联,这反映了 Scala 的面向对象特性。</p>
<p>与函数类型类似,Capture Checking的概念也延伸到了命名参数类型。<code>=&gt; Int</code> 允许任意Capability引用,类似于不纯函数类型。<code>-&gt; Int</code> 禁止任何Capability引用,类似于纯函数类型。而 <code>-&gt;&#123;c&#125; Int</code> 则只允许引用Capability <code>c</code>。这种一致性确保了即使是延迟求值的表达式也遵循Capability约束。</p>
<p>子捕获(<code>C₁ &lt;: C₂</code>)定义了捕获集之间的关系。捕获集 <code>C₁</code> 是 <code>C₂</code> 的子类型,如果 <code>C₂</code> 包含了 <code>C₁</code> 中的每一个元素,并且满足以下条件之一:<code>c ∈ C₂</code>(直接包含);<code>c</code> 是一个类参数,且 <code>C₂</code> 包含 <code>Cls.this</code>(Capability 来源于封闭的类实例);<code>c</code> 的类型具有捕获集 <code>C</code>,且 <code>C &lt;: C₂</code>(基于 Capability 类型的递归子捕获)。子捕获定义了 Capability 依赖的层级结构,这对于类型系统判断一个需要特定 Capability 集合的值是否可以在提供不同 Capability 集合的上下文中使用至关重要。</p>
<p>对于捕获类型的子类型,存在以下规则:纯类型是捕获类型的子类型(<code>T &lt;: C T</code>);较小的捕获集会产生子类型(如果 <code>C₁ &lt;: C₂</code> 且 <code>T₁ &lt;: T₂</code>,则 <code>C₁ T₁ &lt;: C₂ T₂</code>)。这意味着一个依赖较少 Capability 的值通常更通用,可以在更广泛的场景中使用。根 Capability <code>&#123;cap&#125;</code> 覆盖了所有其他捕获集,因此任何特定的捕获集都是 <code>&#123;cap&#125;</code> 的子类型。这允许具有特定捕获要求的类型在允许任何 Capability 的上下文中使用。</p>
-<p>Capability widening(也称为 _avoidance_)是一种简化局部变量类型的机制。局部变量的类型会被 widening 到不提及该变量本身的最小超类型,这个过程通常会涉及到变量的捕获集。这种加宽有助于改善类型推断和代码清晰度,避免局部变量的类型变得过于复杂。</p>
+<p>Capability widening(也称为 <em>avoidance</em>)是一种简化局部变量类型的机制。局部变量的类型会被 widening 到不提及该变量本身的最小超类型,这个过程通常会涉及到变量的捕获集。这种加宽有助于改善类型推断和代码清晰度,避免局部变量的类型变得过于复杂。</p>
<figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">fs: <span class="type">FileSystem</span>^</span><br><span class="line">ct: <span class="type">CanThrow</span>[<span class="type">Exception</span>]^</span><br><span class="line">l : <span class="type">Logger</span>^&#123;fs&#125;</span><br><span class="line"></span><br><span class="line">&#123;l&#125; &lt;: &#123;fs&#125; &lt;: &#123;cap&#125;</span><br><span class="line">&#123;fs&#125; &lt;: &#123;fs, ct&#125; &lt;: &#123;cap&#125;</span><br><span class="line">&#123;ct&#125; &lt;: &#123;fs, ct&#125; &lt;: &#123;cap&#125;</span><br></pre></td></tr></table></figure>
-
<p>继承自 <code>caps.Capability</code> 的类具有隐式的 <code>&#123;cap&#125;</code> 捕获集。这表明这些类的实例本质上代表了一种 Capability。Capability 类提供了一种将 Capability 显式定义和管理为类型系统中的一等公民的方式。开发者可以创建具有特定语义和使用模式的自定义 Capability。</p>
<p>在使用 Capability 类的场景中,通常会结合 using clauses 和隐式参数来减少在代码中显式传递 Capability 的需要。这两种方法提供了一种自动将必要的 Capability 提供给函数和方法的方式,从而减少了样板代码并提高了代码的可读性。</p>
<p>闭包会捕获在其主体中引用的来自其周围环境的 Capability。这导致闭包的函数类型中包含捕获集。例如,如果一个闭包引用了一个局部变量 <code>fs</code>,而 <code>fs</code> 是一个 Capability,那么该闭包的类型可能就是 <code>String -&gt;&#123;fs&#125; Unit</code>。这意味着闭包继承了其封闭代码的 Capability 要求,确保它们只能在这些 Capability 可用的上下文中被使用。</p>
@@ -249,10 +244,7 @@
<p>类的捕获引用包括从类外部使用的局部 Capability 以及具有捕获类型的构造函数参数(参数Capability)。局部Capability会被内部类继承。</p>
<p>类实例的 <code>this</code> 的捕获集是根据捕获的引用、父类以及类内部的使用约束来推断的 1。这种自动推断机制在很多情况下减少了手动指定类捕获集的需要。</p>
<figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> caps.<span class="type">Capability</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">FileSystem</span> <span class="keyword">extends</span> <span class="title">Capability</span></span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Logger</span>(<span class="params">using <span class="type">FileSystem</span></span>)</span>:</span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">log</span></span>(s: <span class="type">String</span>): <span class="type">Unit</span> = ???</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">test</span></span>(<span class="keyword">using</span> fs: <span class="type">FileSystem</span>) =</span><br><span class="line"> <span class="keyword">val</span> l: <span class="type">Logger</span>^&#123;fs&#125; = <span class="type">Logger</span>()</span><br><span class="line"> ...</span><br></pre></td></tr></table></figure>
-
<figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Logger</span>(<span class="params">using <span class="type">FileSystem</span>^&#123;cap&#125;</span>)</span>:</span><br><span class="line"> ^^^^^^^^^^^^^^</span><br><span class="line"> redundant capture: <span class="type">FileSystem</span> already accounts <span class="keyword">for</span> cap</span><br></pre></td></tr></table></figure>
-
-
<p>捕获隧道 (Capture Tunnelling) 是指当一个类型变量被一个捕获类型实例化时,捕获信息不会立即传播到外层的泛型类型。相反,捕获会“穿过隧道”,并在类型变量被访问或其成员被使用时重新出现。这种机制有助于以更简洁和可管理的方式处理泛型代码中的捕获集,避免类型签名过于复杂。</p>
<p>逃逸检查施加了一些限制。作为类型变量实例的捕获类型不能携带通用Capability <code>cap</code> 。可变变量也不能拥有通用捕获集 。逃逸检查阻止了在参数化类型的参数中返回或分配带有局部 Capability 的闭包,因为这可能导致 Capability 逃逸其预期的作用域。单调性规则指出,在一个带有字段 <code>f</code> 的类中,<code>&#123;this&#125;</code> 覆盖了 <code>&#123;this.f&#125;</code> 以及 <code>this.f</code> 对纯参数的应用。这意味着如果类实例本身被视为一种 Capability,那么其字段所持有的任何 Capability 也会被隐式地覆盖。逃逸检查对于维护 Capability 跟踪的完整性至关重要,它可以防止 Capability 在其预期生命周期或作用域之外被使用,尤其是在泛型和可变状态的上下文中。</p>
<p>受检异常可以通过导入 <code>language.experimental.saferExceptions</code> 来启用。方法上的 <code>throws</code> 子句会扩展为一个隐式的 <code>CanThrow</code> Capability参数,表明该方法可能抛出指定类型的异常。<code>throw</code> 表达式需要 <code>CanThrow</code> Capability,而 <code>try</code> 表达式会创建这种Capability。在 <code>language.experimental.captureChecking</code> 下,由于逃逸的 Capability 而导致未处理异常的代码会被拒绝。为了实现这种集成,<code>CanThrow</code> 需要继承 <code>Capability</code>,并且需要将逃逸检查扩展到 <code>try</code> 表达式,以防止捕获 <code>cap</code>。Capture Checking 与受检异常的集成确保了异常的可能性也被作为一种 Capability 需求来跟踪,从而加强了语言的整体资源管理和错误处理 Capability。</p>
@@ -268,7 +260,8 @@
<th></th>
</tr>
</thead>
-<tbody><tr>
+<tbody>
+<tr>
<td>代码片段</td>
<td>演示概念</td>
<td>捕获行为解释</td>
@@ -338,7 +331,8 @@
<td>使用Capability多态的类型定义</td>
<td>类型 <code>Source</code> 被参数化为可以持有Capability集 <code>X^</code>。</td>
</tr>
-</tbody></table>
+</tbody>
+</table>
<p>当 <code>cap</code> 出现在函数的结果类型中时,通常表示一个由存在性量词绑定的未知类型(例如,<code>() -&gt; Iterator^</code> 意味着 <code>() -&gt; Exists x. Iterator^x</code>)。这表明返回的迭代器可能捕获了某种 Capability,但具体的哪种 Capability 在静态类型检查时是未知的。在内部,这种存在性 Capability 使用带有 sealed trait <code>Exists</code> 的依赖函数类型来表示 。结果类型中协变的 <code>cap</code> 会被替换为一个新的 existential variable。当应用一个具有 existential result 类型 <code>Exists ex.T</code> 的函数时,结果是 <code>T</code>,其中 <code>ex</code> 被 <code>cap</code> 替换。Existential Capability 允许类型系统表达在编译时具体捕获的 Capability 未知的情况,从而提供了灵活性,同时仍然保持了一定程度的跟踪。</p>
<p>Reach Capability 用于表达一个变量引用了通过另一个Capability“Reach”的任何操作。例如,如果 <code>ops</code> 是一个表示一组操作的Capability,那么 <code>ops*</code> 就表示出现在 <code>ops</code> 类型中且通过 <code>ops</code> 访问的任何协变 Capability。Reach Capability 提供了一种间接推理和跟踪 Capability 的方式,这对于建模具有相互连接资源的复杂系统非常有用。</p>
<p>Capability 多态允许使用带有上界 <code>CapSet</code> 的类型变量来参数化操作的捕获集。这使得定义诸如 <code>Source[X^]</code> 这样的类型成为可能,其中 <code>X^</code> 表示监听器可以持有的一组 Capability。Capability 多态增强了代码的表达性和可重用性,因为它允许函数和数据结构在它们可能依赖的 Capability 集上进行参数化。</p>