diff options
| author | muqiuhan <[email protected]> | 2025-05-27 06:06:45 +0000 |
|---|---|---|
| committer | muqiuhan <[email protected]> | 2025-05-27 06:06:45 +0000 |
| commit | 4da1dc74954fafe4d21841892bafc955a37ac805 (patch) | |
| tree | 85a1dfa32a37c0cbf63b7d6b3735ab854d707efb /2024 | |
| parent | bb19987062af9a28aa5dea93cc0eba9ac3a7e1ab (diff) | |
| download | blog-4da1dc74954fafe4d21841892bafc955a37ac805.tar.gz | |
deploy: 74a1d57ead41454de540829cd6de5ba1f5702bd6
Diffstat (limited to '2024')
6 files changed, 39 insertions, 39 deletions
diff --git a/2024/09/15/Advanced-C-binding-using-ocaml-ctypes-and-dune/index.html b/2024/09/15/Advanced-C-binding-using-ocaml-ctypes-and-dune/index.html index 91c25fa7..e041843f 100644 --- a/2024/09/15/Advanced-C-binding-using-ocaml-ctypes-and-dune/index.html +++ b/2024/09/15/Advanced-C-binding-using-ocaml-ctypes-and-dune/index.html @@ -210,9 +210,9 @@ <p>Most network-based C libraries refer to <code>socket.h</code> to describe the type of socket that can be used with their API so it’s an important entry point for a lot of network operations and one that would be nice to support as generically as possible in OCaml.</p> <p>The catch, though, is that, most likely for historical reasons¹, the <a target="_blank" rel="noopener" href="https://pubs.opengroup.org/onlinepubs/7908799/xns/syssocket.h.html">POSIX specifications</a> only <em>partially</em> defines some of the required data structures and types, which makes it possible to write C code using them but does not give enough information to write C bindings without having to use the compiler to parse the actual system-specific headers of the running host.</p> <p>For instance, here’s how the <code>sockaddr</code> structure is specified:</p> -<p>The <em><sys/socket.h></em> header defines the <strong>sockaddr</strong> structure that includes at least the following members:sa_family_t sa_family address family<br>char sa_data[] socket address (variable-length data)</p> +<p>The <em><sys/socket.h></em> header defines the sockaddr structure that includes at least the following members:sa_family_t sa_family address family<br>char sa_data[] socket address (variable-length data)</p> <p>Likewise, here’s what is specified about the size of the <code>socklen_t</code> data type:</p> -<p><em><sys/socket.h></em> makes available a type, <strong>socklen_t</strong>, which is an unsigned opaque integral type of length of at least 32 bits.</p> +<p><em><sys/socket.h></em> makes available a type, socklen_t, which is an unsigned opaque integral type of length of at least 32 bits.</p> <p>Thus, in order to know the exact offset of <code>sa_family</code> inside the <code>sockaddr</code> structure or the actual size of a <code>socklen_t</code> integer, one has to include the OS-specific header, parse its definitions for that specific OS and, only then, is it possible to compute that offset or data size. Let’s see how it’s done in our binding now!</p> <h1 id="Putting-it-together"><a href="#Putting-it-together" class="headerlink" title="Putting it together"></a>Putting it together</h1><p>The C binding requires 4 separate passes:</p> <ul> diff --git a/2024/09/27/Turborepo-简述/index.html b/2024/09/27/Turborepo-简述/index.html index 24ed29e9..a2322b79 100644 --- a/2024/09/27/Turborepo-简述/index.html +++ b/2024/09/27/Turborepo-简述/index.html @@ -222,8 +222,8 @@ <p>那么 <code>apps</code> 或 <code>packages</code> 目录中有 <code>package.json</code> 的每个目录都将被视为一个包。</p> <blockquote> -<p>注意:Turborepo 不支持嵌套包,例如 <code>apps/**</code> 或 <code>packages/**</code> 这种,将一个包放在<code>apps/a</code> 并将另一个包放在 <code>apps/a/b</code> 的结构将导致错误。</p> -<p>如果想按目录对包进行分组,可以使用 <code>packages/*</code> 和 <code>packages/group/*</code> 等 glob 来完成此操作,而<strong>不是</strong>创建 <code>packages/group/package.json</code> 文件。</p> +<p>注意:Turborepo 不支持嵌套包,例如 <code>apps/</code> 或 <code>packages/</code> 这种,将一个包放在<code>apps/a</code> 并将另一个包放在 <code>apps/a/b</code> 的结构将导致错误。</p> +<p>如果想按目录对包进行分组,可以使用 <code>packages/*</code> 和 <code>packages/group/*</code> 等 glob 来完成此操作,而不是创建 <code>packages/group/package.json</code> 文件。</p> </blockquote> <p>根目录的 <code>package.json</code> 是 workspace 的基础,常见的配置:</p> <figure class="highlight json"><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><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"private"</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"scripts"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"build"</span><span class="punctuation">:</span> <span class="string">"turbo run build"</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"dev"</span><span class="punctuation">:</span> <span class="string">"turbo run dev"</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"lint"</span><span class="punctuation">:</span> <span class="string">"turbo run lint"</span></span><br><span class="line"> <span class="punctuation">}</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"devDependencies"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"turbo"</span><span class="punctuation">:</span> <span class="string">"latest"</span></span><br><span class="line"> <span class="punctuation">}</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"packageManager"</span><span class="punctuation">:</span> <span class="string">"[email protected]"</span></span><br><span class="line"><span class="punctuation">}</span></span><br></pre></td></tr></table></figure> @@ -244,9 +244,9 @@ <p>以这种方式使用导出有三个主要好处:</p> <ul> -<li><strong>避免 barrel 文件</strong>:barrel 文件是重新导出同一包中其他文件的文件,从而为整个包创建一个入口点。虽然它们可能看起来很方便,但编译器<a target="_blank" rel="noopener" href="https://vercel.com/blog/how-we-optimized-package-imports-in-next-js#what's-the-problem-with-barrel-files">和捆绑程序很难处理</a>它们,并且可能很快导致性能问题。</li> -<li><strong>更强大的功能</strong>:与<a target="_blank" rel="noopener" href="https://nodejs.org/api/packages.html#main"><code>主</code>字段</a>(如 <a target="_blank" rel="noopener" href="https://nodejs.org/api/packages.html#conditional-exports">Conditional Exports</a>)相比,<code>exports</code> 还具有其他强大的功能。一般来说,尽可能使用 <code>exports</code> 而不是 <code>main</code> 就行了。</li> -<li><strong>IDE 友好</strong>:通过使用 <code>export</code> 指定包的入口点,代码编辑器可以为包的导出提供自动完成。</li> +<li>避免 barrel 文件:barrel 文件是重新导出同一包中其他文件的文件,从而为整个包创建一个入口点。虽然它们可能看起来很方便,但编译器<a target="_blank" rel="noopener" href="https://vercel.com/blog/how-we-optimized-package-imports-in-next-js#what's-the-problem-with-barrel-files">和捆绑程序很难处理</a>它们,并且可能很快导致性能问题。</li> +<li>更强大的功能:与<a target="_blank" rel="noopener" href="https://nodejs.org/api/packages.html#main"><code>主</code>字段</a>(如 <a target="_blank" rel="noopener" href="https://nodejs.org/api/packages.html#conditional-exports">Conditional Exports</a>)相比,<code>exports</code> 还具有其他强大的功能。一般来说,尽可能使用 <code>exports</code> 而不是 <code>main</code> 就行了。</li> +<li>IDE 友好:通过使用 <code>export</code> 指定包的入口点,代码编辑器可以为包的导出提供自动完成。</li> </ul> <hr> <p>除此之外还有 <code>import</code> 字段,也就是一种创建包中其他模块的子路径的方法。可以简单地视为 “快捷方式” ,用于编写更简单的导入路径,这些路径对日后文件被移动后的重构更具弹性。</p> @@ -261,13 +261,13 @@ <p>这种做法有几个好处:</p> <ul> -<li><strong>更清晰</strong>:当软件包的依赖项列在其 <code>package.json</code> 中时,更容易理解软件包所依赖的内容。在存储库中工作的开发人员可以一目了然地看到包中使用了哪些依赖项。</li> -<li><strong>更具灵活性</strong>:在大规模的 monorepo 中,想让每个包都使用相同版本的外部依赖项可能是不现实的。当有许多团队在同一个代码库中工作时,优先级、时间表和需求会有所不同。通过在 “使用它们的包” 中安装依赖项,可以让 <code>UI</code> 团队能够升级到最新版本的 TypeScript,而 <code>Web</code> 团队可以优先发布新功能并在以后使用 TypeScript。</li> -<li><strong>更好的缓存能力</strong>:如果在存储库的根目录中安装了太多依赖项,则每当添加、更新或删除依赖项时,都会更改工作区根目录,从而导致不必要的缓存未命中。</li> -<li><strong>修剪未使用的依赖项</strong>:对于 Docker 用户,<a target="_blank" rel="noopener" href="https://turbo.build/repo/docs/reference/prune">Turborepo 的修剪功能</a>可以从 Docker 镜像中删除未使用的依赖项,以创建更轻量级的镜像。当依赖项安装在它们所适用的包中时,Turborepo 可以读取锁文件并删除所需的包中未使用的依赖项。</li> +<li>更清晰:当软件包的依赖项列在其 <code>package.json</code> 中时,更容易理解软件包所依赖的内容。在存储库中工作的开发人员可以一目了然地看到包中使用了哪些依赖项。</li> +<li>更具灵活性:在大规模的 monorepo 中,想让每个包都使用相同版本的外部依赖项可能是不现实的。当有许多团队在同一个代码库中工作时,优先级、时间表和需求会有所不同。通过在 “使用它们的包” 中安装依赖项,可以让 <code>UI</code> 团队能够升级到最新版本的 TypeScript,而 <code>Web</code> 团队可以优先发布新功能并在以后使用 TypeScript。</li> +<li>更好的缓存能力:如果在存储库的根目录中安装了太多依赖项,则每当添加、更新或删除依赖项时,都会更改工作区根目录,从而导致不必要的缓存未命中。</li> +<li>修剪未使用的依赖项:对于 Docker 用户,<a target="_blank" rel="noopener" href="https://turbo.build/repo/docs/reference/prune">Turborepo 的修剪功能</a>可以从 Docker 镜像中删除未使用的依赖项,以创建更轻量级的镜像。当依赖项安装在它们所适用的包中时,Turborepo 可以读取锁文件并删除所需的包中未使用的依赖项。</li> </ul> <blockquote> -<p>属于工作区根目录的唯一依赖项是<strong>用于管理存储库的工具,</strong>而用于构建应用程序和库的依赖项安装在各自的包中。一些适合安装在根中的依赖项示例包括 <a target="_blank" rel="noopener" href="https://www.npmjs.com/package/turbo"><code>turbo</code></a>、<a target="_blank" rel="noopener" href="https://www.npmjs.com/package/husky"><code>husky</code></a> 或 <a target="_blank" rel="noopener" href="https://www.npmjs.com/package/lint-staged"><code>lint-staged</code></a>。</p> +<p>属于工作区根目录的唯一依赖项是用于管理存储库的工具,而用于构建应用程序和库的依赖项安装在各自的包中。一些适合安装在根中的依赖项示例包括 <a target="_blank" rel="noopener" href="https://www.npmjs.com/package/turbo"><code>turbo</code></a>、<a target="_blank" rel="noopener" href="https://www.npmjs.com/package/husky"><code>husky</code></a> 或 <a target="_blank" rel="noopener" href="https://www.npmjs.com/package/lint-staged"><code>lint-staged</code></a>。</p> </blockquote> <h3 id="保持同一版本的依赖"><a href="#保持同一版本的依赖" class="headerlink" title="保持同一版本的依赖"></a>保持同一版本的依赖</h3><p>一些 monorepo 维护者更喜欢按照规则在所有软件包中保持对相同版本的依赖关系。有几种方法可以实现此目的:</p> <ul> @@ -281,18 +281,18 @@ <h2 id="内部包"><a href="#内部包" class="headerlink" title="内部包"></a>内部包</h2><p><a target="_blank" rel="noopener" href="https://turbo.build/repo/docs/core-concepts/internal-packages">内部包</a>是工作区的构建块(building blocks),是一种在存储库中共享代码的强大方式。Turborepo 读取 <code>package.json</code> 中的依赖项来分析内部包之间的关系,并在后台创建 <a target="_blank" rel="noopener" href="https://turbo.build/repo/docs/core-concepts/package-and-task-graph#package-graph">Package Graph</a> 以优化存储库的工作流程。</p> <p>在创建内部包时,建议创建具有单一 “用途” 的包。这是最佳实践,具体取决于存储库的规模、组织、团队需求等。此策略具有以下几个优点:</p> <ul> -<li><strong>更易于理解</strong>:随着存储库的扩展,在存储库中工作的开发人员将能够更轻松地找到他们需要的代码。</li> -<li><strong>减少每个包的依赖项</strong>:每个包使用更少的依赖项,以便 Turborepo 可以更有效地<a target="_blank" rel="noopener" href="https://turbo.build/repo/docs/reference/prune">修剪包图的依赖项</a>。</li> +<li>更易于理解:随着存储库的扩展,在存储库中工作的开发人员将能够更轻松地找到他们需要的代码。</li> +<li>减少每个包的依赖项:每个包使用更少的依赖项,以便 Turborepo 可以更有效地<a target="_blank" rel="noopener" href="https://turbo.build/repo/docs/reference/prune">修剪包图的依赖项</a>。</li> </ul> <p>在创建<a target="_blank" rel="noopener" href="https://turbo.build/repo/docs/core-concepts/package-types#application-packages">应用程序包</a>时,最好避免将共享代码放在这些包中。相反,应该为共享代码创建一个单独的包,并让应用程序包依赖于该包。<br>此外,应用程序包不应安装到其他包中。相反,应将它们视为 <a target="_blank" rel="noopener" href="https://turbo.build/repo/docs/core-concepts/package-and-task-graph#package-graph">Package Graph</a> 的入口点。</p> <h2 id="配置任务"><a href="#配置任务" class="headerlink" title="配置任务"></a>配置任务</h2><p>Turborepo 将始终按照 <a target="_blank" rel="noopener" href="https://turbo.build/repo/docs/reference/configuration"><code>turbo.json</code> 配置</a>和 <a target="_blank" rel="noopener" href="https://turbo.build/repo/docs/core-concepts/package-and-task-graph#package-graph">Package Graph</a> 中描述的顺序运行任务,并尽可能并行化工作以确保一切尽可能快地运行。</p> <p>根目录的 <code>turbo.json</code> 文件是注册 Turborepo 将运行的任务的位置。定义任务后,将能够使用 <a target="_blank" rel="noopener" href="https://turbo.build/repo/docs/reference/run"><code>turbo run</code></a> 运行一个或多个任务。</p> -<p><code>tasks</code> 对象中的每个 key 都是一个可以通过 <code>turbo run</code> 执行的任务。Turborepo 将在 <code>package.json</code> 中搜索与<strong>任务同名的软件包</strong>:</p> +<p><code>tasks</code> 对象中的每个 key 都是一个可以通过 <code>turbo run</code> 执行的任务。Turborepo 将在 <code>package.json</code> 中搜索与任务同名的软件包:</p> <p><a target="_blank" rel="noopener" href="https://turbo.build/repo/docs/reference/configuration#dependson"><code>dependsOn</code> 键</a>用于指定在其他任务开始运行之前必须完成的任务。在大多数情况下,库的<code>build</code>脚本在应用程序的<code>build</code>脚本运行之前完成,所以可以这么写:</p> <figure class="highlight json"><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"><span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"tasks"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"build"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"dependsOn"</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">"^build"</span><span class="punctuation">]</span> </span><br><span class="line"> <span class="punctuation">}</span></span><br><span class="line"> <span class="punctuation">}</span></span><br><span class="line"><span class="punctuation">}</span></span><br></pre></td></tr></table></figure> <blockquote> -<p><code>^</code> 这个语法告诉 Turborepo 从依赖关系图的底部开始运行任务。如果应用程序依赖于名为 <code>ui</code> 的库,并且该库具有<code>build</code>任务,则 <code>ui</code> 中的<code>build</code>脚本将<strong>首先</strong>运行。成功完成后,才会运行应用程序中的<code>build</code>任务。<br>这是一个重要的形式,因为它可以确保应用程序的<code>build</code>任务具有编译所需的所有必要依赖项。当依赖关系图发展到具有多个级别的任务依赖关系的更复杂的结构时,此概念也适用。</p> +<p><code>^</code> 这个语法告诉 Turborepo 从依赖关系图的底部开始运行任务。如果应用程序依赖于名为 <code>ui</code> 的库,并且该库具有<code>build</code>任务,则 <code>ui</code> 中的<code>build</code>脚本将首先运行。成功完成后,才会运行应用程序中的<code>build</code>任务。<br>这是一个重要的形式,因为它可以确保应用程序的<code>build</code>任务具有编译所需的所有必要依赖项。当依赖关系图发展到具有多个级别的任务依赖关系的更复杂的结构时,此概念也适用。</p> </blockquote> <p>有时可能需要确保同一包中的两个任务按特定顺序运行。例如需要先在库中运行<code>build</code>任务,然后再在同一库中运行<code>test</code>任务。这种情况删掉 <code>^</code> 就行了:</p> <figure class="highlight json"><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"><span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"tasks"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"test"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"dependsOn"</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">"build"</span><span class="punctuation">]</span> </span><br><span class="line"> <span class="punctuation">}</span></span><br><span class="line"> <span class="punctuation">}</span></span><br><span class="line"><span class="punctuation">}</span></span><br></pre></td></tr></table></figure> @@ -309,10 +309,10 @@ <h3 id="指定输入输出"><a href="#指定输入输出" class="headerlink" title="指定输入输出"></a>指定输入输出</h3><p><code>outputs</code> 键告诉 Turborepo 文件和目录在任务成功完成时应该缓存在哪。如果未定义此 key,Turborepo 将不会缓存任何文件。</p> <p>例如缓存 vite 的输出一般可以这么写:</p> -<figure class="highlight json"><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"><span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"tasks"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"build"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"outputs"</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">"dist/**"</span><span class="punctuation">]</span> </span><br><span class="line"> <span class="punctuation">}</span></span><br><span class="line"> <span class="punctuation">}</span></span><br><span class="line"><span class="punctuation">}</span></span><br></pre></td></tr></table></figure> +<figure class="highlight json"><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"><span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"tasks"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"build"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"outputs"</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">"dist/"</span><span class="punctuation">]</span> </span><br><span class="line"> <span class="punctuation">}</span></span><br><span class="line"> <span class="punctuation">}</span></span><br><span class="line"><span class="punctuation">}</span></span><br></pre></td></tr></table></figure> <p><code>inputs</code> 键用于指定要包含在任务哈希中以进行<a target="_blank" rel="noopener" href="https://turbo.build/repo/docs/crafting-your-repository/caching">缓存</a>的文件。默认情况下,Turborepo 将包含包中由 Git 跟踪的所有文件。但是也可以使用 <code>inputs</code> 键更具体地说明哈希中包含哪些文件, 例如,在 Markdown 文件中查找拼写错误的任务可以定义如下::</p> -<figure class="highlight json"><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"><span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"tasks"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"spell-check"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"inputs"</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">"**/*.md"</span><span class="punctuation">,</span> <span class="string">"**/*.mdx"</span><span class="punctuation">]</span> </span><br><span class="line"> <span class="punctuation">}</span></span><br><span class="line"> <span class="punctuation">}</span></span><br><span class="line"><span class="punctuation">}</span></span><br></pre></td></tr></table></figure> +<figure class="highlight json"><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"><span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"tasks"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"spell-check"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"inputs"</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">"/*.md"</span><span class="punctuation">,</span> <span class="string">"/*.mdx"</span><span class="punctuation">]</span> </span><br><span class="line"> <span class="punctuation">}</span></span><br><span class="line"> <span class="punctuation">}</span></span><br><span class="line"><span class="punctuation">}</span></span><br></pre></td></tr></table></figure> <p>可以通过微调 <code>input</code> 以忽略对已知不会影响任务输出的文件的更改来提高某些任务的缓存命中率, 可以使用 <code>$TURBO_DEFAULT$</code> 微语法来微调默认 <code>input</code> 行为:</p> <figure class="highlight json"><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"><span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"tasks"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"build"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"inputs"</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">"$TURBO_DEFAULT$"</span><span class="punctuation">,</span> <span class="string">"!README.md"</span><span class="punctuation">]</span> </span><br><span class="line"> <span class="punctuation">}</span></span><br><span class="line"> <span class="punctuation">}</span></span><br><span class="line"><span class="punctuation">}</span></span><br></pre></td></tr></table></figure> @@ -325,7 +325,7 @@ <li><p><a target="_blank" rel="noopener" href="https://turbo.build/repo/docs/reference/package-configurations">包配置</a>是直接放入包中的<code>turbo.json</code>文件。这允许软件包为其自己的任务定义特定行为,而不会影响存储库的其余部分。</p> </li> <li><p>有一些始终需要运行的任务,例如缓存生成后的部署脚本。对于这些任务,用 <code>“cache”: false</code> :</p> -<figure class="highlight json"><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><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"tasks"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"deploy"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"dependsOn"</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">"^build"</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"cache"</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span></span><br><span class="line"> <span class="punctuation">}</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"build"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"outputs"</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">"dist/**"</span><span class="punctuation">]</span></span><br><span class="line"> <span class="punctuation">}</span></span><br><span class="line"> <span class="punctuation">}</span></span><br><span class="line"><span class="punctuation">}</span></span><br></pre></td></tr></table></figure> +<figure class="highlight json"><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><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"tasks"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"deploy"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"dependsOn"</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">"^build"</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"cache"</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span></span><br><span class="line"> <span class="punctuation">}</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"build"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"outputs"</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">"dist/"</span><span class="punctuation">]</span></span><br><span class="line"> <span class="punctuation">}</span></span><br><span class="line"> <span class="punctuation">}</span></span><br><span class="line"><span class="punctuation">}</span></span><br></pre></td></tr></table></figure> </li> <li><p>某些任务可以并行运行,例如 Linter 不需要等待依赖项中的输出成功才能运行:</p> <figure class="highlight json"><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="punctuation">{</span></span><br><span class="line"> <span class="attr">"tasks"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"transit"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"dependsOn"</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">"^transit"</span><span class="punctuation">]</span></span><br><span class="line"> <span class="punctuation">}</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"check-types"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"dependsOn"</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">"transit"</span><span class="punctuation">]</span></span><br><span class="line"> <span class="punctuation">}</span><span class="punctuation">,</span></span><br><span class="line"> <span class="punctuation">}</span><span class="punctuation">,</span></span><br><span class="line"><span class="punctuation">}</span></span><br></pre></td></tr></table></figure> @@ -352,7 +352,7 @@ <h3 id="其他-1"><a href="#其他-1" class="headerlink" title="其他"></a>其他</h3><ul> <li><a target="_blank" rel="noopener" href="https://turbo.build/repo/docs/reference/run#--dry----dry-run"><code>--dry</code> 标志</a>,可用于查看如果在没有实际运行任务的情况下运行任务会发生什么。当不确定正在运行的任务时,这对于调试缓存问题非常有用。</li> <li><a target="_blank" rel="noopener" href="https://turbo.build/repo/docs/reference/run#--summarize"><code>--summarize</code> 标志</a>,可用于获取任务的所有输入、输出等的概览。比较两个摘要将揭示两个任务的哈希值不同的原因。</li> -<li>强制 <code>turbo</code> 重新执行已缓存的任务,请使用 <a target="_blank" rel="noopener" href="https://turbo.build/repo/docs/reference/run#--force"><code>--force</code> 标志</a>。请注意,这将禁用<strong>读取</strong>缓存,<strong>而不是写入</strong>。</li> +<li>强制 <code>turbo</code> 重新执行已缓存的任务,请使用 <a target="_blank" rel="noopener" href="https://turbo.build/repo/docs/reference/run#--force"><code>--force</code> 标志</a>。请注意,这将禁用读取缓存,而不是写入。</li> </ul> <h2 id="开发工作流"><a href="#开发工作流" class="headerlink" title="开发工作流"></a>开发工作流</h2><p>在 <code>turbo.json</code> 中定义开发任务 (development task) 会告诉 Turborepo 将运行一个长期任务。这对于运行开发服务器、运行测试或构建应用程序等操作非常有用:</p> <figure class="highlight json"><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></pre></td><td class="code"><pre><span class="line"><span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"tasks"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"dev"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"cache"</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"persistent"</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span></span><br><span class="line"> <span class="punctuation">}</span></span><br><span class="line"> <span class="punctuation">}</span></span><br><span class="line"><span class="punctuation">}</span></span><br></pre></td></tr></table></figure> @@ -363,7 +363,7 @@ </ul> <p>一些脚本允许使用 <code>stdin</code> 在其中键入以进行交互式输入。使用<a target="_blank" rel="noopener" href="https://turbo.build/repo/docs/reference/configuration#ui">终端 UI</a>,可以选择一个任务,输入它,然后像往常一样使用 <code>stdin</code>。</p> <p>需要运行用于设置开发环境或预构建包的脚本。可以使用 <code>dependsOn</code> 确保这些任务在 <code>dev</code> 任务之前运行:</p> -<figure class="highlight json"><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><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"tasks"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"dev"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"cache"</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"persistent"</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"dependsOn"</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">"//#dev:setup"</span><span class="punctuation">]</span></span><br><span class="line"> <span class="punctuation">}</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"//#dev:setup"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"outputs"</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">".codegen/**"</span><span class="punctuation">]</span></span><br><span class="line"> <span class="punctuation">}</span></span><br><span class="line"> <span class="punctuation">}</span></span><br><span class="line"><span class="punctuation">}</span></span><br></pre></td></tr></table></figure> +<figure class="highlight json"><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><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"tasks"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"dev"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"cache"</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"persistent"</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"dependsOn"</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">"//#dev:setup"</span><span class="punctuation">]</span></span><br><span class="line"> <span class="punctuation">}</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"//#dev:setup"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"outputs"</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">".codegen/"</span><span class="punctuation">]</span></span><br><span class="line"> <span class="punctuation">}</span></span><br><span class="line"> <span class="punctuation">}</span></span><br><span class="line"><span class="punctuation">}</span></span><br></pre></td></tr></table></figure> <p>这里用的是 <a target="_blank" rel="noopener" href="https://turbo.build/repo/docs/crafting-your-repository/configuring-tasks#registering-root-tasks">Root Task</a>,但可以对 <a target="_blank" rel="noopener" href="https://turbo.build/repo/docs/crafting-your-repository/configuring-tasks#depending-on-a-specific-task-in-a-specific-package">packages 中的任意任务</a>使用相同的思路。</p> <h3 id="Watch-mode"><a href="#Watch-mode" class="headerlink" title="Watch mode"></a>Watch mode</h3><p>许多工具都有一个内置的 watcher,比如 <a target="_blank" rel="noopener" href="https://www.typescriptlang.org/docs/handbook/compiler-options.html#compiler-options"><code>tsc --watch</code></a>,它会响应源代码中的更改。有些则没有,<code>Turbo Watch</code> 为任何工具添加了依赖项感知的 Watcher。对源代码的更改将遵循在 <code>turbo.json</code> 中描述的 <a target="_blank" rel="noopener" href="https://turbo.build/repo/docs/core-concepts/package-and-task-graph#task-graph">Task Graph (任务图</a>),例如:</p> @@ -381,11 +381,11 @@ </blockquote> <h3 id="Environment-mode"><a href="#Environment-mode" class="headerlink" title="Environment mode"></a>Environment mode</h3><p>Turborepo 的 Environment Mode 允许控制哪些环境变量在运行时可用于任务:</p> <ul> -<li><a target="_blank" rel="noopener" href="https://turbo.build/repo/docs/crafting-your-repository/using-environment-variables#strict-mode">严格模式</a>(默认):将环境变量过滤为<strong>仅在</strong> <code>turbo.json</code> 的 <code>env</code> 和 <code>globalEnv</code> 键中指定的环境变量。</li> +<li><a target="_blank" rel="noopener" href="https://turbo.build/repo/docs/crafting-your-repository/using-environment-variables#strict-mode">严格模式</a>(默认):将环境变量过滤为仅在 <code>turbo.json</code> 的 <code>env</code> 和 <code>globalEnv</code> 键中指定的环境变量。</li> <li><a target="_blank" rel="noopener" href="https://turbo.build/repo/docs/crafting-your-repository/using-environment-variables#loose-mode">宽松模式</a>:允许进程的所有环境变量可用。</li> </ul> <h3 id="其他-2"><a href="#其他-2" class="headerlink" title="其他"></a>其他</h3><ul> -<li><p><code>.env</code> 文件非常适合在本地处理应用程序。<strong>Turborepo 不会将 .env 文件加载到任务的运行时</strong>中,而是让它们由框架或 <a target="_blank" rel="noopener" href="https://www.npmjs.com/package/dotenv"><code>dotenv</code></a> 等工具处理。但是,<code>turbo</code> 必须知道 <code>.env</code> 文件中值的更改,以便它可以将它们用于哈希。如果在两次构建之间更改 <code>.env</code> 文件中的变量,则 <code>build</code> 任务应该不会用上缓存。所以可以将其添加到 <code>input</code> 键中:</p> +<li><p><code>.env</code> 文件非常适合在本地处理应用程序。Turborepo 不会将 .env 文件加载到任务的运行时中,而是让它们由框架或 <a target="_blank" rel="noopener" href="https://www.npmjs.com/package/dotenv"><code>dotenv</code></a> 等工具处理。但是,<code>turbo</code> 必须知道 <code>.env</code> 文件中值的更改,以便它可以将它们用于哈希。如果在两次构建之间更改 <code>.env</code> 文件中的变量,则 <code>build</code> 任务应该不会用上缓存。所以可以将其添加到 <code>input</code> 键中:</p> <figure class="highlight json"><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></pre></td><td class="code"><pre><span class="line"><span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"globalDependencies"</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">".env"</span><span class="punctuation">]</span><span class="punctuation">,</span> <span class="comment">// All task hashes</span></span><br><span class="line"> <span class="attr">"tasks"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"build"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"inputs"</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">"$TURBO_DEFAULT$"</span><span class="punctuation">,</span> <span class="string">".env"</span><span class="punctuation">,</span> <span class="string">".env.local"</span><span class="punctuation">]</span> <span class="comment">// Only the `build` task hash</span></span><br><span class="line"> <span class="punctuation">}</span></span><br><span class="line"> <span class="punctuation">}</span></span><br><span class="line"><span class="punctuation">}</span></span><br></pre></td></tr></table></figure> </li> <li><p>不建议在存储库的根目录中使用 <code>.env</code> 文件。相反,建议将 <code>.env</code> 文件放入使用它们的包中。</p> diff --git a/2024/10/18/10-Tips-for-Productive-FSharp-Scripting/index.html b/2024/10/18/10-Tips-for-Productive-FSharp-Scripting/index.html index 5d3257c6..6c52f1b6 100644 --- a/2024/10/18/10-Tips-for-Productive-FSharp-Scripting/index.html +++ b/2024/10/18/10-Tips-for-Productive-FSharp-Scripting/index.html @@ -211,7 +211,7 @@ <p>You can also execute a single-line with Alt + ‘. I rarely use this option, but this can save you time because you don’t need to select the entire line of code.</p> </blockquote> <blockquote> -<p>In case the keyboard shortcuts to send code to FSI do not work anymore (ReSharper used to over-write them in the past), you can reset them in Visual Studio, by going to Tools / Options / Environment / Keyboard. The 2 commands you need to map are <strong>EditorContextMenus.CodeWindow.ExecuteInInteractive</strong> and <strong>EditorContextMenus.CodeWindow.ExecuteLineInInteractive</strong>.</p> +<p>In case the keyboard shortcuts to send code to FSI do not work anymore (ReSharper used to over-write them in the past), you can reset them in Visual Studio, by going to Tools / Options / Environment / Keyboard. The 2 commands you need to map are EditorContextMenus.CodeWindow.ExecuteInInteractive and EditorContextMenus.CodeWindow.ExecuteLineInInteractive.</p> </blockquote> <p>You can also use these shortcuts from a regular <code>.fs</code> file, which can be handy if you want to validate that a piece of code is behaving the way you want.</p> <blockquote> @@ -297,7 +297,7 @@ </blockquote> <h2 id="Tip-6-Use-Paket"><a href="#Tip-6-Use-Paket" class="headerlink" title="Tip 6: Use Paket"></a>Tip 6: Use <code>Paket</code></h2><p>The Nuget package manager is useful to consume existing packages. However, by default, Nuget stores assemblies in a folder that includes the package version number. This is very impractical for a script. In our example above, if <code>fsharp.data</code> gets an update, our script reference will be broken once we update the Nuget package:</p> <p><code>#r @"../packages/FSharp.Data.2.2.5/lib/net40/FSharp.Data.dll"</code></p> -<p>Fixing the script requires manually editing the version number in the path, which quickly becomes a pain. <a target="_blank" rel="noopener" href="https://fsprojects.github.io/Paket/"><strong>Paket</strong></a> provides a better experience, because it stores packages without the version number, in this case, under:</p> +<p>Fixing the script requires manually editing the version number in the path, which quickly becomes a pain. <a target="_blank" rel="noopener" href="https://fsprojects.github.io/Paket/">Paket</a> provides a better experience, because it stores packages without the version number, in this case, under:</p> <p><code>#r @"../packages/FSharp.Data/lib/net40/FSharp.Data.dll"</code></p> <p>Your scripts will now gracefully handle version number changes.</p> <p>If you end up consuming numerous packages, you can make your life even easier, by referencing paths where assemblies might be searched for, using <code>#I</code>:</p> @@ -335,7 +335,7 @@ <h2 id="Tip-10-Bonus-Material"><a href="#Tip-10-Bonus-Material" class="headerlink" title="Tip 10: Bonus Material"></a>Tip 10: Bonus Material</h2><p>Did you know that you could…</p> <ul> <li><a target="_blank" rel="noopener" href="https://channel9.msdn.com/Events/Visual-Studio/Visual-Studio-2015-Final-Release-Event/Six-Quick-Picks-from-Visual-F-40">debug an F# script? (around 0:12:35 in)</a></li> -<li><a target="_blank" rel="noopener" href="http://www.swensensoftware.com/fseye">inspect the objects in your FSI session with <strong>FsEye</strong>?</a></li> +<li><a target="_blank" rel="noopener" href="http://www.swensensoftware.com/fseye">inspect the objects in your FSI session with FsEye?</a></li> <li>change the FSI font size in Tools/Options/Environment/Fonts and Colors/Show Settings for/F# Interactive?</li> <li>add your own pretty-printer to FSI, <a target="_blank" rel="noopener" href="https://github.com/mathnet/mathnet-numerics/blob/master/src/FSharp/MathNet.Numerics.fsx">like this</a>?</li> <li>mess with your coworkers’ mental sanity, by executing <code>(*</code> (opening a multiline comment) in FSI? (credit: <a target="_blank" rel="noopener" href="https://twitter.com/tomaspetricek">Tomas</a>)</li> diff --git a/2024/10/18/Building-custom-fibers-library-in-FSharp/index.html b/2024/10/18/Building-custom-fibers-library-in-FSharp/index.html index ea5071f4..e1a6c2c8 100644 --- a/2024/10/18/Building-custom-fibers-library-in-FSharp/index.html +++ b/2024/10/18/Building-custom-fibers-library-in-FSharp/index.html @@ -203,14 +203,14 @@ <p>What we’re after, are more lightweight structures (less than 1kB), that can live fully in a user space, so that we can have even millions of them cooperating frequently with each other without heavy performance penalties.</p> <p>Before we begin, I think it’s good to discuss different designs. We’ll cover several different topics to be able to make more informed decisions, that we’re up to apply to our own solution.</p> <h3 id="Preemptive-vs-cooperative-scheduler"><a href="#Preemptive-vs-cooperative-scheduler" class="headerlink" title="Preemptive vs cooperative scheduler"></a>Preemptive vs cooperative scheduler</h3><p>Scheduler is a subsystem, which direct responsibility is to assign CPU core processing power to a particular fiber. It’s also responsible for coordinating fibers execution. The two most common categories of schedulers are preemptive and cooperative.</p> -<p>A <strong>preemptive</strong> scheduler is the one, that’s always in control of fiber execution. It’s able to decide on its own, when fiber can be started and stopped. The most obvious example of such is a thread scheduler existing on most operating systems.</p> +<p>A preemptive scheduler is the one, that’s always in control of fiber execution. It’s able to decide on its own, when fiber can be started and stopped. The most obvious example of such is a thread scheduler existing on most operating systems.</p> <p>Preemptive scheduler usually works in one of two ways:</p> <ul> <li>Time based scheduler takes a quant of CPU time and gives it to a given fiber, which ten can execute its logic until it reaches its execution time limit (of course, it can finish earlier). This is how OS thread scheduler, but also how Go goroutine scheduler works.</li> <li>Another variant is step-based scheduler, which splits fiber’s function body into series of (more or less equal) steps. Then each fiber is given a number of steps to execute before preemption occurs. Example of such is Erlang’s BEAM - it simply allows each process to execute up to 2000 “reductions”, where each reduction is basically a function call. <em>And since in Erlang there are no loops, only tail-recursive functions, this approach works well for long-living iterative processes as well.</em></li> </ul> -<p>One of the problems with preemptive schedulers is that they usually need some kind of involvement from the compiler or hosting virtual machine in order to work. For this reason, most of the fiber libraries use <strong>cooperative</strong> schedulers to perform their work.</p> -<p>A <strong>cooperative</strong> scheduler doesn’t have a concept of preemption - once started by the scheduler, a fiber will execute until it doesn’t give back the control willingly. This is often done with dedicated programming constructs, and often is known as yielding, parking or awaiting.</p> +<p>One of the problems with preemptive schedulers is that they usually need some kind of involvement from the compiler or hosting virtual machine in order to work. For this reason, most of the fiber libraries use cooperative schedulers to perform their work.</p> +<p>A cooperative scheduler doesn’t have a concept of preemption - once started by the scheduler, a fiber will execute until it doesn’t give back the control willingly. This is often done with dedicated programming constructs, and often is known as yielding, parking or awaiting.</p> <p>In cooperative variant, a fiber body is usually split into series of discrete steps, between which fiber gives control back to the scheduler.</p> <p>Keep in mind that these two are not mutually exclusive - a preemptive scheduler often provides a way for a fiber to return control back to it when it’s known that fiber won’t be executing any longer eg. because it has been put to sleep for a while.</p> <h3 id="Stackless-vs-stackful"><a href="#Stackless-vs-stackful" class="headerlink" title="Stackless vs. stackful"></a>Stackless vs. stackful</h3><p>A concept, that’s somewhat related to a topic above is the idea of stackless and stackful coroutines.</p> @@ -221,11 +221,11 @@ <li>Finite state machines - this variant is usually faster and can be encoded manually (example of such case is Akka actors), but for a human eye it usually doesn’t really read as a sequential step-by-step program execution, unless it has some support from the compiler itself (see: C# and Rust).</li> <li>Monadic sequencing via bind/flatMap operator, which is very popular in functional languages. While we cover it in more details in the rest of this blog post, for now it’s enough to say that it’s a way to chain callback-based behaviors together in a way, that resembles standard sequential code.</li> </ol> -<p>For sure one of the advantages of stackful coroutines is that they’re <em>mono-colored</em>: you can yield/continue coroutine execution from within any other function, while in the stackless variant splits your world into <em>two-colored</em> functions - synchronous and <strong>async</strong>hronous - where async one can be only called and yielded safely (without blocking underlying OS thread) from within another async function.</p> +<p>For sure one of the advantages of stackful coroutines is that they’re <em>mono-colored</em>: you can yield/continue coroutine execution from within any other function, while in the stackless variant splits your world into <em>two-colored</em> functions - synchronous and asynchronous - where async one can be only called and yielded safely (without blocking underlying OS thread) from within another async function.</p> <h3 id="Eager-vs-lazy-fibers"><a href="#Eager-vs-lazy-fibers" class="headerlink" title="Eager vs lazy fibers"></a>Eager vs lazy fibers</h3><p>We already mentioned two important events in fiber execution life cycle - starting and parking. Here I briefly discuss about different design decisions on when to start a fiber execution.</p> -<p><strong>Eager</strong> execution means, that fiber is started automatically after its creation. An example of such are Scala <code>Future[A]</code> and JavaScript <code>Promise</code>. Since execution process starts right away, we’re willingly resign from a certain degree of control over how or when to execute given fiber. Usually this is solved by wrapping a fiber creation into another function or lambda.</p> -<p><strong>Lazy</strong> execution is much more common and preferred way of work, as it allows us to separate place where we want to define our asynchronous sequence of steps from the place, where the execution details are defined. It’s used in C# TPL as well as pretty much in all functional languages implementations (excluding Scala futures mentioned earlier).</p> -<h3 id="Interruption"><a href="#Interruption" class="headerlink" title="Interruption"></a>Interruption</h3><p>There are also few decisions regarding premature escaping the fiber execution, also known as interruption/cancelation: one of them requires passing special object - a <strong>token</strong> - between method calls and explicit checking for its completion. It is how C# Tasks work. However putting such requirement onto the API user can be cumbersome and error-prone option. Therefore pretty much every other coroutine library either allows to direcly interrupt a fiber or (like in case of F# Async) passes cancelation tokens and check if they were triggered under the hood.</p> +<p>Eager execution means, that fiber is started automatically after its creation. An example of such are Scala <code>Future[A]</code> and JavaScript <code>Promise</code>. Since execution process starts right away, we’re willingly resign from a certain degree of control over how or when to execute given fiber. Usually this is solved by wrapping a fiber creation into another function or lambda.</p> +<p>Lazy execution is much more common and preferred way of work, as it allows us to separate place where we want to define our asynchronous sequence of steps from the place, where the execution details are defined. It’s used in C# TPL as well as pretty much in all functional languages implementations (excluding Scala futures mentioned earlier).</p> +<h3 id="Interruption"><a href="#Interruption" class="headerlink" title="Interruption"></a>Interruption</h3><p>There are also few decisions regarding premature escaping the fiber execution, also known as interruption/cancelation: one of them requires passing special object - a token - between method calls and explicit checking for its completion. It is how C# Tasks work. However putting such requirement onto the API user can be cumbersome and error-prone option. Therefore pretty much every other coroutine library either allows to direcly interrupt a fiber or (like in case of F# Async) passes cancelation tokens and check if they were triggered under the hood.</p> <h2 id="Implementation"><a href="#Implementation" class="headerlink" title="Implementation"></a>Implementation</h2><p>Since we talked a bit about various approaches, let’s get to the meat of this blog post: implementing our own coroutine library in F#. So, what properties will it have?:</p> <ol> <li>We use cooperative scheduling (we don’t want to tweak the compiler) of stackless fibers with support from F# computation expression for nice syntax.</li> @@ -268,7 +268,7 @@ <p>Now, since our cancellation is not explicit, we need to deal with few things:</p> <ol> <li>Whenever parent fiber is cancelled, all child fibers it spawned are also cancelled.</li> -<li>Whenever we cancel a fiber that loose the race, we <strong>don’t want</strong> to accidentally cancel a token of its parent.</li> +<li>Whenever we cancel a fiber that loose the race, we don’t want to accidentally cancel a token of its parent.</li> </ol> <p>This behavior implies at least using two separate tokens, however in practice it will be more pragmatic to make our <code>Cancel</code> token work as a tree hierarchy - this way we can easily keep track of things and support more complex scenarios.</p> <figure class="highlight fsharp"><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><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">[<Sealed;AllowNullLiteral>]</span></span><br><span class="line"><span class="keyword">type</span> <span class="title class_">Cancel</span>(parent<span class="operator">:</span> Cancel) <span class="operator">=</span></span><br><span class="line"> <span class="keyword">let</span> <span class="keyword">mutable</span> flag<span class="operator">:</span> <span class="type">int</span> <span class="operator">=</span> <span class="number">0</span></span><br><span class="line"> <span class="keyword">let</span> <span class="keyword">mutable</span> children<span class="operator">:</span> Cancel <span class="type">list</span> <span class="operator">=</span> []</span><br><span class="line"> <span class="keyword">new</span>() <span class="operator">=</span> Cancel(<span class="literal">null</span>)</span><br><span class="line"> <span class="comment">/// Check if token was cancelled</span></span><br><span class="line"> <span class="keyword">member</span> __.Cancelled <span class="operator">=</span> flag <span class="operator">=</span> <span class="number">1</span></span><br><span class="line"> <span class="comment">/// Remove child token</span></span><br><span class="line"> <span class="keyword">member</span> <span class="keyword">private</span> __.RemoveChild(child) <span class="operator">=</span> </span><br><span class="line"> <span class="keyword">let</span> <span class="keyword">rec</span> loop child <span class="operator">=</span></span><br><span class="line"> <span class="keyword">let</span> children' <span class="operator">=</span> children</span><br><span class="line"> <span class="keyword">let</span> nval <span class="operator">=</span> children' <span class="operator">|></span> List.filter ((<span class="operator"><></span>) child)</span><br><span class="line"> <span class="keyword">if</span> <span class="built_in">not</span> (obj.ReferenceEquals(children', Interlocked.CompareExchange(<span class="operator">&</span>children, nval, children')))</span><br><span class="line"> <span class="keyword">then</span> loop child</span><br><span class="line"> <span class="keyword">if</span> <span class="built_in">not</span> (List.isEmpty children) <span class="keyword">then</span> loop child</span><br><span class="line"> <span class="comment">/// Create a new child token and return it.</span></span><br><span class="line"> <span class="keyword">member</span> this.AddChild () <span class="operator">=</span></span><br><span class="line"> <span class="keyword">let</span> <span class="keyword">rec</span> loop child <span class="operator">=</span></span><br><span class="line"> <span class="keyword">let</span> children' <span class="operator">=</span> children</span><br><span class="line"> <span class="keyword">if</span> (obj.ReferenceEquals(children', Interlocked.CompareExchange(<span class="operator">&</span>children, child<span class="operator">::</span>children', children')))</span><br><span class="line"> <span class="keyword">then</span> child</span><br><span class="line"> <span class="keyword">else</span> loop child</span><br><span class="line"> loop (Cancel this)</span><br><span class="line"> <span class="comment">/// Cancel a token</span></span><br><span class="line"> <span class="keyword">member</span> this.Cancel() <span class="operator">=</span></span><br><span class="line"> <span class="keyword">if</span> Interlocked.Exchange(<span class="operator">&</span>flag, <span class="number">1</span>) <span class="operator">=</span> <span class="number">0</span> <span class="keyword">then</span></span><br><span class="line"> <span class="keyword">for</span> child <span class="keyword">in</span> Interlocked.Exchange(<span class="operator">&</span>children, []) <span class="keyword">do</span> child.Cancel()</span><br><span class="line"> <span class="keyword">if</span> <span class="built_in">not</span> (isNull parent) <span class="keyword">then</span> parent.RemoveChild(this)</span><br><span class="line"></span><br></pre></td></tr></table></figure> @@ -379,7 +379,7 @@ <figure class="highlight fsharp"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> <span class="keyword">rec</span> run () <span class="operator">=</span></span><br><span class="line"> <span class="keyword">match</span> Seq.tryHead timeline <span class="keyword">with</span></span><br><span class="line"> <span class="operator">|</span> <span class="literal">None</span> <span class="operator">-></span> running <span class="operator"><-</span> <span class="literal">false</span></span><br><span class="line"> <span class="operator">|</span> <span class="literal">Some</span> (KeyValue(time, bucket)) <span class="operator">-></span></span><br><span class="line"> timeline <span class="operator"><-</span> Map.remove time timeline</span><br><span class="line"> currentTime <span class="operator"><-</span> time</span><br><span class="line"> <span class="keyword">for</span> fn <span class="keyword">in</span> List.rev bucket <span class="keyword">do</span> </span><br><span class="line"> fn () </span><br><span class="line"> run ()</span><br></pre></td></tr></table></figure> <p>We’ll try to pick the first entry from the timeline - since here we use F# map, which is sorted in ascending order, we know that first entry is the one with the shortest execution timeout. We update our “current” time to match the expected one we calculated earlier, and finally we execute all functions scheduled at that time and repeat the loop all over until we eventually run out of scheduled actions.</p> -<p>Now here’s the trick - we use <code>List.rev</code> to execute functions in the same order in which they were scheduled, because we want our tests to be deterministic and our bugs to be reproducible. However this is not the only strategy - <strong>since we know that functions in the same bucket could as well be executing in parallel, we could shuffle them around in different permutations for early discovery of some data races!</strong> I’ll won’t dive into it, but leave that idea as food for thoughts for you.</p> +<p>Now here’s the trick - we use <code>List.rev</code> to execute functions in the same order in which they were scheduled, because we want our tests to be deterministic and our bugs to be reproducible. However this is not the only strategy - since we know that functions in the same bucket could as well be executing in parallel, we could shuffle them around in different permutations for early discovery of some data races! I’ll won’t dive into it, but leave that idea as food for thoughts for you.</p> <p>One last note about the test scheduler is that isolating it from the actual physical clock means, we cannot trust our time functions (like <code>DateTime.UtcNow</code>) any longer. This shouldn’t really be an issue though - because relying on physical time would potentially make our tests indeterministic, we didn’t want to use it anyway, right?</p> <p>However, we need to be able to obtain current time from the scheduler, so we need to extend its API:</p> <figure class="highlight fsharp"><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">type</span> <span class="title class_">IScheduler</span> <span class="operator">=</span></span><br><span class="line"> <span class="keyword">abstract</span> UtcNow<span class="operator">:</span> <span class="type">unit</span> <span class="operator">-></span> <span class="type">unit</span></span><br><span class="line"> <span class="comment">// ... other methods</span></span><br><span class="line"> </span><br><span class="line"><span class="keyword">type</span> <span class="title class_">TestScheduler</span>() <span class="operator">=</span></span><br><span class="line"> <span class="keyword">let</span> <span class="keyword">mutable</span> currentTime <span class="operator">=</span> DateTime.UtcNow.Ticks</span><br><span class="line"> <span class="comment">// ... rest of the implementation</span></span><br><span class="line"> <span class="keyword">interface</span> IScheduler <span class="keyword">with</span></span><br><span class="line"> <span class="keyword">member</span> __.UtcNow() <span class="operator">=</span> DateTime(currentTime)</span><br><span class="line"> <span class="comment">// ... other methods</span></span><br></pre></td></tr></table></figure> diff --git a/2024/10/18/Dealing-with-complex-dependency-injection-in-FSharp/index.html b/2024/10/18/Dealing-with-complex-dependency-injection-in-FSharp/index.html index 0516c6bd..eb1d0c53 100644 --- a/2024/10/18/Dealing-with-complex-dependency-injection-in-FSharp/index.html +++ b/2024/10/18/Dealing-with-complex-dependency-injection-in-FSharp/index.html @@ -227,7 +227,7 @@ <li>Maybe aside of the logger we may be needing a separate telemetry mechanism to count number of incoming request or password validation failures? That means another parameter.</li> <li>Salt generation is pseudo-random process - it we want our function to be deterministic, we should probably parametrize it over explicitly passed <code>Random</code> as well.</li> </ul> -<p>As you see, what seemed to be simple task at the beginning can quickly blow up out of proportion. As the number of arguments grows, the more nasty our wiring code eventually becomes. Quite common pattern is to hide all of that nastiness under the carpet a.k.a. <strong>composition root</strong>. However this doesn’t have to be the case.</p> +<p>As you see, what seemed to be simple task at the beginning can quickly blow up out of proportion. As the number of arguments grows, the more nasty our wiring code eventually becomes. Quite common pattern is to hide all of that nastiness under the carpet a.k.a. composition root. However this doesn’t have to be the case.</p> <p>Below we’ll cover another approach for dealing with dependencies - inspired by Scala <a target="_blank" rel="noopener" href="https://medium.com/@pascal.mengelt/what-are-the-benefits-of-the-zio-modules-with-zlayers-3bf6cc064a9b?ref=bartoszsypytkowski.com">ZIO</a> library - using incremental steps, from first principles to monadic bindings.</p> <h2 id="Managing-dependencies-beyond-partial-application"><a href="#Managing-dependencies-beyond-partial-application" class="headerlink" title="Managing dependencies beyond partial application"></a>Managing dependencies beyond partial application</h2><p>Let’s start from how our code from above will eventually look like at the end of this step:</p> <figure class="highlight fsharp"><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><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> changePass env <span class="operator">=</span> <span class="keyword">fun</span> req <span class="operator">-></span> <span class="keyword">task</span> {</span><br><span class="line"> <span class="keyword">let!</span> user <span class="operator">=</span> Db.fetchUser env req.UserId</span><br><span class="line"> <span class="keyword">if</span> user.Hash <span class="operator">=</span> bcrypt user.Salt req.OldPass <span class="keyword">then</span></span><br><span class="line"> <span class="keyword">let</span> salt <span class="operator">=</span> Random.bytes env <span class="number">32</span></span><br><span class="line"> <span class="keyword">do!</span> Db.updateUser <span class="keyword">env</span> { user <span class="keyword">with</span> Salt <span class="operator">=</span> salt; Hash <span class="operator">=</span> bcrypt salt req.NewPass }</span><br><span class="line"> Log.info env <span class="string">"Changed password for user %i"</span> user.Id</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">Ok</span> ()</span><br><span class="line"> <span class="keyword">else</span> </span><br><span class="line"> Log.error env <span class="string">"Password change unauthorized: user %i"</span> user.Id</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">Error</span> <span class="string">"Old password is invalid"</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure> @@ -255,8 +255,8 @@ <li>It doesn’t impose specific restrictions on libraries and frameworks.</li> </ul> <p>Now we could as well stop here - IMHO this approach is already good and useful for most cases. We can also try to push it further. As you’ve seen, our code now requires quite a lot of <code>env</code> passing around. Could we do something about this? It turns out that yes, we could.</p> -<h2 id="Reader-monad"><a href="#Reader-monad" class="headerlink" title="Reader monad"></a>Reader monad</h2><p>Before we continue: <strong>what we’re going to cover now is less useful in terms of current state of F# ecosystem for the reasons I’ll mention later</strong>.</p> -<p>The pattern we’ll use here is known as a <strong><a target="_blank" rel="noopener" href="https://fsharpforfunandprofit.com/posts/elevated-world-6/?ref=bartoszsypytkowski.com">Reader Monad</a></strong>. While it’s useful in certain situations, it’s not widely used - IMO it’s fault lies in the name itself, which somehow managed to sound both borderline meaningless and scary in ears of many developers.</p> +<h2 id="Reader-monad"><a href="#Reader-monad" class="headerlink" title="Reader monad"></a>Reader monad</h2><p>Before we continue: what we’re going to cover now is less useful in terms of current state of F# ecosystem for the reasons I’ll mention later.</p> +<p>The pattern we’ll use here is known as a <a target="_blank" rel="noopener" href="https://fsharpforfunandprofit.com/posts/elevated-world-6/?ref=bartoszsypytkowski.com">Reader Monad</a>. While it’s useful in certain situations, it’s not widely used - IMO it’s fault lies in the name itself, which somehow managed to sound both borderline meaningless and scary in ears of many developers.</p> <p>The rest of this blog post will be introduction to this style in F#, however focused solely around problem of dependency management - we’ll ignore other aspects of monads.</p> <p>We’ll going to reuse our environment type from above, but now encode it directly into another type we’ll call <code>Effect</code>. Since I’ve mentioned that our pattern has M-word in it, you can safely assume that our handler’s logic will be defined as a lazy sequence of steps to be executed (sounds almost like async/await). In F# we’ll sugar them by using custom computation expression (I’m going to call it <code>effect { ... }</code>) returning our effect type, which we’ll define as:</p> <figure class="highlight fsharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">[<Struct>]</span> <span class="keyword">type</span> <span class="title class_">Effect</span><span class="operator"><</span><span class="symbol">'env</span>, <span class="symbol">'out</span><span class="operator">></span> <span class="operator">=</span> Effect <span class="keyword">of</span> (<span class="symbol">'env</span> <span class="operator">-></span> <span class="symbol">'out</span>)</span><br></pre></td></tr></table></figure> diff --git a/2024/11/26/如何管理用户界面中的危险操作/index.html b/2024/11/26/如何管理用户界面中的危险操作/index.html index 23f18fa4..96e7d42a 100644 --- a/2024/11/26/如何管理用户界面中的危险操作/index.html +++ b/2024/11/26/如何管理用户界面中的危险操作/index.html @@ -204,7 +204,7 @@ <p>界面是用户与系统通信的中介层,界面中的交互通常需要用户执行某些操作。不同的操作可能会导致不同的结果,其中可能有一些对于双方来说都非常重要甚至危险的操作。</p> <p>所以经常需要提供额外的保护措施来“保护”用户执行一些危险甚至无法恢复的操作。<br>这里的“保护”并不是完全的阻止,否则这个操作也没有存在的必要。</p> <blockquote> -<p>“良好的错误消息很重要,但最好的设计<strong>首先会小心地防止问题发生</strong>。要么消除容易出错的情况,要么检查它们并在用户承诺操作之前向他们提供确认选项。”</p> +<p>“良好的错误消息很重要,但最好的设计首先会小心地防止问题发生。要么消除容易出错的情况,要么检查它们并在用户承诺操作之前向他们提供确认选项。”</p> </blockquote> <h2 id="什么是危险行为?"><a href="#什么是危险行为?" class="headerlink" title="什么是危险行为?"></a>什么是危险行为?</h2><p>危险行为并不意味着要删除某些内容,具体的危险行为应该由系统的“领域”来定义,例如:</p> <ul> |
