<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="zh-Hans"><generator uri="https://jekyllrb.com/" version="4.3.3">Jekyll</generator><link href="https://ioover.net/feed.xml" rel="self" type="application/atom+xml" /><link href="https://ioover.net/" rel="alternate" type="text/html" hreflang="zh-Hans" /><updated>2026-08-16T09:02:29+00:00</updated><id>https://ioover.net/feed.xml</id><title type="html">I/O OVER</title><subtitle>一个不起眼的节点位于赛博空间的角落，发出的点点光亮看起来格外黯淡。</subtitle><author><name>Train Train</name></author><entry><title type="html">观察一下 LLM 的语言模式</title><link href="https://ioover.net/talk/llm-pattern/" rel="alternate" type="text/html" title="观察一下 LLM 的语言模式" /><published>2026-07-19T09:14:41+00:00</published><updated>2026-07-19T09:14:41+00:00</updated><id>https://ioover.net/talk/llm-pattern</id><content type="html" xml:base="https://ioover.net/talk/llm-pattern/"><![CDATA[<p>本来想随便写几个字的，写多了，正好博客重建了，就发这里吧。这些年也在 Telegram 频道写了不少东西，看看能不能搬点过来。</p>

<p>我有个假说，LLM生成文本的种种怪味，感觉可能不是靠训练能解决的，更像是源自 LLM 自回归原理。</p>

<p>人是心里有前语言的思绪，然后想办法化为语言说出来的。LLM 并非这样，或者说不是很这样。它在生成下一 token 的过程中是有前语言的「思绪」的，也就是各层的激活，但是非语言的推理中的激活是很快消散的，不会持久化到思维过程。持久化的思考过程都必须落成 token（至少 2026 年工业界主流似乎是这样的）。所以语言这东西对于 LLM 带上了一个「辅助推理的工具」的属性。</p>

<p>我最近最恨「不是…而是…」的表达，看到就反味。人当然也会用「不是…而是…」，但明显不一样。观察人写的「不是…而是…」会发现是真的有点意外的转折，至少可以看出作者想要表达一个有点意外的转折。两个小句的前后是给出额外信息量的。而LLM喜欢用的「不是…而是…」，前面的「不是」往往是一句没有信息量的废话，生成这个 token 仅仅是一种辅助结构，不是有意图的写作结构，而是一种蓄力<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup>。感觉像是为了激发后面的「而是」部分，这部分在训练的时候会期待给出一个高 reward 分数的结果。若是运气好的话后面的「而是」确实能有点信息甚至惊喜，失败的话后面的「而是」也是个废话空话。无论如何，这时候LLM已经没有办法回头去改掉冗余的「不是」了。</p>

<p>英文中比较刺眼的 em-dash 可能也有类似的作用，在思维中给LLM一个「蓄力」的信号，让它在符号后想办法拿到高评分。</p>

<p>下面是<a href="https://www.zhihu.com/question/2060287898231035691/answer/2060401822695682180">知乎上一个人</a>总结的两条，也可以看出很明显的「辅助蓄力」的成分：</p>

<blockquote>
  <p>一、假起兴</p>

  <p>“有个很好玩的XX”“特别有意思”“特别反直觉的是”……姑且叫做虚假的起兴，就是没话找话硬装惊喜。</p>

  <p>起兴是个表达技巧，目的是吸引受众注意力，一个比较典型的例子是“我有一个好消息一个坏消息，你想先听哪个？”</p>

  <p>人类表达者用这样的话术太多会让人觉得假模假式，因为你能感觉到对方不是发自内心，而是介于口癖和表达套路之间的习惯。现在AI也学会了这种假模假式，会在没有意外的地方，模拟意外的语气。</p>

  <p>AI进入写作模式的时候更容易出现这种拿腔拿调的情况。</p>

  <p>二、说狠话</p>

  <p>比如描述趋势，明明本来是“线性下降/逐渐下降”，它要说“崩溃”“断崖式下跌”。</p>

  <p>这种下意识的语言通胀导致AI无法准确传递程度信息。</p>

  <p>说狠话另一个更严重的后果，就是AI自己给自己挖坑，也就是通过生成有偏差的上下文从而滑向更严重的胡说八道。因为LLM是自回归生成的，前面一旦冒出夸张断言，它生成后续内容的时候，为了跟前文保持语义一致，会顺着这个错误继续往下编，让错误滚雪球。</p>
</blockquote>

<p>抱着这种观念，就比较容易鉴别出文本的AI味了，漫山遍野哇！看个小黄文中间插了一句可疑的「不是…而是…」，就被泼了冷水。</p>

<p>不过这个理论有个问题，就是现在已经分为推理阶段和实际生成阶段了，理论上可以在实际生成阶段去掉冗余吧。</p>

<p>本文全手敲，找 LLM 核查了一下有没有事实错误，LLM 说有，我决定无视它，因为我任性。</p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>注意这里是故意的用了人类写的「不是…而是…」结构。 <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Train Train</name></author><category term="talk" /><summary type="html"><![CDATA[本来想随便写几个字的，写多了，正好博客重建了，就发这里吧。这些年也在 Telegram 频道写了不少东西，看看能不能搬点过来。 我有个假说，LLM生成文本的种种怪味，感觉可能不是靠训练能解决的，更像是源自 LLM 自回归原理。 人是心里有前语言的思绪，然后想办法化为语言说出来的。LLM 并非这样，或者说不是很这样。它在生成下一 token 的过程中是有前语言的「思绪」的，也就是各层的激活，但是非语言的推理中的激活是很快消散的，不会持久化到思维过程。持久化的思考过程都必须落成 token（至少 2026 年工业界主流似乎是这样的）。所以语言这东西对于 LLM 带上了一个「辅助推理的工具」的属性。 我最近最恨「不是…而是…」的表达，看到就反味。人当然也会用「不是…而是…」，但明显不一样。观察人写的「不是…而是…」会发现是真的有点意外的转折，至少可以看出作者想要表达一个有点意外的转折。两个小句的前后是给出额外信息量的。而LLM喜欢用的「不是…而是…」，前面的「不是」往往是一句没有信息量的废话，生成这个 token 仅仅是一种辅助结构，不是有意图的写作结构，而是一种蓄力1。感觉像是为了激发后面的「而是」部分，这部分在训练的时候会期待给出一个高 reward 分数的结果。若是运气好的话后面的「而是」确实能有点信息甚至惊喜，失败的话后面的「而是」也是个废话空话。无论如何，这时候LLM已经没有办法回头去改掉冗余的「不是」了。 英文中比较刺眼的 em-dash 可能也有类似的作用，在思维中给LLM一个「蓄力」的信号，让它在符号后想办法拿到高评分。 下面是知乎上一个人总结的两条，也可以看出很明显的「辅助蓄力」的成分： 一、假起兴 “有个很好玩的XX”“特别有意思”“特别反直觉的是”……姑且叫做虚假的起兴，就是没话找话硬装惊喜。 起兴是个表达技巧，目的是吸引受众注意力，一个比较典型的例子是“我有一个好消息一个坏消息，你想先听哪个？” 人类表达者用这样的话术太多会让人觉得假模假式，因为你能感觉到对方不是发自内心，而是介于口癖和表达套路之间的习惯。现在AI也学会了这种假模假式，会在没有意外的地方，模拟意外的语气。 AI进入写作模式的时候更容易出现这种拿腔拿调的情况。 二、说狠话 比如描述趋势，明明本来是“线性下降/逐渐下降”，它要说“崩溃”“断崖式下跌”。 这种下意识的语言通胀导致AI无法准确传递程度信息。 说狠话另一个更严重的后果，就是AI自己给自己挖坑，也就是通过生成有偏差的上下文从而滑向更严重的胡说八道。因为LLM是自回归生成的，前面一旦冒出夸张断言，它生成后续内容的时候，为了跟前文保持语义一致，会顺着这个错误继续往下编，让错误滚雪球。 抱着这种观念，就比较容易鉴别出文本的AI味了，漫山遍野哇！看个小黄文中间插了一句可疑的「不是…而是…」，就被泼了冷水。 不过这个理论有个问题，就是现在已经分为推理阶段和实际生成阶段了，理论上可以在实际生成阶段去掉冗余吧。 本文全手敲，找 LLM 核查了一下有没有事实错误，LLM 说有，我决定无视它，因为我任性。 注意这里是故意的用了人类写的「不是…而是…」结构。 &#8617;]]></summary></entry><entry><title type="html">小鹤输入法学习和使用心得</title><link href="https://ioover.net/talk/flypy/" rel="alternate" type="text/html" title="小鹤输入法学习和使用心得" /><published>2020-10-22T09:29:26+00:00</published><updated>2020-10-22T09:29:26+00:00</updated><id>https://ioover.net/talk/flypy</id><content type="html" xml:base="https://ioover.net/talk/flypy/"><![CDATA[<h2>双拼篇</h2>

<p>一直希望提高打字速度和准确率，于是打算学习双拼，考察了各种方案最后选了<a href="https://www.flypy.com/index.html">小鹤</a>。原因如下：</p>

<ul>
  <li>本身方案口碑比较好。据说布局比较合理。</li>
  <li>各种系统默认对小鹤的支持也挺好。除了 Windows 10 开发组和作者发生了些不愉快，结果是 Windows 10 直接做了个自定义任意双拼布局的设置，虽然有点麻烦但是还是能<a href="https://ifttl.com/add-flypy-to-win10-microsoft-pinyin-and-other-configuration/">设成小鹤的</a>。</li>
  <li>习惯了双拼还可以进一步去学习双形，经过训练可以达到五笔的速度和准确率。详细请看下面的双形小节。</li>
</ul>

<p>在决定切换之前最好先在打字测速软件<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup>里面测试一下全拼打字速度，以供未来对比。我就没有预先测试速度，所以这篇文章也没办法给出速度对比了。</p>

<aside>
  <p>在一切开始之前建议还是纠正一下指法，<a href="https://www.typingclub.com/">这个网站</a>非常不错，顺便可以练习英文输入的能力。</p>
</aside>

<p>首先打印一个键位图放在容易看到的地方，这两周你会非常需要时不时盯着键位图的。最好在第一天就把所有设备都换成双拼，强迫自己用双拼，不然会前功尽弃。切换一开始会很有挫败感，这是正常的，别难过，你是最棒的。</p>

<p>从全拼切换到双拼需要大概两周的时间习惯，这两周打字效率当然会急速下降。两周以后，虽然有些键位还是会弄错（一般是“O”和“Z”，“Q”和“P”还有“T”），但已经能流畅打字了，接下来就会慢慢体会到双拼的改变了。</p>

<p>个人感觉用双拼更加舒服了。和速度关系不大。举个例子，比如说我打「上」这个字的时候需要输入 “shang” 如果有一个键按错了比如变成 “sjang”，我需要退格四次然后重新按 “hang” 四个键。双拼只需要输入 “uh”，就算 u 打错了也只需要退格两次重按两键。这种优势在手机输入中最明显了，双拼特别特别适合手机全键盘输入。</p>

<p>全拼到双拼的速度提升其实不是非常大的，日常使用中这点速度提升可能并没有多少作用。重要的是体验提升，打字是你每天都要重复上千字的事情，体验提升一点就很值得了。</p>

<p>但对我来说最难受的是选候选字，所有人都经历过在“shi”这类发音中选字的痛苦，其实我学小鹤一开始就是盯着「音形」去的。于是「双形篇」突入。</p>

<h2>双形篇</h2>

<p>小鹤的作者为双形设计了独立的音形结合输入法，所以要用双形最好用他做的输入法，但如果你是搜狗输入法用户，也可以学学双形，双形在搜狗输入法中似乎可以作为辅助码来使用。</p>

<p>小鹤的双形是一个字从首末拆出两个字根，再添到双拼的后面。比如说「字」这个字就是“zi”（双拼）+“bz”，“b”代表「宝盖头」，“z”是「子」。每个字都有这样由四个字母构成的编码。</p>

<p>可能你会觉得现在每个字都要按四个按键很麻烦，但其实很少需要打全四个的，四个码是编码上限。作者按照字频将常用的字都放在 1-3 长度的编码中。比如说「字」这个字，打“z”的时候出的字是「在」，打“zi”的时候就是「字」了，和双拼一样，而打全“zibz”反而出来的是「孳」，原因是虽然「字」和「孳」都是这个码，但「字」前面已经出现过了，所以“zibz”这个码位就让给「孳」了<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">2</a></sup>。在这种方式之下打字的重码率降到了很低，即使偶尔需要选字也只有一两个选项。</p>

<p>小鹤也为常用词语编码了四键编码，二字词只需要和双拼一样输入就能打出来。大多数时候打字的感受是「有时会多加一两个键、需要频繁按空格键的双拼，并且不用选字」。于是用户在普通的双拼和小鹤音形间切换是接近无缝的，在学习音形的时候如果有需要就可以临时切换回普通双拼，我在手机上就是用的普通的双拼输入。</p>

<p>双拼的学习主要是肌肉记忆。照着键位图强行用，肌肉记忆形成了就行了。而<a href="http://help.flypy.com/xhrm/1960602">音形的学习</a>则需要你<a href="https://www.flypy.com/xing.html">记忆一些「部件」，学习编码规则</a>，如果用 Rime 的话还需要一点点技术能力。提升熟练度的时间也相对比较慢，我现在用了几个月有时还是磕磕绊绊的。</p>

<h3>拆字规则</h3>

<p>比起五笔等笔画输入法，小鹤的先进之处是作者制定了一套规则使得任意一个字都能唯一地拆出相同的编码，极大减少了记忆负担也减少了重码率。</p>

<p><a href="https://www.flypy.com/xing.html">作者写的规则</a>还算挺好懂。除了规则以外，你必须特地记忆的只有「部件」了，「部件」是所有拆字的基础，一定要好好记住。最好打印下图或者设成壁纸。</p>

<p><img src="/media/小鹤部件.png" alt="小鹤部件" /></p>

<p>特别要注意的是那些内部包含别的部件的部件。比如说「立」这个字，总是会忘记「立」自身是个部件，拆「靖」这样的字的时候就容易把第一个部件取成文字头。</p>

<p>小鹤的拆字规则实际使用中需要递归地去判断字中某个部分是否是「小字」，虽然不难，但不熟悉的拆分有时是会卡壳一下。用得多了自然会记住常用字的拆分。习惯拆分了以后最大的敌人还是提笔忘字。</p>

<p>这里有<a href="http://react.xhup.club/search">文字拆分的在线查询</a>，非常好用。有个小窍门是如果不确定一个字是不是小字，可以查询一下看看，如果没有出现部件而全都是笔画的字就是「小字」。</p>

<h3>输入法</h3>

<p>作者第一级支持的输入法有很多独特的功能但是仅限 Windows，所以我用的是 Rime 版本，这一节记录一下我是怎么配置 Rime 的。</p>

<p>这类打单字能力很强的输入法是不需要如云词库、整句输入之类的功能的（这些正好是 Rime 的弱项）。词库都是固定记录的，新词的收录是谨慎而保守的，即便有些编码是空的正好可以放词进去也不一定会加，所以可以到<a href="https://bbs.flypy.com/forum.php?mod=viewthread&amp;tid=19">小鹤论坛的这个帖子</a>看看别的用户提议了哪些词，自己选择需要的加到词库文件（<code>flypy_user.txt</code>）里面。</p>

<p>我要在 macOS 和 Windows 同时使用，自然会希望能同步设置和词库。Rime 版本中，用户词库是文本文件。输入法配置是 YAML 的，所以把词库和配置放到云同步的文件夹，再用软链接的方式连接到配置文件夹就可以了（PowerShell 为例）：</p>

<pre><code class="language-powershell">New-Item -Path $home\AppData\Roaming\Rime\flypy_user.txt -ItemType SymbolicLink -Value $home\Dropbox\Rime\flypy_user.txt
</code></pre>

<p>有一个小技巧是，由于<a href="https://github.com/rime/home/wiki/CustomizationGuide#%E5%AE%9A%E8%A3%BD%E6%8C%87%E5%8D%97">Rime 的配置支持 patch</a>，能够在不覆盖原有配置的情况下改写设置。这样做的好处是更新的时候用新版本的配置文件覆盖旧的就新了。小鹤的配置文件是 <code>flypy.schema.yaml</code> 和 <code>flypyplus.schema.yaml</code> ，patch 的配置就名为 <code>flypy.custom.yaml</code> 和 <code>flypyplus.custom.yaml</code>，可以同一个文件软连接到这两个文件去。</p>

<aside>
  <p>flypy 是「小鹤音形」，flypyplus 是「小鹤音形+」，区别是如果一个字的全码之前有过简码的话（比如之前例子中的「字」字），「小鹤音形」输入全码不会显示那个字，而「小鹤音形+」会以候选框的方式都显示出来。个人建议学习的时候用「小鹤音形+」，熟练的时候再试着切换。（我还没切换）</p>
</aside>

<p><code>flypy.custom.yaml</code>  中内容大概是这样的：</p>

<pre><code class="language-yaml">patch:
  style/horizontal: false
  menu/page_size: 6
  speller/max_code_length: 32 # 混杂英文输入的时候稍微流畅点，看运气
  key_binder/bindings:
    - {accept: bracketleft, send: Page_Up, when: paging} # [上翻页
    - {accept: bracketright, send: Page_Down, when: has_menu} # ]下翻页
    - {accept: comma, send: comma, when: paging} # 注销逗号翻页
    - {accept: period, send: period, when: has_menu} # 注销句号翻页
</code></pre>

<h3>练习</h3>

<p>就像<a href="https://www.bilibili.com/video/BV1at411Y7c1">这个视频</a>展现的，小鹤音形能达到很高的键入速度，但这需要刻意去练习。这是<a href="http://help.flypy.com/zhiyin/1958969">作者写的练习方式</a>。</p>

<p>下载<a href="http://flypy.ys168.com/">小鹤的网盘</a>里面提供的「<a href="http://ys-f.ys168.com/116124336/l524K6J3N75MLUiTwstW/小鹤专用添雨跟打器.zip">小鹤专用添雨跟打器.zip</a>」打开后，在菜单里面选择「发文」，选择常用500字，字数限定在每段10字，不要点乱序。打字时先按 Ctrl + R 打乱当前段落的顺序。作者推荐的的速度标准是「击键超过6换下一组」意思是要达到打字时每秒按6键的速度，并且不能有错误和退格回改。这一开始看起来是不可能的任务，其实是不难做到的，只是需要时间。到了5以后其实多重复就能上6了。不过这种事情也是有边际效益递减的，为了提高到击键6所花的时间成本不一定值得，日常使用的话把目标击键数设为5就挺好的。</p>

<p>打字这种重复练习，反复训练以后速度会逐渐提高，这其中是有乐趣的。有许多人把打文章作为一种娱乐，还时常在打字群里开展比赛，现在的打字软件就是为打字群准备的。<a href="https://www.jsxiaoshi.com/index.php/Home/Rank/saiwen">这里有一个打字软件提供的排行榜</a><sup id="fnref:3" role="doc-noteref"><a href="#fn:3" class="footnote" rel="footnote">3</a></sup>。我已经很久没练习了，大概只练了前50个字左右。练习很花时间，十个字要打到击键6我经常需要两个小时左右，而且不坚持巩固就会生疏。</p>

<h3>感受</h3>

<p>和双拼一样，最大的改善是打字时的体验。无需选字真的、真的很舒服。熟练以后可以不怎么注意候选框从能完全盲打。而和双拼不一样，音形有一个特别高的输入速度上限，能给你许诺一个未来的提升空间。而对我来说，即使没有那么大速度提升，为了不需要选字的流畅舒服感也值得去学习一下。</p>

<p>但蹩脚的地方也是有的。提笔忘字或者分不清平舌翘舌与前后音这种是我自己的问题。除此以外，输入的时候一大麻烦是字的重码变少了但是词的重码还是很多。有重码且不是最常用的词只能拆开来打了，然而在我输入完之前往往不知道这个码被用作更重要的词所以打不出来，经常输完了以后发现打不出词，只能删掉重打。<a href="http://help.flypy.com/xhrm/1960613">有些最常用的词有两键的简码</a>这让试错变得更复杂了。这个问题看上去只能积累经验来克服了，老用户的建议是习惯打字而不是打词，如果不确定的话就直接一个字一个字把词打出来，不要总想着打词。</p>

<hr />

<p>P.S. 还有一类神奇的输入法叫做顶功类输入法，据说是不需要按空格键不需要选字的。看到<a href="https://tieba.baidu.com/p/3279027138">小鹤的作者讨论这类输入法</a>。</p>
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>官方推荐的是<a href="http://flypy.ys168.com/">添雨跟打器的定制版</a>，是个比较老的软件，需要试试它的功能。不过还是很够用的。 <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2" role="doc-endnote">
      <p>也可以设置「小鹤音形+」，这样输入“zibz”就有候选框可以选「字」字了。 <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:3" role="doc-endnote">
      <p>可以看到排行榜前列经常和小鹤有关。我看到有人写的是「魔鹤」，没搜出来什么，只有只言片语和<a href="https://www.bilibili.com/s/video/BV1se411p7Ni">一个很可怕的视频</a>。 <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Train Train</name></author><category term="talk" /><summary type="html"><![CDATA[双拼篇 一直希望提高打字速度和准确率，于是打算学习双拼，考察了各种方案最后选了小鹤。原因如下： 本身方案口碑比较好。据说布局比较合理。 各种系统默认对小鹤的支持也挺好。除了 Windows 10 开发组和作者发生了些不愉快，结果是 Windows 10 直接做了个自定义任意双拼布局的设置，虽然有点麻烦但是还是能设成小鹤的。 习惯了双拼还可以进一步去学习双形，经过训练可以达到五笔的速度和准确率。详细请看下面的双形小节。 在决定切换之前最好先在打字测速软件1里面测试一下全拼打字速度，以供未来对比。我就没有预先测试速度，所以这篇文章也没办法给出速度对比了。 在一切开始之前建议还是纠正一下指法，这个网站非常不错，顺便可以练习英文输入的能力。 首先打印一个键位图放在容易看到的地方，这两周你会非常需要时不时盯着键位图的。最好在第一天就把所有设备都换成双拼，强迫自己用双拼，不然会前功尽弃。切换一开始会很有挫败感，这是正常的，别难过，你是最棒的。 从全拼切换到双拼需要大概两周的时间习惯，这两周打字效率当然会急速下降。两周以后，虽然有些键位还是会弄错（一般是“O”和“Z”，“Q”和“P”还有“T”），但已经能流畅打字了，接下来就会慢慢体会到双拼的改变了。 个人感觉用双拼更加舒服了。和速度关系不大。举个例子，比如说我打「上」这个字的时候需要输入 “shang” 如果有一个键按错了比如变成 “sjang”，我需要退格四次然后重新按 “hang” 四个键。双拼只需要输入 “uh”，就算 u 打错了也只需要退格两次重按两键。这种优势在手机输入中最明显了，双拼特别特别适合手机全键盘输入。 全拼到双拼的速度提升其实不是非常大的，日常使用中这点速度提升可能并没有多少作用。重要的是体验提升，打字是你每天都要重复上千字的事情，体验提升一点就很值得了。 但对我来说最难受的是选候选字，所有人都经历过在“shi”这类发音中选字的痛苦，其实我学小鹤一开始就是盯着「音形」去的。于是「双形篇」突入。 双形篇 小鹤的作者为双形设计了独立的音形结合输入法，所以要用双形最好用他做的输入法，但如果你是搜狗输入法用户，也可以学学双形，双形在搜狗输入法中似乎可以作为辅助码来使用。 小鹤的双形是一个字从首末拆出两个字根，再添到双拼的后面。比如说「字」这个字就是“zi”（双拼）+“bz”，“b”代表「宝盖头」，“z”是「子」。每个字都有这样由四个字母构成的编码。 可能你会觉得现在每个字都要按四个按键很麻烦，但其实很少需要打全四个的，四个码是编码上限。作者按照字频将常用的字都放在 1-3 长度的编码中。比如说「字」这个字，打“z”的时候出的字是「在」，打“zi”的时候就是「字」了，和双拼一样，而打全“zibz”反而出来的是「孳」，原因是虽然「字」和「孳」都是这个码，但「字」前面已经出现过了，所以“zibz”这个码位就让给「孳」了2。在这种方式之下打字的重码率降到了很低，即使偶尔需要选字也只有一两个选项。 小鹤也为常用词语编码了四键编码，二字词只需要和双拼一样输入就能打出来。大多数时候打字的感受是「有时会多加一两个键、需要频繁按空格键的双拼，并且不用选字」。于是用户在普通的双拼和小鹤音形间切换是接近无缝的，在学习音形的时候如果有需要就可以临时切换回普通双拼，我在手机上就是用的普通的双拼输入。 双拼的学习主要是肌肉记忆。照着键位图强行用，肌肉记忆形成了就行了。而音形的学习则需要你记忆一些「部件」，学习编码规则，如果用 Rime 的话还需要一点点技术能力。提升熟练度的时间也相对比较慢，我现在用了几个月有时还是磕磕绊绊的。 拆字规则 比起五笔等笔画输入法，小鹤的先进之处是作者制定了一套规则使得任意一个字都能唯一地拆出相同的编码，极大减少了记忆负担也减少了重码率。 作者写的规则还算挺好懂。除了规则以外，你必须特地记忆的只有「部件」了，「部件」是所有拆字的基础，一定要好好记住。最好打印下图或者设成壁纸。 特别要注意的是那些内部包含别的部件的部件。比如说「立」这个字，总是会忘记「立」自身是个部件，拆「靖」这样的字的时候就容易把第一个部件取成文字头。 小鹤的拆字规则实际使用中需要递归地去判断字中某个部分是否是「小字」，虽然不难，但不熟悉的拆分有时是会卡壳一下。用得多了自然会记住常用字的拆分。习惯拆分了以后最大的敌人还是提笔忘字。 这里有文字拆分的在线查询，非常好用。有个小窍门是如果不确定一个字是不是小字，可以查询一下看看，如果没有出现部件而全都是笔画的字就是「小字」。 输入法 作者第一级支持的输入法有很多独特的功能但是仅限 Windows，所以我用的是 Rime 版本，这一节记录一下我是怎么配置 Rime 的。 这类打单字能力很强的输入法是不需要如云词库、整句输入之类的功能的（这些正好是 Rime 的弱项）。词库都是固定记录的，新词的收录是谨慎而保守的，即便有些编码是空的正好可以放词进去也不一定会加，所以可以到小鹤论坛的这个帖子看看别的用户提议了哪些词，自己选择需要的加到词库文件（flypy_user.txt）里面。 我要在 macOS 和 Windows 同时使用，自然会希望能同步设置和词库。Rime 版本中，用户词库是文本文件。输入法配置是 YAML 的，所以把词库和配置放到云同步的文件夹，再用软链接的方式连接到配置文件夹就可以了（PowerShell 为例）： New-Item -Path $home\AppData\Roaming\Rime\flypy_user.txt -ItemType SymbolicLink -Value $home\Dropbox\Rime\flypy_user.txt 有一个小技巧是，由于Rime 的配置支持 patch，能够在不覆盖原有配置的情况下改写设置。这样做的好处是更新的时候用新版本的配置文件覆盖旧的就新了。小鹤的配置文件是 flypy.schema.yaml 和 flypyplus.schema.yaml ，patch 的配置就名为 flypy.custom.yaml 和 flypyplus.custom.yaml，可以同一个文件软连接到这两个文件去。 flypy 是「小鹤音形」，flypyplus 是「小鹤音形+」，区别是如果一个字的全码之前有过简码的话（比如之前例子中的「字」字），「小鹤音形」输入全码不会显示那个字，而「小鹤音形+」会以候选框的方式都显示出来。个人建议学习的时候用「小鹤音形+」，熟练的时候再试着切换。（我还没切换） flypy.custom.yaml 中内容大概是这样的： patch: style/horizontal: false menu/page_size: 6 speller/max_code_length: 32 # 混杂英文输入的时候稍微流畅点，看运气 key_binder/bindings: - {accept: bracketleft, send: Page_Up, when: paging} # [上翻页 - {accept: bracketright, send: Page_Down, when: has_menu} # ]下翻页 - {accept: comma, send: comma, when: paging} # 注销逗号翻页 - {accept: period, send: period, when: has_menu} # 注销句号翻页 练习 就像这个视频展现的，小鹤音形能达到很高的键入速度，但这需要刻意去练习。这是作者写的练习方式。 下载小鹤的网盘里面提供的「小鹤专用添雨跟打器.zip」打开后，在菜单里面选择「发文」，选择常用500字，字数限定在每段10字，不要点乱序。打字时先按 Ctrl + R 打乱当前段落的顺序。作者推荐的的速度标准是「击键超过6换下一组」意思是要达到打字时每秒按6键的速度，并且不能有错误和退格回改。这一开始看起来是不可能的任务，其实是不难做到的，只是需要时间。到了5以后其实多重复就能上6了。不过这种事情也是有边际效益递减的，为了提高到击键6所花的时间成本不一定值得，日常使用的话把目标击键数设为5就挺好的。 打字这种重复练习，反复训练以后速度会逐渐提高，这其中是有乐趣的。有许多人把打文章作为一种娱乐，还时常在打字群里开展比赛，现在的打字软件就是为打字群准备的。这里有一个打字软件提供的排行榜3。我已经很久没练习了，大概只练了前50个字左右。练习很花时间，十个字要打到击键6我经常需要两个小时左右，而且不坚持巩固就会生疏。 感受 和双拼一样，最大的改善是打字时的体验。无需选字真的、真的很舒服。熟练以后可以不怎么注意候选框从能完全盲打。而和双拼不一样，音形有一个特别高的输入速度上限，能给你许诺一个未来的提升空间。而对我来说，即使没有那么大速度提升，为了不需要选字的流畅舒服感也值得去学习一下。 但蹩脚的地方也是有的。提笔忘字或者分不清平舌翘舌与前后音这种是我自己的问题。除此以外，输入的时候一大麻烦是字的重码变少了但是词的重码还是很多。有重码且不是最常用的词只能拆开来打了，然而在我输入完之前往往不知道这个码被用作更重要的词所以打不出来，经常输完了以后发现打不出词，只能删掉重打。有些最常用的词有两键的简码这让试错变得更复杂了。这个问题看上去只能积累经验来克服了，老用户的建议是习惯打字而不是打词，如果不确定的话就直接一个字一个字把词打出来，不要总想着打词。 P.S. 还有一类神奇的输入法叫做顶功类输入法，据说是不需要按空格键不需要选字的。看到小鹤的作者讨论这类输入法。 官方推荐的是添雨跟打器的定制版，是个比较老的软件，需要试试它的功能。不过还是很够用的。 &#8617; 也可以设置「小鹤音形+」，这样输入“zibz”就有候选框可以选「字」字了。 &#8617; 可以看到排行榜前列经常和小鹤有关。我看到有人写的是「魔鹤」，没搜出来什么，只有只言片语和一个很可怕的视频。 &#8617;]]></summary></entry><entry><title type="html">一点点政治</title><link href="https://ioover.net/talk/a-little-politics/" rel="alternate" type="text/html" title="一点点政治" /><published>2020-05-04T21:21:51+00:00</published><updated>2020-05-04T21:21:51+00:00</updated><id>https://ioover.net/talk/a-little-politics</id><content type="html" xml:base="https://ioover.net/talk/a-little-politics/"><![CDATA[<p>我对政治没有学问，不敢确定自己的政治立场，但还是想稍微阐述一下自己的想法。过几年看看有没有变化。</p>

<p>我是不能接受马克思/共产主义/社会主义作为国家的根本的，历史上所有尝试均失败就足够以为理由了。有人解释历史的原因、地缘政治的原因、如何如何的原因，对此我只想捂住耳朵说「我不听我不听我不听」。除非有一个成功稳定的真社会主义政权否则这些辩护都很可疑。</p>

<p>资本主义是自发形成的，用时髦的词儿来说经济是「复杂系统的涌现」，看似人类掌中物其实是完全异质的东西。资本主义会不断演化适应，而这种演化是没有人设计的，人只能去尝试调整，像医生对身体所做的那样。<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup> 而概括任何社会主义的核心——依我看——是一种愿望，试图塑造和把控人的行动所形成的社会的愿望。这种愿望我不相信会完全成功。</p>

<p>资本主义是超越人的东西，是人类行为所创造的「大自然」，自是不能指望它有任何人的温情和理想，描写其恶果的书早已汗牛充栋。而马克思主义无论酿成什么血腥恶果，他们的初衷确实是为了人能好好生活，是<em>人的愿望</em>。他们必须存在下去，<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">2</a></sup>永远作为一股制衡非人性的资本主义的力量，永远为此提供思想和工具，但永远不能达成他们所梦想的「革命」。</p>

<p>可这就足够了吗？这像是在放弃思考，把问题推给未来的风云变幻。况且这种状态就是世界很多民主国家当下在经历的，也不见得他们现在感觉多好。难道说我有信心担保让他们继续下去就会越来越好吗？不可能有这样的信心。可能我只是认为这是最不坏的选择吧。</p>

<p>唉。到头来其实没什么想法。仅作记录吧。</p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p><a href="https://en.wikisource.org/wiki/I,_Pencil">I, Pencil</a> （铅笔的故事）这篇文章可以读一下。 <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2" role="doc-endnote">
      <p>这也有点之前那篇《<a href="/talk/dynamic-equilibrium/">动态平衡</a>》的思路。现在的我觉得这个想法太过无力，简直就是放弃思考的废话和遁词了。 <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Train Train</name></author><category term="talk" /><summary type="html"><![CDATA[我对政治没有学问，不敢确定自己的政治立场，但还是想稍微阐述一下自己的想法。过几年看看有没有变化。 我是不能接受马克思/共产主义/社会主义作为国家的根本的，历史上所有尝试均失败就足够以为理由了。有人解释历史的原因、地缘政治的原因、如何如何的原因，对此我只想捂住耳朵说「我不听我不听我不听」。除非有一个成功稳定的真社会主义政权否则这些辩护都很可疑。 资本主义是自发形成的，用时髦的词儿来说经济是「复杂系统的涌现」，看似人类掌中物其实是完全异质的东西。资本主义会不断演化适应，而这种演化是没有人设计的，人只能去尝试调整，像医生对身体所做的那样。1 而概括任何社会主义的核心——依我看——是一种愿望，试图塑造和把控人的行动所形成的社会的愿望。这种愿望我不相信会完全成功。 资本主义是超越人的东西，是人类行为所创造的「大自然」，自是不能指望它有任何人的温情和理想，描写其恶果的书早已汗牛充栋。而马克思主义无论酿成什么血腥恶果，他们的初衷确实是为了人能好好生活，是人的愿望。他们必须存在下去，2永远作为一股制衡非人性的资本主义的力量，永远为此提供思想和工具，但永远不能达成他们所梦想的「革命」。 可这就足够了吗？这像是在放弃思考，把问题推给未来的风云变幻。况且这种状态就是世界很多民主国家当下在经历的，也不见得他们现在感觉多好。难道说我有信心担保让他们继续下去就会越来越好吗？不可能有这样的信心。可能我只是认为这是最不坏的选择吧。 唉。到头来其实没什么想法。仅作记录吧。 I, Pencil （铅笔的故事）这篇文章可以读一下。 &#8617; 这也有点之前那篇《动态平衡》的思路。现在的我觉得这个想法太过无力，简直就是放弃思考的废话和遁词了。 &#8617;]]></summary></entry><entry><title type="html">正确的Rust引用类型心智模型</title><link href="https://ioover.net/dev/reference-types/" rel="alternate" type="text/html" title="正确的Rust引用类型心智模型" /><published>2019-10-28T09:34:27+00:00</published><updated>2019-10-28T09:34:27+00:00</updated><id>https://ioover.net/dev/reference-types</id><content type="html" xml:base="https://ioover.net/dev/reference-types/"><![CDATA[<p>这是一篇译文，翻译自<a href="https://github.com/dtolnay">David Tolnay</a>的<a href="https://docs.rs/dtolnay/0.0.6/dtolnay/macro._02__reference_types.html#accurate-mental-model-for-rusts-reference-types">Accurate mental model for Rust’s reference types</a>，MIT 协议授权。</p>

<p>Rust的<a href="https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html">所有权和借用模型</a>涉及使用<em>引用</em>（references）去操作借来的数据，类型系统区分了两种不同的基本引用类型。在代码中写成 <code>&amp;T</code> 和 <code>&amp;mut T</code>。</p>

<p><code>&amp;mut T</code> 一般称为对类型为 <code>T</code> 的数据的「可变引用」（mutable reference）。而 <code>&amp;T</code> 则是一个对于 <code>T</code> 的「不可变引用」（immutable reference）或者「常量引用」（const reference）。这些名字不错，对Rust新手能建立合理的直觉。但这篇文章会讲一些理由来说明，对于新手阶段之后的Rust使用者来说，更好的名字是「共享引用」（shared reference）和「独占引用」（exclusive reference）。</p>

<h2>初学者的理解</h2>

<p>如同 Rust 书中的「<a href="https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.html">引用和借用</a>」一章所述，函数如果获取了一个不可变引用的参数，那就可以读取引用指向的数据：</p>

<pre><code class="language-rust">struct Point {
    x: u32,
    y: u32,
}

fn print_point(pt: &amp;Point) {
    println!("x={} y={}", pt.x, pt.y);
}
</code></pre>

<p>但不允许改变数据：</p>

<pre><code class="language-rust">fn embiggen_x(pt: &amp;Point) {
    pt.x = pt.x * 2;
}
</code></pre>

<pre><code>error[E0594]: cannot assign to `pt.x` which is behind a `&amp;` reference
 --&gt; src/main.rs
  |
1 | fn embiggen_x(pt: &amp;Point) {
  |                   ------ help: consider changing this to be a mutable reference: `&amp;mut Point`
2 |     pt.x = pt.x * 2;
  |     ^^^^^^^^^^^^^^^ `pt` is a `&amp;` reference, so the data it refers to cannot be written
</code></pre>

<p>要改变结构的字段或者调用那些修改类方法，参数必须通过 <code>&amp;mut</code> 引用获取。</p>

<pre><code class="language-rust">fn embiggen_x(pt: &amp;mut Point) {
    pt.x = pt.x * 2; // okay
}
</code></pre>

<p>用Rust写一些玩具程序的话，这种区分，这种「不可变引用」和「可变引用」的术语通常也足够了。</p>

<h2>散架啦</h2>

<p>迟早你会遇到一个库签名，直截了当地相悖于初学者对Rust引用的心智模型。作为一个例子，来看看标准库中 <a href="https://doc.rust-lang.org/std/sync/atomic/struct.AtomicU32.html"><code>AtomicU32</code></a> 的 <code>store</code> 方法的签名：</p>

<pre><code class="language-rust">impl AtomicU32 {
    pub fn store(&amp;self, val: u32, order: Ordering);
}
</code></pre>

<p>给一个 u32 值，它原子地将 <code>AtomicU32</code> 的中的数字改成了你给的那个。可以像这样调用 <code>store</code> 方法：</p>

<pre><code class="language-rust">static COUNTER: AtomicU32 = AtomicU32::new(0);

fn reset() {
    COUNTER.store(0, Ordering::SeqCst);
}
</code></pre>

<p>在这个讨论中可以忽略 <code>Ordering</code> 参数，它根据<a href="https://doc.rust-lang.org/nomicon/atomics.html">C11 原子操作的内存模型</a>来运作。</p>

<p>在初学者的心智模型下，<code>AtomicU32::store</code> 方法获取自身的不可变引用这件事将会让人感觉浑身难受。确实修改是原子的，但修改不可变引用之下的数据怎么会是正确的呢？如果这是刻意为之，确实会令人感觉很魔法（hacky）甚至危险。为什么这个方法是safe的？为什么不是Undefined Behavior？</p>

<p>这会让前C++程序员想起C++中一些 <code>const_cast</code>的滥用，也许作者根本没法保证代码不会因为违背一些幽深的语言法则而在未来炸掉，即使现在代码看上去运作正常。</p>

<p>当然C++中所有像 <a href="https://en.cppreference.com/w/cpp/atomic/atomic/store"><code>std::atomic&lt;T&gt;::store</code></a> 这样的原子性修改方法只能用于可变引用。通过常量引用来储存值如同预料中那样不会通过编译。</p>

<pre><code class="language-c++">// C++

#include &lt;atomic&gt;

void test(const std::atomic&lt;unsigned&gt;&amp; val) {
  val.store(0);
}
</code></pre>

<pre><code>test.cc:4:7: error: no matching member function for call to 'store'
  val.store(0);
  ~~~~^~~~~
/usr/include/c++/5.4.0/bits/atomic_base.h:367:7: note: candidate function not viable: no known conversion from 'const std::atomic&lt;unsigned int&gt;' to 'std::__atomic_base&lt;unsigned int&gt;' for object argument
      store(__int_type __i, memory_order __m = memory_order_seq_cst) noexcept
      ^
/usr/include/c++/5.4.0/bits/atomic_base.h:378:7: note: candidate function not viable: no known conversion from 'const std::atomic&lt;unsigned int&gt;' to 'volatile std::__atomic_base&lt;unsigned int&gt;' for object argument
      store(__int_type __i,
      ^
</code></pre>

<p>有什么地方出了问题。这超出了初学者对Rust <code>&amp;</code> 和 <code>&amp;mut</code> 引用类型意义的理解。</p>

<h2>更好的名字</h2>

<p><code>&amp;T</code> 不是对那些类型为 <code>T</code> 的数据的 「不可变引用」或者「常量引用」，而是「共享引用」。<code>&amp;mut T</code> 不是「可变引用」而是「独占引用」。</p>

<p>独占引用意味着在同一时刻，同一个值不可能存在别的引用。共享引用则意味着<em>可能</em>存在对同一个值的其它引用，也许是在别的线程（如果 <code>T</code> 实现了 <code>Sync</code> 的话）或是当前线程的调用栈中。Rust 借用检查器的一个关键职能就是确保独占引用真的是独占性的。</p>

<p>再看看 <code>AtomicU32::store</code> 的签名。</p>

<pre><code class="language-rust">impl AtomicU32 {
    pub fn store(&amp;self, val: u32, order: Ordering);
}
</code></pre>

<p>对于函数用共享引用获取原子式的u32，此时应该<strong>感到完全自然</strong>。同一时刻对同一个 <code>AtomicU32</code> 有别的引用当然没问题。原子性就是为了并发读写而不导致数据争用（data race）而存在的。如果库在调用 <code>store</code> 时不允许别的引用存在，那就没什么理由用原子性了。</p>

<p>独占引用一直是可变的原因是如果没有别的代码看着同一个数据，我们可以大胆地修改数据而不引发数据争用。数据争用（data race）是指多个地方同时操作同一个数据，并且至少有一个在修改，从而产生意外的结果或者内存不安全的情况。但是通过原子性或者下述内部可变性的方式，通过共享引用修改数据也是安全的。</p>

<p>充分内化「共享引用」和「独占引用」，学会这样思考，是学会充分利用Rust与其强大的安全性保证的重要一步。</p>

<h2>怎么教</h2>

<p>一开始将 <code>&amp;</code>  和 <code>&amp;mut</code> 作为不可变和可变来介绍的做法，我不觉得不好。学习曲线就经足够难了，即使不算上本文的内容。对于初学者而言，修改能力的不同是两种引用类型最显著的实际差异。</p>

<p>我觉得建立从「不可变引用」/「可变引用」到「共享引用」/「独占引用」的心智模型转变是必要的一步。应该鼓励初学者在正确的时间走出这一步，而本页可以帮助他们走出这一步。当有人困惑于一些库函数预料需要 <code>&amp;mut</code> 却只获取 <code>&amp;</code> 时，就是发本页链接的好时机了。</p>

<p>在内化了共享和独占引用之后，我觉得继续说「可变引用」也不错，毕竟关键字是 <code>mut</code>。只要别忘了共享引用背后的数据有时也<em>可能</em>是可变的。另一方面，对于共享引用，我建议始终说「共享引用」而不是「不可变引用」或者「常量引用」。</p>

<h2>附录：内部可变性</h2>

<p>在Rust中，术语「内部可变性」（interior mutability）表示支持通过共享引用修改数据。</p>

<p>我用 <code>AtomicU32</code> 作为例子的缘故是当你从初学者的心智模型切换到正确的心智模型时，它最能唤起从「浑身难受」到「完全自然」的深刻转变。尽管原子性是多线程代码的重要一块，但内部可变性在单线程中也同样重要。</p>

<p>通过共享引用持有可变数据的<em>唯一</em>方法是标准库类型 <a href="https://doc.rust-lang.org/std/cell/struct.UnsafeCell.html"><code>UnsafeCell&lt;T&gt;</code></a> 。它是一种 unsafe 的底层工具，一般不会直接去使用。所有别的内部可变性方式都是建立在它之上的安全抽象，有着不同的性质和需求，适用于不同的情况。（根本上来说，Rust就是一个建立安全抽象的语言，而内部可变性就是其中一个最明显的部分。）</p>

<p>除了原子操作以外，其它建立在内部可变性上的标准库安全抽象还包括：</p>

<ul>
  <li><a href="https://doc.rust-lang.org/std/cell/struct.Cell.html"><code>Cell&lt;T&gt;</code></a>： 修改是安全的，哪怕可能存在其它对同一个 <code>Cell&lt;T&gt;</code> 的引用，因为API施行：
    <ul>
      <li>多个线程不可能同时持有对同一个 <code>Cell&lt;T&gt;</code> 的应用，因为 <code>Cell&lt;T&gt;</code> 没有实现 <code>Sync</code> trait，也就是说 <code>Cell&lt;T&gt;</code> 是单线程的；</li>
      <li>不可能获取对 <code>Cell&lt;T&gt;</code> 中的内容的引用，因为这种引用可能因为修改而失效，作为替代所有的访问通过复制数据来完成。</li>
    </ul>
  </li>
  <li><a href="https://doc.rust-lang.org/std/cell/struct.RefCell.html"><code>RefCell&lt;T&gt;</code></a>：修改是安全的，哪怕可能存在其它对同一个 <code>RefCell&lt;T&gt;</code> 的引用，因为API施行：
    <ul>
      <li>类似 <code>Cell&lt;T&gt;</code>，<code>RefCell&lt;T&gt;</code> 是单线程的，所以不同的线程不可能引用同一个值。</li>
      <li>在一个线程之内，当有读者持有内容的引用时，运行时的借用检查会阻止对内容的修改。</li>
    </ul>
  </li>
  <li><a href="https://doc.rust-lang.org/std/sync/struct.Mutex.html"><code>Mutex&lt;T&gt;</code></a>：修改是安全的，哪怕可能存在其它对同一个 <code>Mutex&lt;T&gt;</code> 的引用，因为API施行：
    <ul>
      <li>同一时刻只有一个引用可以对内部的 <code>T</code> 读写。其他访问会被阻塞到现在的引用释放了锁为止。</li>
    </ul>
  </li>
  <li><a href="https://doc.rust-lang.org/std/sync/struct.RwLock.html"><code>RwLock&lt;T&gt;</code></a>：修改是安全的，哪怕可能存在其它对同一个 <code>RwLock&lt;T&gt;</code> 的引用，因为API施行：
    <ul>
      <li>同一时刻只有一个引用可以用来修改 <code>T</code>，且仅当此时没有别的引用在被读。</li>
    </ul>
  </li>
</ul>]]></content><author><name>Train Train</name></author><category term="dev" /><summary type="html"><![CDATA[这是一篇译文，翻译自David Tolnay的Accurate mental model for Rust’s reference types，MIT 协议授权。 Rust的所有权和借用模型涉及使用引用（references）去操作借来的数据，类型系统区分了两种不同的基本引用类型。在代码中写成 &amp;T 和 &amp;mut T。 &amp;mut T 一般称为对类型为 T 的数据的「可变引用」（mutable reference）。而 &amp;T 则是一个对于 T 的「不可变引用」（immutable reference）或者「常量引用」（const reference）。这些名字不错，对Rust新手能建立合理的直觉。但这篇文章会讲一些理由来说明，对于新手阶段之后的Rust使用者来说，更好的名字是「共享引用」（shared reference）和「独占引用」（exclusive reference）。 初学者的理解 如同 Rust 书中的「引用和借用」一章所述，函数如果获取了一个不可变引用的参数，那就可以读取引用指向的数据： struct Point { x: u32, y: u32, } fn print_point(pt: &amp;Point) { println!("x={} y={}", pt.x, pt.y); } 但不允许改变数据： fn embiggen_x(pt: &amp;Point) { pt.x = pt.x * 2; } error[E0594]: cannot assign to `pt.x` which is behind a `&amp;` reference --&gt; src/main.rs | 1 | fn embiggen_x(pt: &amp;Point) { | ------ help: consider changing this to be a mutable reference: `&amp;mut Point` 2 | pt.x = pt.x * 2; | ^^^^^^^^^^^^^^^ `pt` is a `&amp;` reference, so the data it refers to cannot be written 要改变结构的字段或者调用那些修改类方法，参数必须通过 &amp;mut 引用获取。 fn embiggen_x(pt: &amp;mut Point) { pt.x = pt.x * 2; // okay } 用Rust写一些玩具程序的话，这种区分，这种「不可变引用」和「可变引用」的术语通常也足够了。 散架啦 迟早你会遇到一个库签名，直截了当地相悖于初学者对Rust引用的心智模型。作为一个例子，来看看标准库中 AtomicU32 的 store 方法的签名： impl AtomicU32 { pub fn store(&amp;self, val: u32, order: Ordering); } 给一个 u32 值，它原子地将 AtomicU32 的中的数字改成了你给的那个。可以像这样调用 store 方法： static COUNTER: AtomicU32 = AtomicU32::new(0); fn reset() { COUNTER.store(0, Ordering::SeqCst); } 在这个讨论中可以忽略 Ordering 参数，它根据C11 原子操作的内存模型来运作。 在初学者的心智模型下，AtomicU32::store 方法获取自身的不可变引用这件事将会让人感觉浑身难受。确实修改是原子的，但修改不可变引用之下的数据怎么会是正确的呢？如果这是刻意为之，确实会令人感觉很魔法（hacky）甚至危险。为什么这个方法是safe的？为什么不是Undefined Behavior？ 这会让前C++程序员想起C++中一些 const_cast的滥用，也许作者根本没法保证代码不会因为违背一些幽深的语言法则而在未来炸掉，即使现在代码看上去运作正常。 当然C++中所有像 std::atomic&lt;T&gt;::store 这样的原子性修改方法只能用于可变引用。通过常量引用来储存值如同预料中那样不会通过编译。 // C++ #include &lt;atomic&gt; void test(const std::atomic&lt;unsigned&gt;&amp; val) { val.store(0); } test.cc:4:7: error: no matching member function for call to 'store' val.store(0); ~~~~^~~~~ /usr/include/c++/5.4.0/bits/atomic_base.h:367:7: note: candidate function not viable: no known conversion from 'const std::atomic&lt;unsigned int&gt;' to 'std::__atomic_base&lt;unsigned int&gt;' for object argument store(__int_type __i, memory_order __m = memory_order_seq_cst) noexcept ^ /usr/include/c++/5.4.0/bits/atomic_base.h:378:7: note: candidate function not viable: no known conversion from 'const std::atomic&lt;unsigned int&gt;' to 'volatile std::__atomic_base&lt;unsigned int&gt;' for object argument store(__int_type __i, ^ 有什么地方出了问题。这超出了初学者对Rust &amp; 和 &amp;mut 引用类型意义的理解。 更好的名字 &amp;T 不是对那些类型为 T 的数据的 「不可变引用」或者「常量引用」，而是「共享引用」。&amp;mut T 不是「可变引用」而是「独占引用」。 独占引用意味着在同一时刻，同一个值不可能存在别的引用。共享引用则意味着可能存在对同一个值的其它引用，也许是在别的线程（如果 T 实现了 Sync 的话）或是当前线程的调用栈中。Rust 借用检查器的一个关键职能就是确保独占引用真的是独占性的。 再看看 AtomicU32::store 的签名。 impl AtomicU32 { pub fn store(&amp;self, val: u32, order: Ordering); } 对于函数用共享引用获取原子式的u32，此时应该感到完全自然。同一时刻对同一个 AtomicU32 有别的引用当然没问题。原子性就是为了并发读写而不导致数据争用（data race）而存在的。如果库在调用 store 时不允许别的引用存在，那就没什么理由用原子性了。 独占引用一直是可变的原因是如果没有别的代码看着同一个数据，我们可以大胆地修改数据而不引发数据争用。数据争用（data race）是指多个地方同时操作同一个数据，并且至少有一个在修改，从而产生意外的结果或者内存不安全的情况。但是通过原子性或者下述内部可变性的方式，通过共享引用修改数据也是安全的。 充分内化「共享引用」和「独占引用」，学会这样思考，是学会充分利用Rust与其强大的安全性保证的重要一步。 怎么教 一开始将 &amp; 和 &amp;mut 作为不可变和可变来介绍的做法，我不觉得不好。学习曲线就经足够难了，即使不算上本文的内容。对于初学者而言，修改能力的不同是两种引用类型最显著的实际差异。 我觉得建立从「不可变引用」/「可变引用」到「共享引用」/「独占引用」的心智模型转变是必要的一步。应该鼓励初学者在正确的时间走出这一步，而本页可以帮助他们走出这一步。当有人困惑于一些库函数预料需要 &amp;mut 却只获取 &amp; 时，就是发本页链接的好时机了。 在内化了共享和独占引用之后，我觉得继续说「可变引用」也不错，毕竟关键字是 mut。只要别忘了共享引用背后的数据有时也可能是可变的。另一方面，对于共享引用，我建议始终说「共享引用」而不是「不可变引用」或者「常量引用」。 附录：内部可变性 在Rust中，术语「内部可变性」（interior mutability）表示支持通过共享引用修改数据。 我用 AtomicU32 作为例子的缘故是当你从初学者的心智模型切换到正确的心智模型时，它最能唤起从「浑身难受」到「完全自然」的深刻转变。尽管原子性是多线程代码的重要一块，但内部可变性在单线程中也同样重要。 通过共享引用持有可变数据的唯一方法是标准库类型 UnsafeCell&lt;T&gt; 。它是一种 unsafe 的底层工具，一般不会直接去使用。所有别的内部可变性方式都是建立在它之上的安全抽象，有着不同的性质和需求，适用于不同的情况。（根本上来说，Rust就是一个建立安全抽象的语言，而内部可变性就是其中一个最明显的部分。） 除了原子操作以外，其它建立在内部可变性上的标准库安全抽象还包括： Cell&lt;T&gt;： 修改是安全的，哪怕可能存在其它对同一个 Cell&lt;T&gt; 的引用，因为API施行： 多个线程不可能同时持有对同一个 Cell&lt;T&gt; 的应用，因为 Cell&lt;T&gt; 没有实现 Sync trait，也就是说 Cell&lt;T&gt; 是单线程的； 不可能获取对 Cell&lt;T&gt; 中的内容的引用，因为这种引用可能因为修改而失效，作为替代所有的访问通过复制数据来完成。 RefCell&lt;T&gt;：修改是安全的，哪怕可能存在其它对同一个 RefCell&lt;T&gt; 的引用，因为API施行： 类似 Cell&lt;T&gt;，RefCell&lt;T&gt; 是单线程的，所以不同的线程不可能引用同一个值。 在一个线程之内，当有读者持有内容的引用时，运行时的借用检查会阻止对内容的修改。 Mutex&lt;T&gt;：修改是安全的，哪怕可能存在其它对同一个 Mutex&lt;T&gt; 的引用，因为API施行： 同一时刻只有一个引用可以对内部的 T 读写。其他访问会被阻塞到现在的引用释放了锁为止。 RwLock&lt;T&gt;：修改是安全的，哪怕可能存在其它对同一个 RwLock&lt;T&gt; 的引用，因为API施行： 同一时刻只有一个引用可以用来修改 T，且仅当此时没有别的引用在被读。]]></summary></entry><entry><title type="html">自由意志和道德</title><link href="https://ioover.net/talk/free-will-and-morality/" rel="alternate" type="text/html" title="自由意志和道德" /><published>2019-07-04T19:02:44+00:00</published><updated>2019-07-04T19:02:44+00:00</updated><id>https://ioover.net/talk/free-will-and-morality</id><content type="html" xml:base="https://ioover.net/talk/free-will-and-morality/"><![CDATA[<blockquote>
  <p>“这个杀人犯是在贫民窟长大的。当他七个月大的时候，他的父亲遗弃了他。他经常受到母亲的虐待，他的哥哥姐姐也欺负他。他从来没有机会上学，而当他能找到工作的时候，他从来都保不住自己的工作。他抢那家商店的时候已经快饿死了，而且还染上了强烈的毒瘾，也没有朋友能够给他帮助。他姐姐说：‘当他还是小孩子时，我就知道他早晚会这么干的。’他母亲抱怨说：‘我不理解’检察官称之为‘一个冷酷无情的、蓄谋已久的行为’。辩方则控诉整个社会，声称正是社会的忽视和负面的影响才使此人不可避免地成为一个凶手。”我们知道这些辩论的其余部分，但我们不知道他们会如何解决。一个人应该为他的一生都在为此创造条件的行为负责吗？或者我们应不应该再坚持这样的看法：无论事件的背景如何，他本可以抵制，本可以决定不犯罪，所以他必须为此负责？</p>
</blockquote>

<p>这段话原文出自于《大问题》，不过我第一次读到它是在<a href="https://www.zhihu.com/question/19610240/answer/37078752">这篇知乎文章中</a>，如果没看过的话希望能去看一下。</p>

<p>不必多说，决定论自始至终是一个很有吸引力的选择，否则我们要放弃很多才能去主张自己有不被物理定律束缚的自由意志。但决定论带来的责任的丧失是种问题。</p>

<p>本文想要谈谈的是：我们能不能假设自己接受强决定论的前提，并且不去想什么「选择」或者「实践和理论」，然后去获得道德责任呢？我觉得是可以的。</p>

<p>想象一群理性人，他们都看过哲学方面的文章。有人犯罪的时候他用决定论给自己辩护：「嗨呀，你们都懂的，我做的事情是被决定的，你们有什么理由定我有罪。嗨呀，你们也都懂的，我干嘛说这些废话，快把我放出来！」</p>

<p>显然如果这种策略能成功的话，整个社会不能继续运转了。这对每个人的利益都是损害，哪怕是作恶的人。</p>

<p>所以这群理性人需要运作起道德，而这本身就能成为责任存在的理由。他们会共同<strong>假装</strong>责任存在，道德就如同某种共同的妥协或是社会契约一样运作。也就是说，即便大家都心知肚明彼此并没有选择的能力（自由意志），仍然会以彼此（某种程度上）有这种能力为前提而行动甚至思考。即使所有人心中最深处知道归根结底责任并不存在——可别忘了，从根本上来说不存在的东西太多了：国家、民族、货币价值、人生的意义、人类的意义…</p>

<p>很显然可以说这种<strong>假装</strong>的道德责任就是假的，是空洞而无力的。这是另一个大问题了：我们一定要认为一个被<strong>假装</strong>出来的东西就一定低劣吗？什么才是真实存在的？自然数1真实存在吗？我觉得道德责任一旦被生效践行了，哪怕来自于<strong>假装</strong>，也就是成立的了，因为它的成立不来自于它的前提而来自于它的被承认。</p>

<p>就道德本身而言，这种方法或许能提供一个调和效用主义和道德普遍主义思路。也就是存在这种可能，即，效用主义的结果告诉我们：<strong>假装</strong>接受一个反效用主义的普遍的道德规范才是效用最大的。这只是一个思路。</p>

<p>我上面说的这些很可能根本不值一提或是有严重错误，不然肯定已经早就被更完善地写进某本书里了。但<a href="/talk/i-wanna-talk-about-philosophy/">写下来自己的想法总是好的</a>。</p>]]></content><author><name>Train Train</name></author><category term="talk" /><summary type="html"><![CDATA[“这个杀人犯是在贫民窟长大的。当他七个月大的时候，他的父亲遗弃了他。他经常受到母亲的虐待，他的哥哥姐姐也欺负他。他从来没有机会上学，而当他能找到工作的时候，他从来都保不住自己的工作。他抢那家商店的时候已经快饿死了，而且还染上了强烈的毒瘾，也没有朋友能够给他帮助。他姐姐说：‘当他还是小孩子时，我就知道他早晚会这么干的。’他母亲抱怨说：‘我不理解’检察官称之为‘一个冷酷无情的、蓄谋已久的行为’。辩方则控诉整个社会，声称正是社会的忽视和负面的影响才使此人不可避免地成为一个凶手。”我们知道这些辩论的其余部分，但我们不知道他们会如何解决。一个人应该为他的一生都在为此创造条件的行为负责吗？或者我们应不应该再坚持这样的看法：无论事件的背景如何，他本可以抵制，本可以决定不犯罪，所以他必须为此负责？ 这段话原文出自于《大问题》，不过我第一次读到它是在这篇知乎文章中，如果没看过的话希望能去看一下。 不必多说，决定论自始至终是一个很有吸引力的选择，否则我们要放弃很多才能去主张自己有不被物理定律束缚的自由意志。但决定论带来的责任的丧失是种问题。 本文想要谈谈的是：我们能不能假设自己接受强决定论的前提，并且不去想什么「选择」或者「实践和理论」，然后去获得道德责任呢？我觉得是可以的。 想象一群理性人，他们都看过哲学方面的文章。有人犯罪的时候他用决定论给自己辩护：「嗨呀，你们都懂的，我做的事情是被决定的，你们有什么理由定我有罪。嗨呀，你们也都懂的，我干嘛说这些废话，快把我放出来！」 显然如果这种策略能成功的话，整个社会不能继续运转了。这对每个人的利益都是损害，哪怕是作恶的人。 所以这群理性人需要运作起道德，而这本身就能成为责任存在的理由。他们会共同假装责任存在，道德就如同某种共同的妥协或是社会契约一样运作。也就是说，即便大家都心知肚明彼此并没有选择的能力（自由意志），仍然会以彼此（某种程度上）有这种能力为前提而行动甚至思考。即使所有人心中最深处知道归根结底责任并不存在——可别忘了，从根本上来说不存在的东西太多了：国家、民族、货币价值、人生的意义、人类的意义… 很显然可以说这种假装的道德责任就是假的，是空洞而无力的。这是另一个大问题了：我们一定要认为一个被假装出来的东西就一定低劣吗？什么才是真实存在的？自然数1真实存在吗？我觉得道德责任一旦被生效践行了，哪怕来自于假装，也就是成立的了，因为它的成立不来自于它的前提而来自于它的被承认。 就道德本身而言，这种方法或许能提供一个调和效用主义和道德普遍主义思路。也就是存在这种可能，即，效用主义的结果告诉我们：假装接受一个反效用主义的普遍的道德规范才是效用最大的。这只是一个思路。 我上面说的这些很可能根本不值一提或是有严重错误，不然肯定已经早就被更完善地写进某本书里了。但写下来自己的想法总是好的。]]></summary></entry><entry><title type="html">ID 弃用声明</title><link href="https://ioover.net/id-obsolete/" rel="alternate" type="text/html" title="ID 弃用声明" /><published>2019-06-23T17:55:46+00:00</published><updated>2019-06-23T17:55:46+00:00</updated><id>https://ioover.net/id-obsolete</id><content type="html" xml:base="https://ioover.net/id-obsolete/"><![CDATA[<pre><code>-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA256

从现在 （2019年06月22日）开始我不会再用以前的常用ID（如酿泉、quanbrew、tioover 等等）在网路上发表信息。（GitHub 除外）
-----BEGIN PGP SIGNATURE-----

iQEzBAEBCAAdFiEEX2b4zx61VCs5HflylILu6wpQriUFAl03ZvQACgkQlILu6wpQ
riWdQAf6A0PHd/IOAhzoJdZM0GeUwnuMDVGLiXufopv+P9GK4/ZLM3zTimvmxqBt
TOaoSxm8XnoH1+DgVo2YFp936vXsOhgQfhUcud5rxxXtJKOONQvoCeHQufbTWi/U
10WngcNF/JeJMQChRJKbWfVUsWHT+Meo0qx54z2CiDsKBimNGbcp1wMN4zGNa1aI
nnWIh4SVBOIjqUJAewprIjvsbd0j0p7nwnUh7Zss5V/5Wye9gy7FU0aSzwmQeH2D
jxvcmoGh95lFG+emfS2VGSFVULJFXV8gxvXIm/r5+3SHcz+vddjonHm6OhmTgY4M
RXDl1dhaDzvuozgCrd+ZGYgjghOPGw==
=SlBP
-----END PGP SIGNATURE-----
</code></pre>

<p>从现在 （2019年06月22日）开始我<strong>不会</strong>再用以前的常用ID（如酿泉、quanbrew、tioover 等等）在网路上发表信息。（GitHub除外 <sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">1</a></sup>）</p>

<p>在未经PGP确认的情况下，看上去像是我的人<strong>请不要相信</strong><sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">2</a></sup>。</p>

<p>我无法为此带来的法律、政治风险负责。</p>

<p>今后的 ID 会看心情乱取。有缘再见啦。我早就对「酿泉」这个名字不爽了。</p>

<h2>如何辨识？</h2>

<ul>
  <li>用别的途径向我确认。</li>
  <li>或通过 PGP Key。<a href="/about/">About</a> 中有写签名。</li>
  <li>或就当是遇到了一位新网友。</li>
  <li>我是很遵纪守法<!--怂-->的人，不会传播淫秽信息，也不会侮辱领导人或政策。</li>
</ul>

<h2>脚注</h2>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:2" role="doc-endnote">
      <p>还有一些不重要的平台比如 Instagram 也没改，不过无所谓了。 <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:1" role="doc-endnote">
      <p>E-Hentai 上有一个属于「酿泉个人翻译」的画廊，并非我或者 tastySugar 上传。上传者声称同名纯属巧合。 <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Train Train</name></author><summary type="html"><![CDATA[-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256 从现在 （2019年06月22日）开始我不会再用以前的常用ID（如酿泉、quanbrew、tioover 等等）在网路上发表信息。（GitHub 除外） -----BEGIN PGP SIGNATURE----- iQEzBAEBCAAdFiEEX2b4zx61VCs5HflylILu6wpQriUFAl03ZvQACgkQlILu6wpQ riWdQAf6A0PHd/IOAhzoJdZM0GeUwnuMDVGLiXufopv+P9GK4/ZLM3zTimvmxqBt TOaoSxm8XnoH1+DgVo2YFp936vXsOhgQfhUcud5rxxXtJKOONQvoCeHQufbTWi/U 10WngcNF/JeJMQChRJKbWfVUsWHT+Meo0qx54z2CiDsKBimNGbcp1wMN4zGNa1aI nnWIh4SVBOIjqUJAewprIjvsbd0j0p7nwnUh7Zss5V/5Wye9gy7FU0aSzwmQeH2D jxvcmoGh95lFG+emfS2VGSFVULJFXV8gxvXIm/r5+3SHcz+vddjonHm6OhmTgY4M RXDl1dhaDzvuozgCrd+ZGYgjghOPGw== =SlBP -----END PGP SIGNATURE----- 从现在 （2019年06月22日）开始我不会再用以前的常用ID（如酿泉、quanbrew、tioover 等等）在网路上发表信息。（GitHub除外 1） 在未经PGP确认的情况下，看上去像是我的人请不要相信2。 我无法为此带来的法律、政治风险负责。 今后的 ID 会看心情乱取。有缘再见啦。我早就对「酿泉」这个名字不爽了。 如何辨识？ 用别的途径向我确认。 或通过 PGP Key。About 中有写签名。 或就当是遇到了一位新网友。 我是很遵纪守法的人，不会传播淫秽信息，也不会侮辱领导人或政策。 脚注 还有一些不重要的平台比如 Instagram 也没改，不过无所谓了。 &#8617; E-Hentai 上有一个属于「酿泉个人翻译」的画廊，并非我或者 tastySugar 上传。上传者声称同名纯属巧合。 &#8617;]]></summary></entry><entry><title type="html">理解 Rust 中的 Closure</title><link href="https://ioover.net/dev/rust-closure/" rel="alternate" type="text/html" title="理解 Rust 中的 Closure" /><published>2019-04-30T09:18:55+00:00</published><updated>2019-04-30T09:18:55+00:00</updated><id>https://ioover.net/dev/rust-closure</id><content type="html" xml:base="https://ioover.net/dev/rust-closure/"><![CDATA[<p>Rust中的closure（闭包函数），很难用！这里记录下重要的事情。</p>

<h2>原理</h2>

<p>一些语言中没有closure和普通函数的区分，但Rust有。对Rust来说普通函数就是一段代码。而closure和 C++ 类似：每个closure会创建一个匿名的 <code>struct</code>，编译器会在当前<ruby>上下文 <rt>Context</rt></ruby>捕获closure代码中的外部变量然后塞进这个<ruby>结构体 <rt>Struct</rt></ruby>里面。</p>

<p><strong>这件事非常重要</strong>，请默念三遍<em>一个closure就是一个捕获了当前上下文变量的结构体</em>（外加一段代码，这不重要）。</p>

<p>这解释了为什么Rust中两个参数和返回值一样的closure不被视作同一类型<sup id="fnref:not-same-type" role="doc-noteref"><a href="#fn:not-same-type" class="footnote" rel="footnote">1</a></sup>，因为它们背后的匿名结构体不同，有着不同的大小、字段和lifetime。</p>

<pre><code class="language-rust">let m = 1.0;
let c = 2.0;

let line = |x| m*x + c;

// 等价于

struct SomeUnknownType&lt;'a&gt; {
    m: &amp;'a f64,
    c: &amp;'a f64
}

impl&lt;'a&gt; SomeUnknownType&lt;'a&gt; {
    fn call(&amp;self, x: f64) -&gt; f64 {
        self.m * x + self.c
    }
}
</code></pre>

<p>例子来源于<a href="https://stevedonovan.github.io/rustifications/2018/08/18/rust-closures-are-hard.html">Why Rust Closures are (Somewhat) Hard</a>。</p>

<p>这也是closure难用的根源：</p>

<ol>
  <li>
    <p>Rust中结构体的可变性以及liftime本身就很烦人。</p>
  </li>
  <li>
    <p>Closure的规则都是隐式的：closure捕获值的方式及所生成的closure的类型都是按照隐式的规则决定的。</p>
  </li>
  <li>
    <p>Closure一直会捕获整个复合类型，如 <code>struct</code>, <code>tuple</code> 和 <code>enum</code> 。而不只是单个字段。[2]</p>
  </li>
</ol>

<p>对于 (3)，<a href="https://github.com/rust-lang/rfcs/blob/master/text/2229-capture-disjoint-fields.md">Rust团队已经接受了一个提案</a>，旨在改进不相交字段的捕获规则。（当前看起来没多少进展）</p>

<h3>为什么</h3>

<p>对于 (1) 和 (2) 是语言设计思路所带来的结果，为什么会这样呢？</p>

<p>因为closure很好用，但是我们不想付出运行时代价。所有语言都有类似的东西，但是它们把closure捕获的结构丢到堆上以保证所有closure类型大小一样，且借助了GC管理资源。</p>

<p>Rust选择「<ruby>零<rt>Zero</rt>额外开销<rt>Overhead</rt></ruby>」所以必须用这种方式来实现closure。使用高级抽象的同时保持了性能无损。比如说我们能用很函数式的方法处理迭代器，但最后生成的汇编和手写循环没什么区别。</p>

<p>并且Rust提供了 <code>Box&lt;Fn() -&gt; T&gt;</code> 和 <code>Rc</code>让你可以手动做到别的语言自动做到的事情。你需要显式使用这些设施，因为这代表额外的开销。</p>

<p>而选择隐式的捕获规则是因为closure被设计为在某个特定上下文内以短小、简洁而频繁的方式书写<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">2</a></sup>，所以采用了这种隐式且最保守的捕获方式。代价就是容易让人摸不着头脑。虽说利大于弊，但的确是一个缺点（参见下一节的边栏部分）。</p>

<h2>规则</h2>

<p>捕获规则最简单的情形是 <code>move || {...}</code> 它会尝试获取closure中用到的值的ownership，如果值是 <code>Copy</code> 的则 copy 一个。</p>

<p>而默认的捕获方式是：</p>

<ol>
  <li>如果可以，则尽量用 <code>&amp;</code> 借用</li>
  <li>否则，如果可以，则总是 <code>&amp;mut</code> 借用</li>
  <li>最后，无计可施必须要ownership的话，才会move</li>
</ol>

<aside>

  <p>以下内容可以跳过。</p>

  <p>即使是面临必须要ownership的情况，如果值可以 <code>Copy</code>，编译器依然会避免move，而是用 <code>&amp;</code> 的方式借用值，之后在需要的时候 <code>*</code>。相关文章是《<a href="https://iovxw.net/p/rust-closure-and-copy/">Rust 闭包环境捕获行为与 Copy trait</a>》。</p>

  <p>我们都认为是 bug，直到<a href="https://github.com/rust-lang/rust/issues/60413#issuecomment-487965668">语言团队成员回复说这是预料中的行为</a>。之后我注意到这是规则1较为反直觉的特例。</p>

</aside>

<p>捕获之后，根据<ins>你在closure代码中<strong>如何使用捕获到的值</strong></ins>，编译器会为closure实现函数traits。最后实现了哪些traits和捕获的方式（有没有加 <code>move</code>）<ins>或者捕获到了哪些变量是无关的</ins>。</p>

<ul>
  <li>所有函数都至少能调用一次，所以全都会实现 <code>FnOnce</code>。
    <ul>
      <li>另外，对于那些<em>不会<strong>移走</strong>匿名结构体中变量</em>的closure实现 <code>FnMut</code>。
        <ul>
          <li>并且，对于那些<em>不会<strong>修改</strong>匿名结构体中变量</em>的closure实现 <code>Fn</code>。</li>
        </ul>
      </li>
    </ul>
  </li>
</ul>

<p>下图中可以看出这三者是包含的关系。</p>

<p><a href="https://docs.google.com/drawings/d/1yuCkfuHW693Lg93in3Vk_hvIwJIQeOsfkX_2q5sC5H8/edit?usp=sharing"><img src="/media/Rust - Closure.png" alt="Rust - Closure" /></a></p>

<p>其中 <code>FnMut</code> 和 <code>Fn</code> 能调用多次。 <code>FnMut</code> 调用时需要对自己匿名结构体的 <code>&amp;mut self</code> 引用。调用 <code>Fn</code> 只需要 <code>&amp;self</code> 引用就足够了。</p>

<h2>实践</h2>

<p>现在我们写下不同类型的closure。然后去看编译器产出的MIR。</p>

<p>MIR是中级中间表示（简称中二表示）详细可以看<a href="https://blog.rust-lang.org/2016/04/19/MIR.html">官方博客的这篇文章</a>。我们关注的只是少部分内容，大部分看不懂也没关系。</p>

<p>总而言之，MIR告诉我们「代码究竟会变成什么样」但又保留了类型信息，不像汇编那样面目全非。</p>

<h3>FnOnce</h3>

<p>Closure中必须移走某个变量的ownership，这种closure需要 <code>self</code> 来执行，所以只能 <code>FnOnce</code>。<a href="https://play.rust-lang.org/?version=stable&amp;mode=debug&amp;edition=2018&amp;gist=49be2b83ebe6e9dc7c85fb833c7fd9d5">Playground</a> （点右上角“RUN”按钮旁的「…」按钮，再点 “MIR” 看结果。）</p>

<pre><code class="language-rust">fn main() {
    let homu = Homura;
    let get_homu = || homu;
    get_homu();
}
</code></pre>

<p>调用时的 MIR</p>

<pre><code class="language-rust">let mut _4: [closure@src/main.rs:9:20: 9:27 homu:Homura];
let mut _5: ();
_3 = const std::ops::FnOnce::call_once(move _4, move _5) -&gt; bb1;
</code></pre>

<p>可以看到它是以 <code>FnOnce</code> 方式调用的。</p>

<p><code>_4</code> 作为第一个参数传进去，它的类型 <code>[closure@src/main.rs:10:20: 10:27 homu:Homura]</code> 就是本文一直在叨念的匿名结构体了。其中 <code>home:Homura</code> 则是这个结构体捕获的变量和她的类型。</p>

<p><code>_5</code> 代表着无参数。</p>

<p>Closure代码所编译成的普通函数：</p>

<pre><code class="language-rust">fn main::(_1: [closure@src/main.rs:9:20: 9:27 homu:Homura]) -&gt; Homura {
    let mut _0: Homura;                  // return place

    bb0: {                              
        _0 = move (_1.0: Homura);        // bb0[0]: scope 0 at src/main.rs:9:23: 9:27
        return;                          // bb0[1]: scope 0 at src/main.rs:9:27: 9:27
    }
}
</code></pre>
<p>注意这里 <code>_1</code> 的类型：<code>[closure@src/main.rs:9:20: 9:27 homu:Homura]</code> 前没有 <code>&amp;</code> 或者 <code>&amp;mut</code>，代表这个调用后会消耗掉匿名结构体。</p>

<p><code>_0 = move (_1.0: Homura);</code> 可以看见内部移走了 <code>homu</code>。</p>

<h3>FnMut</h3>

<p>在closure中修改某个可变的引用<sup id="fnref:exception" role="doc-noteref"><a href="#fn:exception" class="footnote" rel="footnote">3</a></sup>，但无需移走任何捕获到的值。这种closure必须请求一个 <code>&amp;mut</code>，所以有 <code>FnMut</code>。 <a href="https://play.rust-lang.org/?version=stable&amp;mode=debug&amp;edition=2018&amp;gist=ec4a6dfb4373776899a44d4ab6f5efb7">Playground</a></p>

<pre><code class="language-rust">fn main() {
    let mut madoka: Option&lt;Madoka&gt; = Some(Madoka);
    let mut disappear = || madoka = None;
    disappear();
}
</code></pre>

<p>调用时：</p>

<pre><code class="language-rust">let mut _6: &amp;mut [closure@src/main.rs:9:25: 9:41 madoka:&amp;mut std::option::Option&lt;Madoka&gt;];
let mut _7: ();
_5 = const std::ops::FnMut::call_mut(move _6, move _7) -&gt; bb1;
</code></pre>

<p>Closure 所生成的函数体：</p>

<pre><code class="language-rust">fn main::(_1: &amp;mut [closure@src/main.rs:9:25: 9:41 madoka:&amp;mut std::option::Option&lt;Madoka&gt;]) -&gt; () {
	// ...
}
</code></pre>

<p>可以看到 <code>_1</code> 变成一个 <code>&amp;mut</code> 引用了。能多次调用而不会消耗匿名结构体。</p>

<p>被捕获的值变成了 <code>madoka:&amp;mut std::option::Option&lt;Madoka&gt;</code> 。于是在这个 closure 销毁之前别人都不能访问 <code>madoka</code> 了。</p>

<h3>Fn</h3>

<p>在closure中只会读取外部的值，只需要 <code>&amp;self</code> 就能执行，当然全部三种都实现了。</p>

<pre><code class="language-rust">fn main() {
    let homu = Homura;
    let mado = Madoka;
    let marry = || (&amp;homu, &amp;mado);
  	marry();
}
</code></pre>

<p><a href="https://play.rust-lang.org/?version=stable&amp;mode=debug&amp;edition=2018&amp;gist=111e670f19717bfb7331e38e206bb165">Playground</a></p>

<p>调用时：</p>

<pre><code class="language-rust">let mut _7: &amp;[closure@src/main.rs:10:17: 10:34 homu:&amp;Homura, mado:&amp;Madoka];
let mut _8: ();
_6 = const std::ops::Fn::call(move _7, move _8) -&gt; bb1;
</code></pre>

<p>是用 <code>Fn</code> 的方式调用的。</p>

<p>Closure 生成的函数体：</p>

<pre><code class="language-rust">fn main::(_1: &amp;[closure@src/main.rs:10:17: 10:34 homu:&amp;Homura, mado:&amp;Madoka]) -&gt; (&amp;Homura, &amp;Madoka) {
	// ...
}
</code></pre>

<p>如果closure根本不捕获任何东西，则匿名结构体是<a href="https://doc.rust-lang.org/nomicon/exotic-sizes.html#zero-sized-types-zsts">Zero Sized Types</a>，在运行时不会被创建。这类closure等价于普通函数，自然也实现了全部三种。代码略。</p>

<h3>实现哪些 traits 和捕获到的值无关</h3>

<p>就算用 <code>move</code> 强制捕获变量的所有权，只要不移走它而仅仅是修改或读取它。这种情况依然会实现 <code>FnMut</code> 或 <code>Fn</code>。<a href="https://play.rust-lang.org/?version=stable&amp;mode=debug&amp;edition=2018&amp;gist=4e5061559d9d80ac364278e33caed614">Playground</a></p>

<pre><code class="language-rust">fn main() {
    let homu = Homura;
    let mado = Madoka;
    let marry = move || {
        (&amp;homu, &amp;mado);
    };
    marry();
}
</code></pre>

<p>这种代码，用了 <code>move</code>  所以会捕获 <code>homu</code> 和 <code>mado</code> 的所有权，但是MIR可以看到是通过 <code>Fn::call</code> 调用的：</p>

<pre><code class="language-rust">let mut _5: &amp;[closure@src/main.rs:10:17: 12:6 homu:Homura, mado:Madoka];
let mut _6: ();
_4 = const std::ops::Fn::call(move _5, move _6) -&gt; bb1;
</code></pre>

<p>看看closure所生成的函数体吧：</p>

<pre><code class="language-rust">fn main::(_1: &amp;[closure@src/main.rs:10:17: 12:6 homu:Homura, mado:Madoka]) -&gt; () {
    let mut _0: ();                      // return place
    let mut _2: (&amp;Homura, &amp;Madoka);
    let mut _3: &amp;Homura;
    let mut _4: &amp;Madoka;

    bb0: {                              
        // ...
        _3 = &amp;((*_1).0: Homura);
        StorageLive(_4);
        _4 = &amp;((*_1).1: Madoka);
        (_2.0: &amp;Homura) = move _3;
        (_2.1: &amp;Madoka) = move _4;
        // ...
        return;
    }
}
</code></pre>

<p>不同于前一个没有加 <code>move</code> 的例子。<code>homu:Homura</code> 和 <code>mado:Madoka</code> 前<strong>没有</strong> <code>&amp;</code>，代表匿名结构体捕获了这两个变量的所有权。</p>

<p>然而捕获了那些变量的匿名结构体本身又是以 <code>_1: &amp;[closure...]</code> 的方式传入的。因为函数体内根本不会移走 <code>homu</code> 或者 <code>mado</code>。</p>

<p>如果修改这份代码在 closure 过程内修改 <code>mado</code> 的话会变成什么样呢？留作习题。</p>

<hr />

<p><em>感谢 Telegram「Rust 众」群网友们对本文的帮助。</em></p>

<h2>参考阅读</h2>

<ol>
  <li><a href="https://stevedonovan.github.io/rustifications/2018/08/18/rust-closures-are-hard.html">Why Rust Closures are (Somewhat) Hard</a></li>
  <li><a href="https://doc.rust-lang.org/reference/types/closure.html">Closure types</a></li>
  <li><a href="https://doc.rust-lang.org/std/ops/index.html"><code>std::ops::{Fn, FnMut, FnOnce}</code></a></li>
  <li><a href="https://zhuanlan.zhihu.com/p/23710601">闭包 - Rust编程</a></li>
  <li><a href="https://doc.rust-lang.org/nomicon/hrtb.html">Higher-Rank Trait Bounds (HRTBs)</a></li>
</ol>

<h2>脚注</h2>
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:not-same-type" role="doc-endnote">
      <p>其实所有的普通函数也都是唯一的类型。被视作 <a href="https://doc.rust-lang.org/nomicon/exotic-sizes.html#zero-sized-types-zsts">Zero Sized Types</a>。 <a href="#fnref:not-same-type" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2" role="doc-endnote">
      <p>比如参数和返回值类型都可以省略。 <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:exception" role="doc-endnote">
      <p>有一种<a href="https://doc.rust-lang.org/reference/types/closure.html#unique-immutable-borrows-in-captures">符合直觉的例外在这里</a>。 <a href="#fnref:exception" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Train Train</name></author><category term="dev" /><summary type="html"><![CDATA[Rust中的closure（闭包函数），很难用！这里记录下重要的事情。 原理 一些语言中没有closure和普通函数的区分，但Rust有。对Rust来说普通函数就是一段代码。而closure和 C++ 类似：每个closure会创建一个匿名的 struct，编译器会在当前上下文 Context捕获closure代码中的外部变量然后塞进这个结构体 Struct里面。 这件事非常重要，请默念三遍一个closure就是一个捕获了当前上下文变量的结构体（外加一段代码，这不重要）。 这解释了为什么Rust中两个参数和返回值一样的closure不被视作同一类型1，因为它们背后的匿名结构体不同，有着不同的大小、字段和lifetime。 let m = 1.0; let c = 2.0; let line = |x| m*x + c; // 等价于 struct SomeUnknownType&lt;'a&gt; { m: &amp;'a f64, c: &amp;'a f64 } impl&lt;'a&gt; SomeUnknownType&lt;'a&gt; { fn call(&amp;self, x: f64) -&gt; f64 { self.m * x + self.c } } 例子来源于Why Rust Closures are (Somewhat) Hard。 这也是closure难用的根源： Rust中结构体的可变性以及liftime本身就很烦人。 Closure的规则都是隐式的：closure捕获值的方式及所生成的closure的类型都是按照隐式的规则决定的。 Closure一直会捕获整个复合类型，如 struct, tuple 和 enum 。而不只是单个字段。[2] 对于 (3)，Rust团队已经接受了一个提案，旨在改进不相交字段的捕获规则。（当前看起来没多少进展） 为什么 对于 (1) 和 (2) 是语言设计思路所带来的结果，为什么会这样呢？ 因为closure很好用，但是我们不想付出运行时代价。所有语言都有类似的东西，但是它们把closure捕获的结构丢到堆上以保证所有closure类型大小一样，且借助了GC管理资源。 Rust选择「零Zero额外开销Overhead」所以必须用这种方式来实现closure。使用高级抽象的同时保持了性能无损。比如说我们能用很函数式的方法处理迭代器，但最后生成的汇编和手写循环没什么区别。 并且Rust提供了 Box&lt;Fn() -&gt; T&gt; 和 Rc让你可以手动做到别的语言自动做到的事情。你需要显式使用这些设施，因为这代表额外的开销。 而选择隐式的捕获规则是因为closure被设计为在某个特定上下文内以短小、简洁而频繁的方式书写2，所以采用了这种隐式且最保守的捕获方式。代价就是容易让人摸不着头脑。虽说利大于弊，但的确是一个缺点（参见下一节的边栏部分）。 规则 捕获规则最简单的情形是 move || {...} 它会尝试获取closure中用到的值的ownership，如果值是 Copy 的则 copy 一个。 而默认的捕获方式是： 如果可以，则尽量用 &amp; 借用 否则，如果可以，则总是 &amp;mut 借用 最后，无计可施必须要ownership的话，才会move 以下内容可以跳过。 即使是面临必须要ownership的情况，如果值可以 Copy，编译器依然会避免move，而是用 &amp; 的方式借用值，之后在需要的时候 *。相关文章是《Rust 闭包环境捕获行为与 Copy trait》。 我们都认为是 bug，直到语言团队成员回复说这是预料中的行为。之后我注意到这是规则1较为反直觉的特例。 捕获之后，根据你在closure代码中如何使用捕获到的值，编译器会为closure实现函数traits。最后实现了哪些traits和捕获的方式（有没有加 move）或者捕获到了哪些变量是无关的。 所有函数都至少能调用一次，所以全都会实现 FnOnce。 另外，对于那些不会移走匿名结构体中变量的closure实现 FnMut。 并且，对于那些不会修改匿名结构体中变量的closure实现 Fn。 下图中可以看出这三者是包含的关系。 其中 FnMut 和 Fn 能调用多次。 FnMut 调用时需要对自己匿名结构体的 &amp;mut self 引用。调用 Fn 只需要 &amp;self 引用就足够了。 实践 现在我们写下不同类型的closure。然后去看编译器产出的MIR。 MIR是中级中间表示（简称中二表示）详细可以看官方博客的这篇文章。我们关注的只是少部分内容，大部分看不懂也没关系。 总而言之，MIR告诉我们「代码究竟会变成什么样」但又保留了类型信息，不像汇编那样面目全非。 FnOnce Closure中必须移走某个变量的ownership，这种closure需要 self 来执行，所以只能 FnOnce。Playground （点右上角“RUN”按钮旁的「…」按钮，再点 “MIR” 看结果。） fn main() { let homu = Homura; let get_homu = || homu; get_homu(); } 调用时的 MIR let mut _4: [closure@src/main.rs:9:20: 9:27 homu:Homura]; let mut _5: (); _3 = const std::ops::FnOnce::call_once(move _4, move _5) -&gt; bb1; 可以看到它是以 FnOnce 方式调用的。 _4 作为第一个参数传进去，它的类型 [closure@src/main.rs:10:20: 10:27 homu:Homura] 就是本文一直在叨念的匿名结构体了。其中 home:Homura 则是这个结构体捕获的变量和她的类型。 _5 代表着无参数。 Closure代码所编译成的普通函数： fn main::(_1: [closure@src/main.rs:9:20: 9:27 homu:Homura]) -&gt; Homura { let mut _0: Homura; // return place bb0: { _0 = move (_1.0: Homura); // bb0[0]: scope 0 at src/main.rs:9:23: 9:27 return; // bb0[1]: scope 0 at src/main.rs:9:27: 9:27 } } 注意这里 _1 的类型：[closure@src/main.rs:9:20: 9:27 homu:Homura] 前没有 &amp; 或者 &amp;mut，代表这个调用后会消耗掉匿名结构体。 _0 = move (_1.0: Homura); 可以看见内部移走了 homu。 FnMut 在closure中修改某个可变的引用3，但无需移走任何捕获到的值。这种closure必须请求一个 &amp;mut，所以有 FnMut。 Playground fn main() { let mut madoka: Option&lt;Madoka&gt; = Some(Madoka); let mut disappear = || madoka = None; disappear(); } 调用时： let mut _6: &amp;mut [closure@src/main.rs:9:25: 9:41 madoka:&amp;mut std::option::Option&lt;Madoka&gt;]; let mut _7: (); _5 = const std::ops::FnMut::call_mut(move _6, move _7) -&gt; bb1; Closure 所生成的函数体： fn main::(_1: &amp;mut [closure@src/main.rs:9:25: 9:41 madoka:&amp;mut std::option::Option&lt;Madoka&gt;]) -&gt; () { // ... } 可以看到 _1 变成一个 &amp;mut 引用了。能多次调用而不会消耗匿名结构体。 被捕获的值变成了 madoka:&amp;mut std::option::Option&lt;Madoka&gt; 。于是在这个 closure 销毁之前别人都不能访问 madoka 了。 Fn 在closure中只会读取外部的值，只需要 &amp;self 就能执行，当然全部三种都实现了。 fn main() { let homu = Homura; let mado = Madoka; let marry = || (&amp;homu, &amp;mado); marry(); } Playground 调用时： let mut _7: &amp;[closure@src/main.rs:10:17: 10:34 homu:&amp;Homura, mado:&amp;Madoka]; let mut _8: (); _6 = const std::ops::Fn::call(move _7, move _8) -&gt; bb1; 是用 Fn 的方式调用的。 Closure 生成的函数体： fn main::(_1: &amp;[closure@src/main.rs:10:17: 10:34 homu:&amp;Homura, mado:&amp;Madoka]) -&gt; (&amp;Homura, &amp;Madoka) { // ... } 如果closure根本不捕获任何东西，则匿名结构体是Zero Sized Types，在运行时不会被创建。这类closure等价于普通函数，自然也实现了全部三种。代码略。 实现哪些 traits 和捕获到的值无关 就算用 move 强制捕获变量的所有权，只要不移走它而仅仅是修改或读取它。这种情况依然会实现 FnMut 或 Fn。Playground fn main() { let homu = Homura; let mado = Madoka; let marry = move || { (&amp;homu, &amp;mado); }; marry(); } 这种代码，用了 move 所以会捕获 homu 和 mado 的所有权，但是MIR可以看到是通过 Fn::call 调用的： let mut _5: &amp;[closure@src/main.rs:10:17: 12:6 homu:Homura, mado:Madoka]; let mut _6: (); _4 = const std::ops::Fn::call(move _5, move _6) -&gt; bb1; 看看closure所生成的函数体吧： fn main::(_1: &amp;[closure@src/main.rs:10:17: 12:6 homu:Homura, mado:Madoka]) -&gt; () { let mut _0: (); // return place let mut _2: (&amp;Homura, &amp;Madoka); let mut _3: &amp;Homura; let mut _4: &amp;Madoka; bb0: { // ... _3 = &amp;((*_1).0: Homura); StorageLive(_4); _4 = &amp;((*_1).1: Madoka); (_2.0: &amp;Homura) = move _3; (_2.1: &amp;Madoka) = move _4; // ... return; } } 不同于前一个没有加 move 的例子。homu:Homura 和 mado:Madoka 前没有 &amp;，代表匿名结构体捕获了这两个变量的所有权。 然而捕获了那些变量的匿名结构体本身又是以 _1: &amp;[closure...] 的方式传入的。因为函数体内根本不会移走 homu 或者 mado。 如果修改这份代码在 closure 过程内修改 mado 的话会变成什么样呢？留作习题。 感谢 Telegram「Rust 众」群网友们对本文的帮助。 参考阅读 Why Rust Closures are (Somewhat) Hard Closure types std::ops::{Fn, FnMut, FnOnce} 闭包 - Rust编程 Higher-Rank Trait Bounds (HRTBs) 脚注 其实所有的普通函数也都是唯一的类型。被视作 Zero Sized Types。 &#8617; 比如参数和返回值类型都可以省略。 &#8617; 有一种符合直觉的例外在这里。 &#8617;]]></summary></entry><entry><title type="html">驳《表演性自谦》</title><link href="https://ioover.net/talk/anti-performance-self-modest/" rel="alternate" type="text/html" title="驳《表演性自谦》" /><published>2019-04-22T07:21:53+00:00</published><updated>2019-04-22T07:21:53+00:00</updated><id>https://ioover.net/talk/anti-performance-self-modest</id><content type="html" xml:base="https://ioover.net/talk/anti-performance-self-modest/"><![CDATA[<p>去年写了《<a href="https://ioover.net/talk/performance-self-modest/">表演性自谦</a>》这篇文章。刚刚重新想到这个话题，又有了新的想法，记录一下。<del>本文原想以《驳〈表演性自谦〉》为标题，趴在地上仔细想了想还是噱头多过实际。</del><ins>噱头就噱头，老子不怕！</ins></p>

<p>《表演性自谦》这篇文章，发泄情绪多于分析和劝诫。发现问题后大声喊出来并且嘲讽一遍，把别人逼到对立面，却没有细想问题是怎么产生的。</p>

<p>我也是经历过这个过程的，我也趴在地上喊着「巨巨」，我是为了表演吗？部分是。但表演就真是目的吗？<!--more--></p>

<p>我们表演自己的谦虚，诚然这样能满足一些阴暗的部分，但这不能解释当今遍地都在「卖弱」。我们都不厉害，那些我们眼中厉害的人同样不觉得自己厉害<sup id="fnref:imposter" role="doc-noteref"><a href="#fn:imposter" class="footnote" rel="footnote">1</a></sup>。我们说自己弱的时候往往不是说谎，但无论怎样严密论证自己有多弱，也不能解释为什么要把它给表现出来。在前文中，我光是宣泄了对这种表现的厌恶，那么成因呢？难道我们一个一个都是「戏精」<sup id="fnref:xijing" role="doc-noteref"><a href="#fn:xijing" class="footnote" rel="footnote">2</a></sup>吗？</p>

<p>趴在地上仔细再想了想，答案显而易见：表演是手段，表演是为了自我保护。</p>

<p>人的处境以前是以生活的周边来决定的。比如说大锤在三班，班里作文大锤写得最好，大锤自然有资格骄傲。尽管可能他在市里、区里、校里乃至年级的作文比赛都没办法出人头地。可这关系不大，比赛对大锤来说也只是一个「事件」，而不是「生活」，他在学校里日常还是能作为三班里作文第一，时常被语文老师拿来当范文。</p>

<p>而现在，人生活的相当部分是在赛博世界的，对我们来说网络是持续的「生活」。</p>

<p>大锤之后加了一个写作群。在群里有一两位「大佬」，是全国作文比赛奖牌得主，但他们还在仰望着别处。</p>

<p>人们的处境从身边的人陡然扩大到了全国乃至全世界。自己小小的得意、小小的骄傲，在沛然的因特网之下变得可笑起来。即使是不带恶意的客观评价就能扑灭。更何况人与人之间从来就不缺少恶意，小份量的恶意足以令人忌惮。</p>

<p>比起展现自己谦虚，我们又何尝不想为自己自豪，何尝不想被夸奖。但是这不可以的。我们得意起来的时候，心里会浮现一个他人的眼光审视着自己，以前是身边同学的目光，在现在则是五湖四海趣味相投的网友的。我们再也做不到为自己所做的这一点点事情而感到特别得意了。实际面对网友的时候为了自我保护又会把腰压得再低一些。</p>

<p>我觉得「表演性自谦」可恶，因为它不真。但这个世界并不鼓励真。人就是用不真的东西来保护自己的。这个世界就是这样运作的。
<br />
<br />
<br />
<br /><br /></p>

<p>你已经做得很好了。别老是用世界性的外部目光审视自己。你觉得骄傲是因为做到了曾经没做到的事情，曾经的你望向未来，不会不想成为此刻的你的（虽然肯定会更贪婪些），这才是心中自豪的来源。</p>

<hr />

<p>我觉得这篇文章写得很好。嗯。</p>
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:imposter" role="doc-endnote">
      <p>冒名顶替综合症这个话题，我并不觉得我们眼中的大人物感受到自身的「弱」是假的。 <a href="#fnref:imposter" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:xijing" role="doc-endnote">
      <p>很讨厌这个词。 <a href="#fnref:xijing" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Train Train</name></author><category term="talk" /><summary type="html"><![CDATA[去年写了《表演性自谦》这篇文章。刚刚重新想到这个话题，又有了新的想法，记录一下。本文原想以《驳〈表演性自谦〉》为标题，趴在地上仔细想了想还是噱头多过实际。噱头就噱头，老子不怕！ 《表演性自谦》这篇文章，发泄情绪多于分析和劝诫。发现问题后大声喊出来并且嘲讽一遍，把别人逼到对立面，却没有细想问题是怎么产生的。 我也是经历过这个过程的，我也趴在地上喊着「巨巨」，我是为了表演吗？部分是。但表演就真是目的吗？]]></summary></entry><entry><title type="html">I wanna talk about philosophy</title><link href="https://ioover.net/talk/i-wanna-talk-about-philosophy/" rel="alternate" type="text/html" title="I wanna talk about philosophy" /><published>2019-04-17T20:17:12+00:00</published><updated>2019-04-17T20:17:12+00:00</updated><id>https://ioover.net/talk/i-wanna-talk-about-philosophy</id><content type="html" xml:base="https://ioover.net/talk/i-wanna-talk-about-philosophy/"><![CDATA[<p>这个世界分工越来越细致越来越专业化。即使是软件工程师，也不需要懂操作系统进程资源表的组织方式就能构建软件。现代学术<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup>都是这样。</p>

<p>最前沿的哲学无可辩驳地向上累积着智慧，却离我们越来越远。普通人能想到的一切都被讨论过了，讨论又产生新的问题。这样不断累积下去，一个人如果不是前沿的学者，不管有什么想法，要不是有严重的错误，要不就已经被说出来了。</p>

<p>但麻烦的事情是和别的学科不一样，哲学它<strong>不是</strong>专家研究后普通人坐享其成的东西。哲学这东西<em>是我们普通人需要去思考的</em>。哲学这东西至少应该有这样的作用：我们普通人能用它来审视自己的生活。</p>

<p>这就产生很大的麻烦了，我不知道怎么解决。不知道你有什么想法。</p>

<p>至少有一点是确定的，<em>我们应该去「民哲」</em>。不要去害怕说出幼稚的观点。想得太多读得太少是问题，但这不是不去表达的理由。日后推翻自己的观念之后回过头对比看看也是挺好的。我初中时候写过一篇文章，被人用夸奖的语气提及，我立刻害羞删掉了，自此对这个话题讳忌莫深。太蠢了。</p>
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>不管什么东西到了现代就变得奇怪了，你有什么头猪嘛？ <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Train Train</name></author><category term="talk" /><summary type="html"><![CDATA[这个世界分工越来越细致越来越专业化。即使是软件工程师，也不需要懂操作系统进程资源表的组织方式就能构建软件。现代学术1都是这样。 最前沿的哲学无可辩驳地向上累积着智慧，却离我们越来越远。普通人能想到的一切都被讨论过了，讨论又产生新的问题。这样不断累积下去，一个人如果不是前沿的学者，不管有什么想法，要不是有严重的错误，要不就已经被说出来了。 但麻烦的事情是和别的学科不一样，哲学它不是专家研究后普通人坐享其成的东西。哲学这东西是我们普通人需要去思考的。哲学这东西至少应该有这样的作用：我们普通人能用它来审视自己的生活。 这就产生很大的麻烦了，我不知道怎么解决。不知道你有什么想法。 至少有一点是确定的，我们应该去「民哲」。不要去害怕说出幼稚的观点。想得太多读得太少是问题，但这不是不去表达的理由。日后推翻自己的观念之后回过头对比看看也是挺好的。我初中时候写过一篇文章，被人用夸奖的语气提及，我立刻害羞删掉了，自此对这个话题讳忌莫深。太蠢了。 不管什么东西到了现代就变得奇怪了，你有什么头猪嘛？ &#8617;]]></summary></entry><entry><title type="html">比喻</title><link href="https://ioover.net/talk/metaphor/" rel="alternate" type="text/html" title="比喻" /><published>2019-03-04T05:13:56+00:00</published><updated>2019-03-04T05:13:56+00:00</updated><id>https://ioover.net/talk/metaphor</id><content type="html" xml:base="https://ioover.net/talk/metaphor/"><![CDATA[<p>我曾把比喻当成一种罪孽，觉得比喻是偷换概念和误解的温床，是应该避免的东西。</p>

<p>假设有这样一句话「政府和人民就像父亲和儿子」从直觉上来说不无道理。可是如果说父亲和儿子的话：母亲是谁？母亲和父亲是合法夫妻吗？领了结婚证吗？做爱做的事情了吗？是顺产还是刨腹产？</p>

<p>巧妙的比喻也能巧妙地曲解。拿个历史上最成功的比喻「<a href="https://zh.wikipedia.org/wiki/%E5%9C%B0%E7%A9%B4%E5%AF%93%E8%A8%80">洞穴神话</a>」来说吧。如果把影子的比喻和几何联系在一起，别人可以去主张「理型其实存在于四维或更高维空间」，配合一些神神叨叨的话还挺能唬人。</p>

<p>更加实际的例子。物理（弦理论）中有一个推论叫「<a href="https://en.wikipedia.org/wiki/Holographic_principle">全息宇宙理论</a>」，说我们的世界可能是以二维方式存在的，而我们感知到的三维宇宙其实是一种不准确的近似。「全息」这个词在这里的作用是「低维重建高维」。这个理论本身是基于计算而来的，传播的时候变了味道：全息摄影有个特点是照片的碎片也包含着全局的信息。把这个套用在「全息宇宙」上，就有一种奇妙的曲解「我们每个人包含着这个宇宙的全部信息」，把前沿假说变成了一种灵修伪科学了。</p>

<p>不管是叙述的人还是听者都能大做文章。不管有意无意，这种错误都会变得很难觉察，叙述者犯了错就是偷换概念，听者理解错就是误解。</p>

<p>比喻不一定是坏东西。清晰准确的论证很好，但是别人理解起来更加费力；又难以传达一些细微的直觉的感受景象。好的比喻就像洞穴神话。它不仅仅好好表达了观点，还表达了一种感受，一种冲动。人看了以后产生想要从墙面上的虚影挣脱出来的意愿。</p>

<p>面对比喻，重点在于本体和喻体的变换。本体是什么？喻体是什么？本体之间的关系是什么？喻体之间的关系是什么？这些关系能对应而不出差错吗？在这个比喻中，哪些关系是有关的，而哪些关系是无关的？最重要的是，<em>喻体之间的关系是不能回馈给本体的</em>。大部分比喻的问题就出在这一步。</p>

<p>「父亲和儿子」的比喻可能关注是精神上的关系、服从、对人格的影响等等，而喻体的那堆生育问题是不相关的。「洞穴神话」关注的是真实和虚假，几何或者光学在这里也是不相关的。「全息」的比喻在于低维重建高维，全息照片本身的部分重建全部在这里还是不相干的。这些不相干的东西是不能回馈给本体的，否则就是偷换概念。</p>

<p>网上看到的比喻很多是有问题的。每当看到一个比喻，看看有没有利用这个比喻从喻体那里回馈了多余的信息。</p>

<p>比喻能让论述更有说服力、直观、还能传递出一些情感，但不能代替论述本身。你总是需要说清楚自己究竟在说什么，否则也怨不得别人搞错。做比喻之前理清楚自己要论证的是什么，在自己的比喻中哪些关系是相关的、哪些是不相关的，然后去比喻吧。</p>]]></content><author><name>Train Train</name></author><category term="talk" /><summary type="html"><![CDATA[我曾把比喻当成一种罪孽，觉得比喻是偷换概念和误解的温床，是应该避免的东西。 假设有这样一句话「政府和人民就像父亲和儿子」从直觉上来说不无道理。可是如果说父亲和儿子的话：母亲是谁？母亲和父亲是合法夫妻吗？领了结婚证吗？做爱做的事情了吗？是顺产还是刨腹产？ 巧妙的比喻也能巧妙地曲解。拿个历史上最成功的比喻「洞穴神话」来说吧。如果把影子的比喻和几何联系在一起，别人可以去主张「理型其实存在于四维或更高维空间」，配合一些神神叨叨的话还挺能唬人。 更加实际的例子。物理（弦理论）中有一个推论叫「全息宇宙理论」，说我们的世界可能是以二维方式存在的，而我们感知到的三维宇宙其实是一种不准确的近似。「全息」这个词在这里的作用是「低维重建高维」。这个理论本身是基于计算而来的，传播的时候变了味道：全息摄影有个特点是照片的碎片也包含着全局的信息。把这个套用在「全息宇宙」上，就有一种奇妙的曲解「我们每个人包含着这个宇宙的全部信息」，把前沿假说变成了一种灵修伪科学了。 不管是叙述的人还是听者都能大做文章。不管有意无意，这种错误都会变得很难觉察，叙述者犯了错就是偷换概念，听者理解错就是误解。 比喻不一定是坏东西。清晰准确的论证很好，但是别人理解起来更加费力；又难以传达一些细微的直觉的感受景象。好的比喻就像洞穴神话。它不仅仅好好表达了观点，还表达了一种感受，一种冲动。人看了以后产生想要从墙面上的虚影挣脱出来的意愿。 面对比喻，重点在于本体和喻体的变换。本体是什么？喻体是什么？本体之间的关系是什么？喻体之间的关系是什么？这些关系能对应而不出差错吗？在这个比喻中，哪些关系是有关的，而哪些关系是无关的？最重要的是，喻体之间的关系是不能回馈给本体的。大部分比喻的问题就出在这一步。 「父亲和儿子」的比喻可能关注是精神上的关系、服从、对人格的影响等等，而喻体的那堆生育问题是不相关的。「洞穴神话」关注的是真实和虚假，几何或者光学在这里也是不相关的。「全息」的比喻在于低维重建高维，全息照片本身的部分重建全部在这里还是不相干的。这些不相干的东西是不能回馈给本体的，否则就是偷换概念。 网上看到的比喻很多是有问题的。每当看到一个比喻，看看有没有利用这个比喻从喻体那里回馈了多余的信息。 比喻能让论述更有说服力、直观、还能传递出一些情感，但不能代替论述本身。你总是需要说清楚自己究竟在说什么，否则也怨不得别人搞错。做比喻之前理清楚自己要论证的是什么，在自己的比喻中哪些关系是相关的、哪些是不相关的，然后去比喻吧。]]></summary></entry></feed>