Hypercube's Channel
57 subscribers
33 photos
2 videos
2 files
15 links
随便搞的一个 channel,还不知道会变成什么样

可以使用 Telegram 的转发功能转发消息
Download Telegram
为了玩好城市天际线,最近对现实中的城市进行了一些观察,比如看海淀区的主要道路宽度和车道数、交叉口类型和占地面积、快速路和主干道的一般间距等,确实很有帮助。刚才忽然意识到我想找的这些知识都属于城市规划的范畴(以及比如住宅区和商业区分别分配多少面积、它们和道路的空间关系等等),于是读了《城市道路交通规划设计规范》(GB 502220-95),发现我想到的和我没想到的很多问题都已经被回答了😂
看到 GitHub 宣布停止维护 Atom 编辑器,找了一下我上次调研编辑器的记录,是 2019 年 11 月,在那之前我主要在用 PyCharm,之后到现在一直在用 VS Code。考虑到最近几年 VS Code 的发展状况,觉得自己做了一个不错的选择😃当时我发的消息:

前一段时间我调研了现在的主流文本编辑器,并且重新评估了我的选择。我最终选中了 vscode,理由是我想要一个比较轻(也就是说不像 pycharm 那样,自带一整套各种语言的基于 language server 的类型提示和自动补全)、不需要创建“项目”就能临时随便编辑文件、易于上手、功能齐全、免费自由(我很难保证我不用喜欢的编辑器偶尔编辑商业项目)的选择。在最终 atom 和 vscode 的挑选中,atom 我花了很多时间捣鼓,连基本的语法高亮插件啥的都不 work,败给了一下子就配好了的 vscode
我惊了,Haskell 中 callCC [1] [2] 的类型居然没有 rank 2 polymorphism,这能用??

具体而言,我认为
callCC :: forall a b. ((a -> ContT r m b) -> ContT r m a) -> ContT r m a
应该改成
callCC :: forall a. ((a -> (forall b. ContT r m b)) -> ContT r m a) -> ContT r m a
或者
callCC :: forall a. ((forall b. a -> ContT r m b) -> ContT r m a) -> ContT r m a
(这两种定义用起来应该是一样的?)

查了一下,只有这里提到了这个问题,搞不懂为什么没人觉得需要改库中的定义。
最近对 Pixel Art 感兴趣,第一幅作品,不知道 USTC 的同学能不能看出来😂
我好喜欢 Haskell 的这两个特性:各种控制结构都是表达式,并且内部是独立的文法块。(其实 JS 也是这样啦)这两个特性使得变量名和变量的生命期管理非常轻松,配合上编译器的静态检查可以避免很多错误。

在一个已经有些复杂的 Python 函数中,我想把一个常数改成如果满足特定条件,就要在运行时打开一个文件并做一些计算来得到。简单的写法是这样:
...
foo = 5
if dynamicFoo:
count = 0
for line in open(...).readlines():
type_, n = line.split()
if type_ == 'foo':
count += int(n)
foo = count
...
注意这段代码产生了很多个新变量:count, line, type_, n。任何一个如果在其他地方已经使用了,就会静默地覆盖掉,导致难以发现的错误,没有工具可以对此发出警告。有人可能会说正解是把这一段代码包装成单独的函数,但这就需要为这个只用一次、完全是为这里专用的函数起一个名字并且放在其他地方,导致阅读这段代码时增加几个步骤。

这段代码在 haskell 中可以写成这样:
...
foo <- if dynamicFoo then do
s <- readFileUtf8 ...
values <- for (T.lines s) $ \line -> do
case words line of
["foo", n] -> return (read (T.unpack n))
_ -> return 0
return (sum values)
else do
return 5
...
除了 foo 以外的变量的作用域都被限制在各自的文法块中,并不会泄漏到外层。如果其他地方已经使用了同名变量,编译器还会对 name shadowing 发出警告。
第二套乐高🥳虽然比上次那个打字机颗粒数更多,但这次分了很多天完成,相对不是那么辛苦。感觉学到了不少设计技巧
今天拿到了海底捞黑海会员😋(虽然感觉也没啥特别的好处)

经理说现在特别难招服务员,人手严重不足,只有五六个正式员工,剩下的全是大学生临时工,工作质量不太行。甚至因为人手不足不得不关闭了一部分桌台,导致高峰期排号时间也变长了。我问是不是工资或者待遇给不到以前那么好了,不然在如今难道不应该更容易招到人吗?她说待遇没变,只是很多人不继续待在北京了,走了不少老员工,也找不到新人。这对我来说是很新的信息,我以前没想过这个问题。如果像服务业、快递、外卖等行业的人大量因为经济萧条而离开大城市,是不是很多大家习惯的生活方式都会有变化,很多服务要么会变得难以获得,要么会贵很多(涨价不会解决问题,因为物价上涨的同时更多人会因为感到经济压力而离开城市)🤔

还被问了有没有能介绍来工作的人😂好像是直接就能开始上班,边做边学。我一直以为前堂看台的工作需要有比较多的培训才能做,毕竟客人的要求很复杂多样,光是帮忙下菜和捞菜没有一些练习的话都不好做。现在看来没什么门槛,什么时候有空闲时间的话我也想试试。
https://0x01.me/colorspace/
(网页可以交互)

最近学了一些 OpenGL,做了一个我一直想做的事情:把 sRGB 的结构在 CIELUV 色彩空间中展示出来。

之前看这个博客 https://ciechanow.ski/ 觉得好羡慕,也想用可交互的 3D 模型更好地学习和讲解一些概念。之后可能会进一步做一些 CIELUV 色彩空间相关的工具。

CIELUV 色彩空间是符合人眼对光的感知的,它有三个主要的性质:

1. 在这个空间中,任意两点的欧几里德距离就表示在人眼看来它们相差多少
2. L 坐标轴(图中纯白和纯黑之间的连线)符合人眼对光的强度的感知,任意一点的 L 坐标表示在人眼看来它有多亮
3. 任意一点偏离 L 坐标轴的程度表示它有多强烈的“色彩”(相对于白灰黑)

常用的 sRGB 色彩空间是没有这三个性质的,例如 #f00 #0f0 #00f 看起来亮度并不相同。#000 #111 #222 #333 也不是等间距的。

这张图中每一小段连线表示 rgb 中的某个分量变化了 17,每个交点表示一个 rgb 分量都是 17 的整数倍的颜色(也就是能用 #ccc #74f 等这种缩写表示的颜色)
以前以为矢量图无非是一堆指定了填充颜色的几何图案,仔细研究了一下 SVG 标准才意识到还要考虑各种连续变化的颜色,例如渐变色、高斯模糊、贴图、光照产生的明暗、随机扰动等。上图就是一个用了这些功能可以做出的图片的例子。

如果文件格式只支持为每个几何图案指定单一颜色的话,这张图就没法被做成真正的矢量图了,只能做很多不同颜色的小正方形,相当于低效地存了一张 PNG。真正的矢量图应该存储某种数学公式,用它可以计算任何一个精确位置的精确颜色。

一种实现可以是,发明一个编程语言,用它编写的函数可以接受某个精确位置作为参数,返回计算出的精确颜色。显示一张矢量图时,需要先按照显示时的尺寸把所有几何图案栅格化,然后对每个物理像素调用一次这个函数,计算出颜色。无论想要什么效果都可以自己写代码,但一个图灵完全的编程语言会带来复杂性。

另一种实现则是只提供一些基本原语,允许用户任意组合,但不允许自定义新的计算逻辑,SVG、CSS 等都走了这条路。但如果用户对原生提供的算法不满意,例如不想要线性插值渐变,而希望按照某个复杂函数插值,就没有办法实现了。上图就是 SVG 文档中的一个例子,它组合了多个原语,制造出了立体的效果。

OpenGL 早期版本也走了后一条路,它提供一大堆可选功能,例如光照、粒子、雾、透视变换等,允许用户按需组合。这套接口如今已经过时了,都被 shader(着色器)程序取代。shader 是用一种专门的编程语言编写的程序,需要什么效果都可以自己写,曾经那些可选功能就不再需要专门接口了。

从这个角度想的话,最通用的矢量图格式应该允许用户写 shader,确切地说,fragment shader(片段着色器)。fragment shader 在栅格化之后对每个像素(也叫片段)执行一次,计算这个像素的颜色。如果能在栅格化之前运行 shader 好像也挺有用的,例如绘制无限重复的分形图案,由于没法预测渲染时的画布大小,无论存储多少个顶点都有可能在放大到足够大之后出现问题,geometry shader(几何着色器)可以在渲染时生成顶点,让图案中最小的细节恰好小于一个物理像素。

(也就是说最通用的矢量图格式是装着一个 canvas 和一堆 WebGL JS 代码的 HTML 文件😂
忽然在想,一个字体要支持多少个中文字符,才能较好地覆盖常见字,比如 1000 个字能覆盖多少?

用我的 Telegram 导出数据做了个统计,只统计所有我发出的,且不是转发的消息中的 Unicode 字符,不限语言,统计所有字符的出现次数。我应该有自己的风格,比如更多用某些字而不用另一些字,不能很好地代表日常上网时会看到的文字,但我觉得这个结果反而会更有趣。

我总共发过接近 4 百万个 Unicode 字符,但只有 4213 个不同的。这里面最常出现的 1000 个字符覆盖了 97%,2000 个字符覆盖了 99.7%,3000 个字符覆盖了 99.95%,详细数据见附图。

利用这些数据,我也得到了一个有点像 https://xkcd.com/simplewriter/ 的程序,输入一段话,它就会告诉我里面我最少使用的字是哪些,分别排在多少位。例如这条消息的结果:

'详' 是第 1195 常出现的字,在 98.133% 处
'覆' 是第 1152 常出现的字,在 97.931% 处
'附' 是第 1038 常出现的字,在 97.264% 处
'盖' 是第 1007 常出现的字,在 97.050% 处
'万' 是第 841 常出现的字,在 95.537% 处
'忽' 是第 813 常出现的字,在 95.201% 处
我一直想搞明白该如何制作高质量的 GUI 组件,例如看似简单的多行编辑框。无论是 web 还是原生,绝大多数组件库都是在给已有组件套壳,并没有真的自己实现编辑逻辑。这是好事,因为 GUI 组件并不容易做好,我也看过不少文章呼吁大家不要自己重写,因为重写的版本会缺功能,熟练用户会感到难用。

我能想到的多行编辑框该有的一些功能:
- 正确渲染里面的文字,正确断行,知道字之间分界线的位置。
- 用键盘输入、退格、移动光标、按 Shift 选中、复制粘贴。
- 用鼠标移动光标、选中、双击、三击。
- 支持输入法,输入法窗口位置正确。
- 右键菜单中有复制、粘贴等功能。
- Linux 上 primary clipboard 的中键粘贴功能,和相应的选中复制功能。
- 其他操作系统特有的功能,例如安卓上长按选中并在两端出现方便拖动的指针,点击改变光标位置并出现指针,点击这个指针出现粘贴按钮。

然而并不是所有地方都有直接可以拿来用的现成组件,比如游戏开发。很多简单的游戏框架(Pygame,SDL 等)是基于画布的,并不提供原生组件。很多场景下原生组件并不适用,而开发者不一定愿意通过嵌入一个浏览器来解决问题(更别说浏览器里组件可定制性也非常有限,想控制文字渲染过程中的一些细节的话几乎只能自己写文字渲染的代码)。

比起呼吁大家“不要自己重写 GUI 组件”,更好的做法或许是列举出现代的、高质量的 GUI 组件所应有的功能?我发现很难找到这样的列表。当然我可以通过阅读 GTK 或类似物的代码,手工整理出代码中实现的所有细节(我简单读了读就发现了一些以前不知道的功能!),但这很麻烦,而且会使我陷入它的思维模式中,不容易重新思考什么架构可以更好地实现这些需求。

只要整理出需求列表,“自己重写 GUI 组件”可能也没有那么糟。虽然我找到的几个自称“功能完整”的基于画布的编辑框都破破烂烂,能在 30 秒内发现不少问题,但这件事也不是没人做好过——Google Docs 在 2021 年从基于 DOM 的渲染方式换成了基于画布的,文字、选中区域、光标等全是自己画的,使用体验还挺好,大多数常见功能都支持了。VS Code 虽然是用 DOM 渲染的,但处理输入、按键、鼠标的逻辑似乎都是自己写的,我感觉功能丰富程度来说比 Google Docs 做得还好,从来没遇到过我认为多行编辑框应该支持的功能在 VS Code 中不工作的情况。他们是按什么列表做的呢?
我才意识到 HTML 中的 click 事件比“鼠标点击”复杂:
- button 元素的 click 事件会在按回车或空格时触发,不一定需要鼠标点击。有类似性质的元素还有选择框等。
- 如果自己用 div 元素画一个按钮,click 事件默认不会在按回车或空格时触发(实际上这个元素可能都无法被聚焦),这可能会产生可访问性问题。
- 在一个普通的 div 元素上绑 click 事件处理函数,也不能保证被调用时一定发生了鼠标点击。如果 div 里面有一个 button 子元素,它被按回车时,div 的 click 事件处理函数也会被调用。

感觉有点混乱,要是能把所有事件明确分成两类就好了:一类是原始事件,比如鼠标或者键盘动作。另一类是每个元素处理原始事件后产生的高层事件,比如 button 用鼠标和键盘都可以产生“按钮被按下”事件,input 用输入文字、粘贴、甚至鼠标拖动都可以产生“内容被改变”事件。开发者一般会想监听后一种事件,但需要自己实现一些复杂功能时可以选择前一种事件。
每次买笔记本电脑时都很担心 Linux 兼容性问题,不知道如果格盘重装 Linux 后发现不工作还能不能退。但其实我也从来没有遇到过装 Linux 后基本功能有问题的电脑,触摸板、键盘、声音、WiFi 等都是好的,我以为如今应该不太会遇到这些问题了。

昨天买了个联想 YOGA Pro 14s,看中的是 32G 内存 + 触控屏(这两个条件下选择不多)。买之前就担心有点危险,因为查到有人说相似的型号存在键盘、触摸板默认不工作的情况,但毕竟是其他型号,没有查到明确说这一款有问题的,觉得可以冒险一下。刚刚收到一试,好家伙,键盘不工作,Debian Installer 都操作不了😂加内核参数 i8042.dumbkbd 后键盘能工作,但(在 live 中测试发现)触摸板不工作,屏幕分辨率不正确,触控屏不工作,键盘无法调整屏幕亮度。赶紧收拾收拾准备退货.jpg

至于试 Linux 后怎么退货的问题,我现在觉得可能没有我以前想象得那么可怕。一是我意识到要试各种基本功能的话在 live 里面试就可以了,不用格盘。二是我查到确实有人说只要不激活 Windows,哪怕格盘装了 Linux 也是可以退的,甚至即使激活了 Windows 似乎也有扣几百块钱(Windows 价格)能退货的例子。
inotify 的 watch 数量是按每个用户限制的,完全有可能一个程序 watch 了太多文件,导致另一个之后启动的程序报错。在这种情况下,检查报错的程序,试图减少它 watch 的文件数量,是没有帮助的。这有点像如果一个程序报错说磁盘已满,不一定应该减少它的磁盘占用量,而应该找出目前占用最多磁盘的程序或目录。

可惜并没有一种现成的方法检查当前已经用了多少 watch,或者哪个程序用得最多,这导致我看到 inotify watch 失败的报错时完全不知道该从哪里入手解决。经过一些调研,我发现这个程序工作得不错:
https://github.com/fatso83/dotfiles/blob/master/utils/scripts/inotify-consumers

当然了,也推荐增加 watch 数量限额,这并没有太大坏处。
浏览器里的 canvas 好难用。说两个听起来很简单但实际上很难实现对的需求:

1. 让 canvas 的像素和物理像素一一对应,来确保绘制出的图像是像素级完美的。很多人知道 devicePixelRatio 的概念,以及 1px 并不真的是一个物理像素,但不少网站错误地假设 devicePixelRatio 一定是整数。还有一个概念叫 visualViewport.scale。1px 其实对应 devicePixelRatio * visualViewport.scale 个物理像素。

2. 在 1 的基础上,确定鼠标所在的精确物理像素,以便在鼠标所在处绘制内容。鼠标相关事件中给出的几种坐标都是以 px 为单位的整数,由于 1px 不是一个物理像素,鼠标肉眼可见地移动了几个像素时,JS 获取到的坐标经常并没有变化,相关信息在取整后丢失了。screenX 和 screenY 是以物理像素为单位的,不会丢失信息,但并不容易把它换算到 canvas 坐标系,我还没发现任何可靠的方法获取 canvas 在 screen 上的物理像素坐标。
GitHub 发布了 fine-grained personal access tokens。以前这个问题很难解决:你和别人在一台服务器上合作,你们希望这台机器有权限读写某几个 GitHub 私有仓库。
1. 某个人在这台机器上创建一个 SSH key 并添加到自己的 GitHub 账号,坏处是合作者可以访问他的所有私有仓库了。
2. 某个人创建一个 personal access token 并放在这台机器上,坏处同上,因为 personal access token 不能按 repo 设置权限,只能允许读写所有私有仓库。
3. 为每个仓库创建一个 deploy key,坏处是要管理很多个不同的 deploy key,执行各种 git 命令时都要选择正确的那个 key,很不方便。(GitHub 不允许任何 deploy key 相同)

这个新功能就是给 2 加上了细粒度权限设置,一个 personal access token 最多可以拥有 50 个仓库的权限。可惜不能为每个仓库分别设置不同的权限,目前只支持指定一堆仓库,再指定对所有这些仓库都拥有什么权限。

https://github.blog/2022-10-18-introducing-fine-grained-personal-access-tokens-for-github/
一般的 Linux 包管理器都做不到这两件事:
1. 一个软件包的多个版本共存。当两个软件包都依赖第三个软件包时,只能选一个版本装,不能装两个版本分别给两个软件包用。
2. 非 root 用户方便地安装喜欢的软件版本和所有依赖。如果系统装了 vim 8.2,非 root 用户想用 vim 9.0 就只能自己找来所有依赖,编译安装,没有包管理器这样方便的方案。

为什么一个软件在整个系统中只能装一个版本呢?存储不同版本的软件以及依赖,让它们各用各的依赖,听起来并没有本质上的困难。我最近研究了一番 Nix,它看似可以解决这种需求,但实际上它使我意识到了这里实际的困难。

各种软件里面都有写死的其他软件的名字或路径(一般不带精确版本),Nix 打包时改写了这些内容,为它们加上了精确版本。这样一来,两个软件可以分别调用自己的依赖,互不干扰,这些依赖也都不会暴露给最终用户。最终用户如果也想用这个依赖,要自己指定想用哪个版本(可以是和这两个软件用的都不同的第三个版本)。

但是 Nix 并没有改掉一切,例如 git commit 会自动调用 vi 来编辑提交信息,git diff 会自动调用 less 来分页,Nix 并没有将 vi 或 less 视为 git 的依赖并锁定版本。我想这是比较合理的,因为用户会希望用自己喜欢的 vi/vim 或 less 版本,而不是 git 这个软件打包时锁定的某个版本。虽然 git 提供了配置选项来自定义使用的命令,但默认值也是很重要的。如果每个软件都必须自定义一番才能用上自己在全局安装的 vim 或 less,体验应该会很差。

还有一个小问题是家目录中的配置文件和数据,如果系统中装了多个版本的 vim,却共用一个家目录下面的 .vimrc 和 .viminfo,很可能会出现问题。如果试图让它们用不同的家目录,恐怕不仅不会解决问题,反而可能会更混乱。

所以这里存在本质上的矛盾:所有软件的所有依赖都被锁定和相互隔离的话,好处是省心,依赖都是开发者预期的版本,自己写的程序随便起名也不会被意外调用,坏处是用不上自己喜欢和熟悉的版本,家目录中的数据文件可能不支持不同版本的程序读写,以 daemon 形式存在的服务可能无法工作(因为 client 和 server 版本不一致,例如 git clone 时 git 自己的 ssh 版本可能无法连上 control master)。更高层的应用程序可能还好,对于底层的各种基础工具(git vim curl less ssh 等),看来想实现不同版本共存确实有难度。