一直希望提高打字速度和准确率,于是打算学习双拼,考察了各种方案最后选了小鹤。原因如下:
在决定切换之前最好先在打字测速软件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.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. 还有一类神奇的输入法叫做顶功类输入法,据说是不需要按空格键不需要选字的。看到小鹤的作者讨论这类输入法。
]]>我是不能接受马克思/共产主义/社会主义作为国家的根本的,历史上所有尝试均失败就足够以为理由了。有人解释历史的原因、地缘政治的原因、如何如何的原因,对此我只想捂住耳朵说「我不听我不听我不听」。除非有一个成功稳定的真社会主义政权否则这些辩护都很可疑。
资本主义是自发形成的,用时髦的词儿来说经济是「复杂系统的涌现」,看似人类掌中物其实是完全异质的东西。资本主义会不断演化适应,而这种演化是没有人设计的,人只能去尝试调整,像医生对身体所做的那样。1 而概括任何社会主义的核心——依我看——是一种愿望,试图塑造和把控人的行动所形成的社会的愿望。这种愿望我不相信会完全成功。
资本主义是超越人的东西,是人类行为所创造的「大自然」,自是不能指望它有任何人的温情和理想,描写其恶果的书早已汗牛充栋。而马克思主义无论酿成什么血腥恶果,他们的初衷确实是为了人能好好生活,是人的愿望。他们必须存在下去,2永远作为一股制衡非人性的资本主义的力量,永远为此提供思想和工具,但永远不能达成他们所梦想的「革命」。
可这就足够了吗?这像是在放弃思考,把问题推给未来的风云变幻。况且这种状态就是世界很多民主国家当下在经历的,也不见得他们现在感觉多好。难道说我有信心担保让他们继续下去就会越来越好吗?不可能有这样的信心。可能我只是认为这是最不坏的选择吧。
唉。到头来其实没什么想法。仅作记录吧。
]]>Rust的所有权和借用模型涉及使用引用(references)去操作借来的数据,类型系统区分了两种不同的基本引用类型。在代码中写成 &T
和 &mut T
。
&mut T
一般称为对类型为 T
的数据的「可变引用」(mutable reference)。而 &T
则是一个对于 T
的「不可变引用」(immutable reference)或者「常量引用」(const reference)。这些名字不错,对Rust新手能建立合理的直觉。但这篇文章会讲一些理由来说明,对于新手阶段之后的Rust使用者来说,更好的名字是「共享引用」(shared reference)和「独占引用」(exclusive reference)。
如同 Rust 书中的「引用和借用」一章所述,函数如果获取了一个不可变引用的参数,那就可以读取引用指向的数据:
struct Point {
x: u32,
y: u32,
}
fn print_point(pt: &Point) {
println!("x={} y={}", pt.x, pt.y);
}
但不允许改变数据:
fn embiggen_x(pt: &Point) {
pt.x = pt.x * 2;
}
error[E0594]: cannot assign to `pt.x` which is behind a `&` reference
--> src/main.rs
|
1 | fn embiggen_x(pt: &Point) {
| ------ help: consider changing this to be a mutable reference: `&mut Point`
2 | pt.x = pt.x * 2;
| ^^^^^^^^^^^^^^^ `pt` is a `&` reference, so the data it refers to cannot be written
要改变结构的字段或者调用那些修改类方法,参数必须通过 &mut
引用获取。
fn embiggen_x(pt: &mut Point) {
pt.x = pt.x * 2; // okay
}
用Rust写一些玩具程序的话,这种区分,这种「不可变引用」和「可变引用」的术语通常也足够了。
迟早你会遇到一个库签名,直截了当地相悖于初学者对Rust引用的心智模型。作为一个例子,来看看标准库中 AtomicU32
的 store
方法的签名:
impl AtomicU32 {
pub fn store(&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<T>::store
这样的原子性修改方法只能用于可变引用。通过常量引用来储存值如同预料中那样不会通过编译。
// C++
#include <atomic>
void test(const std::atomic<unsigned>& 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<unsigned int>' to 'std::__atomic_base<unsigned int>' 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<unsigned int>' to 'volatile std::__atomic_base<unsigned int>' for object argument
store(__int_type __i,
^
有什么地方出了问题。这超出了初学者对Rust &
和 &mut
引用类型意义的理解。
&T
不是对那些类型为 T
的数据的 「不可变引用」或者「常量引用」,而是「共享引用」。&mut T
不是「可变引用」而是「独占引用」。
独占引用意味着在同一时刻,同一个值不可能存在别的引用。共享引用则意味着可能存在对同一个值的其它引用,也许是在别的线程(如果 T
实现了 Sync
的话)或是当前线程的调用栈中。Rust 借用检查器的一个关键职能就是确保独占引用真的是独占性的。
再看看 AtomicU32::store
的签名。
impl AtomicU32 {
pub fn store(&self, val: u32, order: Ordering);
}
对于函数用共享引用获取原子式的u32,此时应该感到完全自然。同一时刻对同一个 AtomicU32
有别的引用当然没问题。原子性就是为了并发读写而不导致数据争用(data race)而存在的。如果库在调用 store
时不允许别的引用存在,那就没什么理由用原子性了。
独占引用一直是可变的原因是如果没有别的代码看着同一个数据,我们可以大胆地修改数据而不引发数据争用。数据争用(data race)是指多个地方同时操作同一个数据,并且至少有一个在修改,从而产生意外的结果或者内存不安全的情况。但是通过原子性或者下述内部可变性的方式,通过共享引用修改数据也是安全的。
充分内化「共享引用」和「独占引用」,学会这样思考,是学会充分利用Rust与其强大的安全性保证的重要一步。
一开始将 &
和 &mut
作为不可变和可变来介绍的做法,我不觉得不好。学习曲线就经足够难了,即使不算上本文的内容。对于初学者而言,修改能力的不同是两种引用类型最显著的实际差异。
我觉得建立从「不可变引用」/「可变引用」到「共享引用」/「独占引用」的心智模型转变是必要的一步。应该鼓励初学者在正确的时间走出这一步,而本页可以帮助他们走出这一步。当有人困惑于一些库函数预料需要 &mut
却只获取 &
时,就是发本页链接的好时机了。
在内化了共享和独占引用之后,我觉得继续说「可变引用」也不错,毕竟关键字是 mut
。只要别忘了共享引用背后的数据有时也可能是可变的。另一方面,对于共享引用,我建议始终说「共享引用」而不是「不可变引用」或者「常量引用」。
在Rust中,术语「内部可变性」(interior mutability)表示支持通过共享引用修改数据。
我用 AtomicU32
作为例子的缘故是当你从初学者的心智模型切换到正确的心智模型时,它最能唤起从「浑身难受」到「完全自然」的深刻转变。尽管原子性是多线程代码的重要一块,但内部可变性在单线程中也同样重要。
通过共享引用持有可变数据的唯一方法是标准库类型 UnsafeCell<T>
。它是一种 unsafe 的底层工具,一般不会直接去使用。所有别的内部可变性方式都是建立在它之上的安全抽象,有着不同的性质和需求,适用于不同的情况。(根本上来说,Rust就是一个建立安全抽象的语言,而内部可变性就是其中一个最明显的部分。)
除了原子操作以外,其它建立在内部可变性上的标准库安全抽象还包括:
Cell<T>
: 修改是安全的,哪怕可能存在其它对同一个 Cell<T>
的引用,因为API施行:
Cell<T>
的应用,因为 Cell<T>
没有实现 Sync
trait,也就是说 Cell<T>
是单线程的;Cell<T>
中的内容的引用,因为这种引用可能因为修改而失效,作为替代所有的访问通过复制数据来完成。RefCell<T>
:修改是安全的,哪怕可能存在其它对同一个 RefCell<T>
的引用,因为API施行:
Cell<T>
,RefCell<T>
是单线程的,所以不同的线程不可能引用同一个值。Mutex<T>
:修改是安全的,哪怕可能存在其它对同一个 Mutex<T>
的引用,因为API施行:
T
读写。其他访问会被阻塞到现在的引用释放了锁为止。RwLock<T>
:修改是安全的,哪怕可能存在其它对同一个 RwLock<T>
的引用,因为API施行:
T
,且仅当此时没有别的引用在被读。“这个杀人犯是在贫民窟长大的。当他七个月大的时候,他的父亲遗弃了他。他经常受到母亲的虐待,他的哥哥姐姐也欺负他。他从来没有机会上学,而当他能找到工作的时候,他从来都保不住自己的工作。他抢那家商店的时候已经快饿死了,而且还染上了强烈的毒瘾,也没有朋友能够给他帮助。他姐姐说:‘当他还是小孩子时,我就知道他早晚会这么干的。’他母亲抱怨说:‘我不理解’检察官称之为‘一个冷酷无情的、蓄谋已久的行为’。辩方则控诉整个社会,声称正是社会的忽视和负面的影响才使此人不可避免地成为一个凶手。”我们知道这些辩论的其余部分,但我们不知道他们会如何解决。一个人应该为他的一生都在为此创造条件的行为负责吗?或者我们应不应该再坚持这样的看法:无论事件的背景如何,他本可以抵制,本可以决定不犯罪,所以他必须为此负责?
这段话原文出自于《大问题》,不过我第一次读到它是在这篇知乎文章中,如果没看过的话希望能去看一下。
不必多说,决定论自始至终是一个很有吸引力的选择,否则我们要放弃很多才能去主张自己有不被物理定律束缚的自由意志。但决定论带来的责任的丧失是种问题。
本文想要谈谈的是:我们能不能假设自己接受强决定论的前提,并且不去想什么「选择」或者「实践和理论」,然后去获得道德责任呢?我觉得是可以的。
想象一群理性人,他们都看过哲学方面的文章。有人犯罪的时候他用决定论给自己辩护:「嗨呀,你们都懂的,我做的事情是被决定的,你们有什么理由定我有罪。嗨呀,你们也都懂的,我干嘛说这些废话,快把我放出来!」
显然如果这种策略能成功的话,整个社会不能继续运转了。这对每个人的利益都是损害,哪怕是作恶的人。
所以这群理性人需要运作起道德,而这本身就能成为责任存在的理由。他们会共同假装责任存在,道德就如同某种共同的妥协或是社会契约一样运作。也就是说,即便大家都心知肚明彼此并没有选择的能力(自由意志),仍然会以彼此(某种程度上)有这种能力为前提而行动甚至思考。即使所有人心中最深处知道归根结底责任并不存在——可别忘了,从根本上来说不存在的东西太多了:国家、民族、货币价值、人生的意义、人类的意义…
很显然可以说这种假装的道德责任就是假的,是空洞而无力的。这是另一个大问题了:我们一定要认为一个被假装出来的东西就一定低劣吗?什么才是真实存在的?自然数1真实存在吗?我觉得道德责任一旦被生效践行了,哪怕来自于假装,也就是成立的了,因为它的成立不来自于它的前提而来自于它的被承认。
就道德本身而言,这种方法或许能提供一个调和效用主义和道德普遍主义思路。也就是存在这种可能,即,效用主义的结果告诉我们:假装接受一个反效用主义的普遍的道德规范才是效用最大的。这只是一个思路。
我上面说的这些很可能根本不值一提或是有严重错误,不然肯定已经早就被更完善地写进某本书里了。但写下来自己的想法总是好的。
]]>-----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 会看心情乱取。有缘再见啦。我早就对「酿泉」这个名字不爽了。
一些语言中没有closure和普通函数的区分,但Rust有。对Rust来说普通函数就是一段代码。而closure和 C++ 类似:每个closure会创建一个匿名的 struct
,编译器会在当前上下文 捕获closure代码中的外部变量然后塞进这个结构体 里面。
这件事非常重要,请默念三遍一个closure就是一个捕获了当前上下文变量的结构体(外加一段代码,这不重要)。
这解释了为什么Rust中两个参数和返回值一样的closure不被视作同一类型1,因为它们背后的匿名结构体不同,有着不同的大小、字段和lifetime。
let m = 1.0;
let c = 2.0;
let line = |x| m*x + c;
// 等价于
struct SomeUnknownType<'a> {
m: &'a f64,
c: &'a f64
}
impl<'a> SomeUnknownType<'a> {
fn call(&self, x: f64) -> 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选择「零额外开销」所以必须用这种方式来实现closure。使用高级抽象的同时保持了性能无损。比如说我们能用很函数式的方法处理迭代器,但最后生成的汇编和手写循环没什么区别。
并且Rust提供了 Box<Fn() -> T>
和 Rc
让你可以手动做到别的语言自动做到的事情。你需要显式使用这些设施,因为这代表额外的开销。
而选择隐式的捕获规则是因为closure被设计为在某个特定上下文内以短小、简洁而频繁的方式书写2,所以采用了这种隐式且最保守的捕获方式。代价就是容易让人摸不着头脑。虽说利大于弊,但的确是一个缺点(参见下一节的边栏部分)。
捕获规则最简单的情形是 move || {...}
它会尝试获取closure中用到的值的ownership,如果值是 Copy
的则 copy 一个。
而默认的捕获方式是:
&
借用&mut
借用捕获之后,根据你在closure代码中如何使用捕获到的值,编译器会为closure实现函数traits。最后实现了哪些traits和捕获的方式(有没有加 move
)或者捕获到了哪些变量是无关的。
FnOnce
。
FnMut
。
Fn
。下图中可以看出这三者是包含的关系。
其中 FnMut
和 Fn
能调用多次。 FnMut
调用时需要对自己匿名结构体的 &mut self
引用。调用 Fn
只需要 &self
引用就足够了。
现在我们写下不同类型的closure。然后去看编译器产出的MIR。
MIR是中级中间表示(简称中二表示)详细可以看官方博客的这篇文章。我们关注的只是少部分内容,大部分看不懂也没关系。
总而言之,MIR告诉我们「代码究竟会变成什么样」但又保留了类型信息,不像汇编那样面目全非。
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) -> 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]) -> 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]
前没有 &
或者 &mut
,代表这个调用后会消耗掉匿名结构体。
_0 = move (_1.0: Homura);
可以看见内部移走了 homu
。
在closure中修改某个可变的引用3,但无需移走任何捕获到的值。这种closure必须请求一个 &mut
,所以有 FnMut
。 Playground
fn main() {
let mut madoka: Option<Madoka> = Some(Madoka);
let mut disappear = || madoka = None;
disappear();
}
调用时:
let mut _6: &mut [closure@src/main.rs:9:25: 9:41 madoka:&mut std::option::Option<Madoka>];
let mut _7: ();
_5 = const std::ops::FnMut::call_mut(move _6, move _7) -> bb1;
Closure 所生成的函数体:
fn main::(_1: &mut [closure@src/main.rs:9:25: 9:41 madoka:&mut std::option::Option<Madoka>]) -> () {
// ...
}
可以看到 _1
变成一个 &mut
引用了。能多次调用而不会消耗匿名结构体。
被捕获的值变成了 madoka:&mut std::option::Option<Madoka>
。于是在这个 closure 销毁之前别人都不能访问 madoka
了。
在closure中只会读取外部的值,只需要 &self
就能执行,当然全部三种都实现了。
fn main() {
let homu = Homura;
let mado = Madoka;
let marry = || (&homu, &mado);
marry();
}
调用时:
let mut _7: &[closure@src/main.rs:10:17: 10:34 homu:&Homura, mado:&Madoka];
let mut _8: ();
_6 = const std::ops::Fn::call(move _7, move _8) -> bb1;
是用 Fn
的方式调用的。
Closure 生成的函数体:
fn main::(_1: &[closure@src/main.rs:10:17: 10:34 homu:&Homura, mado:&Madoka]) -> (&Homura, &Madoka) {
// ...
}
如果closure根本不捕获任何东西,则匿名结构体是Zero Sized Types,在运行时不会被创建。这类closure等价于普通函数,自然也实现了全部三种。代码略。
就算用 move
强制捕获变量的所有权,只要不移走它而仅仅是修改或读取它。这种情况依然会实现 FnMut
或 Fn
。Playground
fn main() {
let homu = Homura;
let mado = Madoka;
let marry = move || {
(&homu, &mado);
};
marry();
}
这种代码,用了 move
所以会捕获 homu
和 mado
的所有权,但是MIR可以看到是通过 Fn::call
调用的:
let mut _5: &[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) -> bb1;
看看closure所生成的函数体吧:
fn main::(_1: &[closure@src/main.rs:10:17: 12:6 homu:Homura, mado:Madoka]) -> () {
let mut _0: (); // return place
let mut _2: (&Homura, &Madoka);
let mut _3: &Homura;
let mut _4: &Madoka;
bb0: {
// ...
_3 = &((*_1).0: Homura);
StorageLive(_4);
_4 = &((*_1).1: Madoka);
(_2.0: &Homura) = move _3;
(_2.1: &Madoka) = move _4;
// ...
return;
}
}
不同于前一个没有加 move
的例子。homu:Homura
和 mado:Madoka
前没有 &
,代表匿名结构体捕获了这两个变量的所有权。
然而捕获了那些变量的匿名结构体本身又是以 _1: &[closure...]
的方式传入的。因为函数体内根本不会移走 homu
或者 mado
。
如果修改这份代码在 closure 过程内修改 mado
的话会变成什么样呢?留作习题。
感谢 Telegram「Rust 众」群网友们对本文的帮助。
std::ops::{Fn, FnMut, FnOnce}
其实所有的普通函数也都是唯一的类型。被视作 Zero Sized Types。 ↩
比如参数和返回值类型都可以省略。 ↩
有一种符合直觉的例外在这里。 ↩
《表演性自谦》这篇文章,发泄情绪多于分析和劝诫。发现问题后大声喊出来并且嘲讽一遍,把别人逼到对立面,却没有细想问题是怎么产生的。
我也是经历过这个过程的,我也趴在地上喊着「巨巨」,我是为了表演吗?部分是。但表演就真是目的吗?
我们表演自己的谦虚,诚然这样能满足一些阴暗的部分,但这不能解释当今遍地都在「卖弱」。我们都不厉害,那些我们眼中厉害的人同样不觉得自己厉害1。我们说自己弱的时候往往不是说谎,但无论怎样严密论证自己有多弱,也不能解释为什么要把它给表现出来。在前文中,我光是宣泄了对这种表现的厌恶,那么成因呢?难道我们一个一个都是「戏精」2吗?
趴在地上仔细再想了想,答案显而易见:表演是手段,表演是为了自我保护。
人的处境以前是以生活的周边来决定的。比如说大锤在三班,班里作文大锤写得最好,大锤自然有资格骄傲。尽管可能他在市里、区里、校里乃至年级的作文比赛都没办法出人头地。可这关系不大,比赛对大锤来说也只是一个「事件」,而不是「生活」,他在学校里日常还是能作为三班里作文第一,时常被语文老师拿来当范文。
而现在,人生活的相当部分是在赛博世界的,对我们来说网络是持续的「生活」。
大锤之后加了一个写作群。在群里有一两位「大佬」,是全国作文比赛奖牌得主,但他们还在仰望着别处。
人们的处境从身边的人陡然扩大到了全国乃至全世界。自己小小的得意、小小的骄傲,在沛然的因特网之下变得可笑起来。即使是不带恶意的客观评价就能扑灭。更何况人与人之间从来就不缺少恶意,小份量的恶意足以令人忌惮。
比起展现自己谦虚,我们又何尝不想为自己自豪,何尝不想被夸奖。但是这不可以的。我们得意起来的时候,心里会浮现一个他人的眼光审视着自己,以前是身边同学的目光,在现在则是五湖四海趣味相投的网友的。我们再也做不到为自己所做的这一点点事情而感到特别得意了。实际面对网友的时候为了自我保护又会把腰压得再低一些。
我觉得「表演性自谦」可恶,因为它不真。但这个世界并不鼓励真。人就是用不真的东西来保护自己的。这个世界就是这样运作的。
你已经做得很好了。别老是用世界性的外部目光审视自己。你觉得骄傲是因为做到了曾经没做到的事情,曾经的你望向未来,不会不想成为此刻的你的(虽然肯定会更贪婪些),这才是心中自豪的来源。
我觉得这篇文章写得很好。嗯。
]]>最前沿的哲学无可辩驳地向上累积着智慧,却离我们越来越远。普通人能想到的一切都被讨论过了,讨论又产生新的问题。这样不断累积下去,一个人如果不是前沿的学者,不管有什么想法,要不是有严重的错误,要不就已经被说出来了。
但麻烦的事情是和别的学科不一样,哲学它不是专家研究后普通人坐享其成的东西。哲学这东西是我们普通人需要去思考的。哲学这东西至少应该有这样的作用:我们普通人能用它来审视自己的生活。
这就产生很大的麻烦了,我不知道怎么解决。不知道你有什么想法。
至少有一点是确定的,我们应该去「民哲」。不要去害怕说出幼稚的观点。想得太多读得太少是问题,但这不是不去表达的理由。日后推翻自己的观念之后回过头对比看看也是挺好的。我初中时候写过一篇文章,被人用夸奖的语气提及,我立刻害羞删掉了,自此对这个话题讳忌莫深。太蠢了。
不管什么东西到了现代就变得奇怪了,你有什么头猪嘛? ↩
假设有这样一句话「政府和人民就像父亲和儿子」从直觉上来说不无道理。可是如果说父亲和儿子的话:母亲是谁?母亲和父亲是合法夫妻吗?领了结婚证吗?做爱做的事情了吗?是顺产还是刨腹产?
巧妙的比喻也能巧妙地曲解。拿个历史上最成功的比喻「洞穴神话」来说吧。如果把影子的比喻和几何联系在一起,别人可以去主张「理型其实存在于四维或更高维空间」,配合一些神神叨叨的话还挺能唬人。
更加实际的例子。物理(弦理论)中有一个推论叫「全息宇宙理论」,说我们的世界可能是以二维方式存在的,而我们感知到的三维宇宙其实是一种不准确的近似。「全息」这个词在这里的作用是「低维重建高维」。这个理论本身是基于计算而来的,传播的时候变了味道:全息摄影有个特点是照片的碎片也包含着全局的信息。把这个套用在「全息宇宙」上,就有一种奇妙的曲解「我们每个人包含着这个宇宙的全部信息」,把前沿假说变成了一种灵修伪科学了。
不管是叙述的人还是听者都能大做文章。不管有意无意,这种错误都会变得很难觉察,叙述者犯了错就是偷换概念,听者理解错就是误解。
比喻不一定是坏东西。清晰准确的论证很好,但是别人理解起来更加费力;又难以传达一些细微的直觉的感受景象。好的比喻就像洞穴神话。它不仅仅好好表达了观点,还表达了一种感受,一种冲动。人看了以后产生想要从墙面上的虚影挣脱出来的意愿。
面对比喻,重点在于本体和喻体的变换。本体是什么?喻体是什么?本体之间的关系是什么?喻体之间的关系是什么?这些关系能对应而不出差错吗?在这个比喻中,哪些关系是有关的,而哪些关系是无关的?最重要的是,喻体之间的关系是不能回馈给本体的。大部分比喻的问题就出在这一步。
「父亲和儿子」的比喻可能关注是精神上的关系、服从、对人格的影响等等,而喻体的那堆生育问题是不相关的。「洞穴神话」关注的是真实和虚假,几何或者光学在这里也是不相关的。「全息」的比喻在于低维重建高维,全息照片本身的部分重建全部在这里还是不相干的。这些不相干的东西是不能回馈给本体的,否则就是偷换概念。
网上看到的比喻很多是有问题的。每当看到一个比喻,看看有没有利用这个比喻从喻体那里回馈了多余的信息。
比喻能让论述更有说服力、直观、还能传递出一些情感,但不能代替论述本身。你总是需要说清楚自己究竟在说什么,否则也怨不得别人搞错。做比喻之前理清楚自己要论证的是什么,在自己的比喻中哪些关系是相关的、哪些是不相关的,然后去比喻吧。
]]>可能因为即时通讯的特质吧,跑到Telegram水群以后,和人起冲突的频率直线上升。经常被人讨厌,不过偶尔也会遇见能给我勇气的人。
起冲突固然是我性格不好、容易TRIGGERED、不合群,有些的确是我的错(或太过火)。不过有一类冲突最让我难以忍受。
即,不去明说到底什么事情做错了,就去盖棺定论,形成一种「你悔改罢」的气氛,迫使人自己去想自己哪里错了。
发觉到这一点的我,在普通的一天里写下了一段话。没想到这段话一次又一次保护了我。
如果我不断反思自己哪里做错了,却还找不出自己错误的地方,也没有人来告诉我「你错在这里」,那么即使我真有错,暂时也尽人事了。
我曾做到过,今后想一直做到,当前也在努力做到:如果我错了,批评有道理,哪怕是痛苦的也必须接受。且不应该对指出问题的其人有意见。
但是,假若想要以一种暧昧不明的气氛让我有负罪感、让我委屈,请容我拒绝。
是让人很不好意思的东西对吧。在生气的时候,我偶尔会把这段话发出来壮壮胆。没想到发多了以后,现在已经不好意思再发了1,再生气也不会发。(下次可以发这篇文章,哇咔咔!)
今年遇到了很多事,其中也包括和别人冲突很多次。能被这段话覆盖的情况时有发生。2列出一部分。
今天发生的(4)非常典型,也是写这一篇文章的原因。
对方反问我「难道你没有点自知之明吗?」之后。我追问是(3)的事情吗?他说「我不是说这件事情」。我继续追问,对方不说话了。
是典型的情况。抛出一句暧昧的指责,除了让别人受到压力和自我怀疑以外,没有任何建设性的意义。
平复了一下超级气的心情后,私下去问他:「到底是哪件事情呢。」对方表示「不是针对哪件事」,只是私下有人觉得我「有些那啥」。
你看,正中靶心。后来对方对我说:
我们遇到的大多数事情也并不是非黑即白的
但是长期以来你就会在别人心目中形成一个固定的印象
以前我也是这样的,有些道理随着时间的增长总会渐渐明白的
私下交流过程中,他看起来没有因为我生气而顺带引燃。这大概说明对方并没有多少恶意。6
然而,我并不在意你们的印象,并不在意你们讨厌不讨厌我。你们的印象,你们的喜好,你们的品味和我无关。我只在意那些非黑即白的事情,而不是那种暧昧的事情。只在意自己做错了什么。只在意那些我喜爱的人,而不是你们。我不是美钞,天然被所有人喜欢。不是为了顺你们心意而活在这个世上的。
这就是我的道路。如果随着时间的增长,渐渐明白道理、渐渐懂事,那我就不再是我了。变成别的什么东西了。
这些人曾经都算比较熟识,有过愉快的时候。现在也不是说全绝交了7。只是这样的事逐渐累积了起来,我想象不了彼此间观念有多么大的差异。只知道自己不会再轻易对网友有所期待了。
尽管个人极度厌恶这种行为,但我也知道,这只是自己的怪癖。不应该对遇到的每一个网友都这样要求的。不应该把自己的期待,自己的怪癖强加于人的。这也是我常常和人冲突的原因。
整篇文章只是在描述我的喜好,正如你们对我表示你们的喜好一样。
我不会说,我的做法是对的,而你们是错误的。
只会觉得失望,
非常地。
你想想和人冲突的时候都像宝贝一样抛出这段话,太二了吧!XDDDD 我知道自己很二啦 ↩
看了下面这些例子,你绝对也会觉得我一定是很难相处的人吧!你的感觉是正确的。 ↩
在我要求后他也提供了具体的事例。 ↩
这件事其实并不是特别符合的。毕竟对方退群损失的是他自己欸,也没说什么多余的话干净利落退群了。当初我在气头上也贴了开头那段话,导致后来很后悔,以后再也不好意思贴了。 ↩
原句是「你也有点自知之明 (」我问是祈使句还是称述句,他回答说是反问句。为了简化直接在文中以反问的形式说出来。 ↩
我之后要求后他给出具体的问题——我很爱删消息。这是无可抵赖、无法反驳的,我改不掉的坏毛病… ↩
绝交了一半吧。剩下一半看到这篇文章以后就说不准了。但真的会看吗? ↩