Ian Henry

RSS: https://ianthehenry.com/feed.xml
Ian Henry 的博客,关于编程语言、编译器与函数式编程的技术写作。

键盘乐器创新:Keyboardion 的不对称调音设计

<p>我有一个乐器的构想,我觉得效果还不错。</p><p>它有点像以及一个.或者是数字版…只是它更偏复音,调音也完全不同。</p><p>我不知道。我觉得这算是个新现象。</p><p>半音阶按钮手风琴可能是最古老且最受欢迎的世界上的乐器,所以它不是<em>那</em>这其实是件新鲜事。但据我所知,键盘是第一个调整调音以适应这种特定形态的乐器。<br><br></p><p>我知道的所有数字同构键盘都有更方形的宽高比,而且它们通常设计成像钢琴或手风琴一样演奏:双手对称地与键盘接触。</p><p>但我真的很好奇这种不对称的感觉:左手大概是在低音区演奏,所以可以用和弦伴奏旋律。左手在吉他上弹和弦时表现得相当不错。也许它能用在这奇怪的键盘吉他上?<br><br>让我们来看看。</p><h2>好吧,那这是什么</h2><p>音符布局如下:</p><p>我只高亮了一个完整的八度。为什么我要高亮一个从C♯开始的八度?我不知道;这正是我画这张照片的地方。你可以向左或向右滑动;你可以将C调置在顶行,而不改变相对布局。</p><p>重要的是音程:向下和向右移动会上升半音。向上和向右移动会提升一个整音。直接向右移动时,音程会上升一个小三度。</p><p>这和“B系统”手风琴相符,网上说这在俄罗斯很常见。书中还说大多数西方半音阶按钮手风琴采用“C系统”布局,其实是倒过来的:</p><p>不知道这是不是真的。我两种都试过,发现B系统上的音程对左手常见和弦形状更舒适。比如,这里......</p>
评论点赞收藏65 天前

为什么要学 Janet?

Ian Henry 写了一整本免费的 Janet 编程语言电子书,然后还要写这篇文章说服你入坑。这不是枯燥技术文档,而是有温度的个人安利:Janet 核心只有 8 条指令,一个下午就能上手;编译后原生二进制仅 784KB,无需安装运行时就能直接分发;用解析表达式文法(PEG)替代正则表达式,能解析 HTML、JSON、甚至二进制格式;还有优雅的子进程 DSL,可以替代 Bash 完成大量脚本任务。 作者逐一拆解了宏系统、编译期到运行时的值传递机制、可变与不可变集合等特色,并坦言「Janet 握在手里手感很好」—— 主观但真诚。 有代码示例,有踩坑记录,有和其他语言的对比判断,不是 ChatGPT 拼贴也不是 SEO 搬运。适合对编程语言设计、脚本工具链、Lisp 方言感兴趣的读者,读完很可能会想打开 REPL 试试。
评论点赞收藏120 天前

德劳内三角剖分可视化(2022)

一篇罕见的兼具深度与人味的技术文章。作者用交互式动画一步步拆解德劳内三角剖分——从"最大化最小角"的直观定义,到抛物面映射解释复杂的圆内点测试矩阵行列式,再到增量算法的完整实现与边界情况处理。 更难得的是,整篇文章充满了个人经验、踩坑记录和诙谐叙事(作者做这个的动机竟是为了做一个"可以砸碎的订阅按钮")。没有学院派的枯燥,没有营销号的浮夸,只有认真写代码的人才会有的那种"我终于搞懂了,现在手把手告诉你"的真诚。 适合任何想真正理解这个算法而非仅停留在概念层面的读者。
评论点赞收藏253 天前

周期空间:用周期函数重做 SDF 域重复

<p>我最喜欢的SDF技术之一,就是“域重复”:</p><p>这是一项极佳的派对小技巧,能够让你实时渲染无限多的几何形状,并且还能呈现出柔和的阴影、 环境遮挡,以及SDF所具备的种种精美效果。 当我第一次看到这项技术时,简直觉得像魔法一样——而如今,它依然让我感到神奇——只不过是以一种截然不同的方式。</p><p>之所以能做到这一点,关键在于:你并不是在“评估无限多的几何形状”。每当一束光线穿过场景时,它只会一次只评估一个形状。</p><p>让我们先来看一下这个场景的二维切片:</p><p>说实话,这真的让我有点吓到。不过,我们不妨先看一个更简单的二维场景,相信我,这个原理同样可以推广到更复杂的几何形状上:</p><p>假设那是一个三维场景的二维切片。想象一下,我们其实正试图渲染这样一个场景:</p><p>要渲染一个三维场景,我们需要从相机出发,以不同的角度发射光线,直到光线击中某个物体。不过,在二维空间中,我们更容易直观地理解这一过程:</p><p>然而,在渲染SDF时,我们并不会像这样持续不断地发射光线——光线并不会平滑地向前推进, 直到击中某个形状。由于我们已经知道每个点处的距离场信息,因此我们可以利用这些信息来决定自己该向前推进多远。 真正的光线漫游看起来更像是这样:</p><p>……只不过,你知道的,速度更快。 那个圆的半径,正是光线行进过程中每一点处的距离场值。距离值表示的是“与最近的形状之间的距离”,而在光线漫游的过程中,你可以将它理解为:“在碰到任何方向的障碍物之前,我能移动多远?…</p>
评论点赞收藏304 天前

泛化沃利噪声

<p>沃利噪声是一种在计算机图形学中用于程序化纹理的噪声类型。在其最基础的形式下,它看起来是这样的:</p><p>虽然效果难看又枯燥,但这种方式却能快速直观地展示出该效果的最终呈现效果。如果我们利用沃利噪声来扭曲一个三维形状,就能得到类似锤纹或坑洞状的纹理:</p><p>与许多程序化纹理一样,如果将这一效果重复多次、并采用不同的频率进行渲染,其视觉效果会更加出色:</p><p>这些渲染结果中存在一些视觉伪影,因为它们使用的是对沃利噪声的快速近似算法,在某些数值上会产生错误的结果。</p><p>虽然这种效果并不十分令人满意,但要弄清这些伪影的来源,我们首先需要了解沃利噪声的工作原理。</p><p>它的实现其实非常简单:我们从一个点阵开始。</p><p>然后,我们将每个点按照一定的随机偏移量进行移动:</p><p>在编写着色器时,我们无法真正生成随机数,因此我们借助哈希函数,根据每个点的逻辑位置(即, 中心点的坐标为 $i = [0 0],右侧的点的坐标为 $i = [1 0] 等)来生成看似随机的偏移量。</p><p>最后,当所有点都位于看似随机的位置后,我们就可以计算输入图像中每一个像素到最近点的距离——而这正是沃利噪声的计算过程。</p><p>那么,对于任意一个像素,我们该如何计算到最近点的距离呢? 其实很简单:我们知道,我们最初所构建的是一张完全均匀的方格网格。 对于任何一个像素,我们都可以计算出该像素所属的“网格单元”([0 0]、[0 1] 等)。只需将该像素的坐标除以网格的尺寸,再取最接近的整数即可。…</p>
评论点赞收藏308 天前

用Datalog来调酒:一个家庭调酒师的逻辑编程实验

一篇极其罕见的「个人项目 + 技术叙事 + 生活实用」三位一体好文。作者 Ian Henry 在家调酒时遇到真问题:冰箱里一堆零散基酒和利口酒,面对海量调酒配方,该买什么才能用最少花费解锁最多新鸡尾酒? 他没有写两行 Python 交差,而是借机学了 Datalog(一种逻辑编程语言),写了一个叫 Mixologician 的小程序。文章最可贵的地方在于:他完整呈现了从「想当然地写规则」到「被 Soufflé 编译器报错、反复试错、最终写出优雅递归关系」的全过程。 他坦诚自己花了几个小时才搞定一条规则,把 Lime → Lime Juice → Lime Cordial 这类食材衍生链用 Begets 和 Composite 关系优雅建模。 整篇文章语言幽默、有真人对话感(自问自答段落贯穿全文),代码全公开在 GitHub。不是教程,胜似教程;不是故事,却处处是故事。 对于对逻辑编程、领域 DSL、工具思维或调酒感兴趣的读者,这篇文章既有启发又有实操价值。
评论点赞收藏363 天前

构建 Bauble:一个用数学函数雕琢3D图形的个人编程项目

一位资深程序员以第一人称讲述从零构建 Bauble——一个基于 SDF(有符号距离函数)的浏览器端 3D 创作工具——的完整故事。核心看点不是工具本身,而是个人项目从"兴奋原型"到"代码崩塌"再到"两年后彻底重写"的真实心路。 作者用 Janet 语言嵌入 WebAssembly,设计了一套函数式 DSL 来自动生成 GLSL 着色器,早期靠字符串拼接实现,最终因无法维护而全部推倒重来;两年后用工程化方法构建了 GLSL AST、类型系统、测试套件和完备的交互式文档。 全文充满具体的技术踩坑(矩阵列主序、WebGL 跨浏览器差异、Marching Cubes 精度问题)、真实困惑(为什么第一次不写测试)、审美判断(颜色校正、交互体验)和个人表达,是一篇兼具编程硬核深度与人性温度的个人技术叙事,能激发读者讨论欲和动手尝试的冲动。
评论点赞收藏628 天前

Janet 凡人指南(2023)

一位独立开发者用 20 周业余时间写了本免费的编程书《Janet for Mortals》,并坦诚这篇博文就是借机推广的"垫场文"。但他详细记录了写书之外的大量周边工程:为书籍网站内置交互式 REPL(支持按 Escape 调出、自动补全、一键运行),给 Janet 语言写命令行参数解析库 cmd(受 OCaml Core.Command 启发),重写快照测试框架 Judge,构建交互式海龟画图 playground ——这些 side project 占去了写书一半的时间。 文章不回避真实数据:HN 首页一日带来 3 万访客,真正读完 5 个章节以上的仅 387 人;内置 REPL 的 report 功能收到近 500 条反馈。 全文坦诚自嘲,充满一手工程判断和取舍思考——比如为什么不用 Janet 写后端(安全性不足)、为什么为 logo 花一天建模乌龟(伴侣说没有乌龟的海龟画图是"辜负")。 适合对技术写作、独立开发、编程语言生态感兴趣的读者。
评论点赞收藏672 天前

如何学习 Nix

一篇带有强烈个人风格的 Nix 学习指南索引页——作者 Ian Henry 以自己的真实学习历程为主线,从零开始构建了一份涵盖 Nix 包管理、表达式语言、derivation、交叉编译、flakes 等主题的系列教程。 章节标题充满自嘲和幽默("好吧,这才是我的第一个 derivation"、"更多的 flakes,不幸的是"、"我读了手册,嗯"),记录了大量一手踩坑经验:从 Homebrew 迁移到 Nix、第一次写 bug、第一次升级包、配置声明式用户环境等。 这不是官方文档的复述,而是一个有真实困惑、有失败经历、有动手过程的学习者笔记。虽然页面本身是索引而非正文,但整体结构完整、覆盖面广,且持续维护到 2024 年,对想认真学 Nix 的开发者而言是一个信息密度高、有温度的入口。
评论点赞收藏734 天前

一种与众不同的键盘:Peggi琶音键盘制作手记

一位独立开发者用Faunchpad自制的八键琶音键盘Peggi——贴在手机背面,通过手指顺序滚动按键输入文字,而非同时按压和弦。作者详细分享了设计理念、键位手工布局(把最常用字母放在最顺手的滚动方向上)、与和弦键盘的优劣对比(和弦只需同时按,琶音需顺序按但容错性更好),以及人体工学上的真实翻车(竖屏根本无法使用,横屏拇指操作也很别扭)。 最精彩的是前面大谈理论预期可达50wpm,最后实测坦诚"能到10wpm就谢天谢地了"。文风幽默自嘲,信息密度高,有完整踩坑记录和开源固件链接,对替代输入设备或键盘DIY感兴趣的读者非常有启发。
评论点赞收藏777 天前

深入理解宏中的 quote-unquote:编译时值传递与运行时引用

作者以个人学习历程中的"顿悟"为线索,揭示了 Lisp(Janet)中 quote 的真实语义——它并不是"返回参数的抽象语法树",而是"直接透传解析器生成的值"。 这意味着你可以在宏展开时分配可变对象(如哈希表),并通过 quote-unquote 模式将同一对象的引用嵌入运行时代码的每一处调用点,实现普通函数做不到的效果:每个宏调用点拥有独占的缓存状态。 文章从 Python 装饰器/JavaScript 高阶函数讲起,对比纯语法变换宏的局限,逐步深入 Janet 的具体实现,并展示了这一技巧在 Bauble 编译器重构和 Judge 内联快照测试框架中的实际应用。 适合对元编程、Lisp 系语言或编译器实现感兴趣的读者,有清晰代码示例和扎实的底层理解。
评论点赞收藏779 天前

制作并定制一把Kyria无线人体工学键盘

一位从未焊过电路板的开发者,亲手打造了一把完全无线、且能用相机三脚架配件自由调节角度和倾斜度的人体工学键盘。文章详细记录了Kyria套件的组装全过程,包括蓝牙左右分体方案、底壳1/4-20螺纹孔配合云台实现任意"帐篷角"、强磁吸附金属桌面等巧妙设计。 作者坦诚分享了初次焊接的踩坑经验与省力技巧,并强调"通孔焊接其实很简单"来打消新手顾虑。这不是一篇泛泛的DIY教程,而是一份充满实物照片和真实手感反馈的建造日记,对想入门客制化键盘或寻找办公舒适方案的人有直接参考价值。
评论点赞收藏793 天前

函数命名空间与 Lisp 宏的问题

一位自称非 Lisp 专家的程序员,在写游戏时被迫写测试框架、从而被迫深入理解宏,结果意外颠覆了对 Lisp 宏的全部认知。他原以为宏只是"把代码转换成代码"的语法变换,但在 Janet 语言中写 do-texture 宏时发现,宏展开时函数名是在调用处而非定义处查找,导致命名空间污染——而解决方案竟然简单到直接在宏里 unquote 函数对象本身。 这让他意识到宏不止是语法转换,还涉及编译时值捕获这一至今未被主流教科书充分讨论的机制。他翻出《On Lisp》和《Let Over Lambda》,发现 Paul Graham 对函数捕获的建议只是"放个独立包名",Doug Hoyte 则把 Lisp-2 的独立函数命名空间当成宏构造的护身符。 作者质疑:Lisp-2 从来不是解决宏作用域问题的设计,它只是早期硬件的性能 hack,让社区长期回避了真正的问题。文章充满个人探索的鲜活感——不装懂、不背书,边写代码边推翻自己的偏见,是那种能让人读完就想打开 REPL 试一试的技术写作。
评论点赞收藏828 天前

从零开始配置 tmux

从清空所有默认按键绑定开始教你配置 tmux——作者 Ian Henry 分享了一套"从零开始"的 tmux 配置哲学:先 unbind-key -a 解除所有默认绑定,再按需添加自己真正需要的功能。 文章覆盖了自定义 prefix 键、配置复制模式(含 vi 风格操作、系统剪贴板集成)、鼠标支持等完整流程,并解释了每个设置背后的真实原因和踩坑经验,比如 escape-time 设为 1 而不是 0 的来由、set-clipboard 的终端兼容性问题、copy-pipe 跨 macOS/Linux 的差异处理等。 这不是文档搬运,而是一位长期用户经过思考后的配置实践,充满个人观点("C-b 是键盘上最难按到的键之一")和实用细节。适合想真正理解 tmux 配置原理而非照抄他人配置的读者。
评论点赞收藏858 天前

学习 Nix 第 49 篇:nix-direnv 大幅提升开发体验

nix-direnv 是 Nix 生态中被低估的工具。作者两年实战总结:nix-shell 两大痛点——每次 GC 要重下依赖、强制 bash 交互 shell。自己写 wrapper 解决前者费尽周折,后者一直没搞好。 nix-direnv 同时解决:不启动新 shell,在当前 shell 注入环境变量;自动注册 GC root;仅 shell.nix 变化时重评估,典型场景零启动延迟(原生 nix-shell 400ms,作者包装要 750ms)。 shell.nix 改动后环境自动更新,无需反复开关 shell。附完整配置步骤和 zsh 隐藏冗余输出的 hack。有真实踩坑、有具体数据、有可用方案,适合所有受 nix-shell 折磨的 Nix 用户。
评论点赞收藏976 天前

学习 Nix 第 48 篇:在 macOS 上安装(单用户模式)Nix

一篇极具个人风格的真实技术踩坑记录。作者以"斯德哥尔摩综合征"自嘲与 Nix 的 18 个月纠缠:觉得它毛病一堆但比 Homebrew 强,咬牙坚持。 核心问题:macOS 上 Nix 官方已移除单用户安装选项,强制多用户模式(需跑 root 守护进程、建一堆构建用户),且每次 macOS 升级都会崩。 作者受不了多用户安装,自己黑了一套非官方单用户方案,跑了一年半毫无问题。文章详细解释了两者区别、多用户安装的弊端(升级必崩、改系统配置)、以及如何绕过限制完成单用户安装。 文末反思多用户也有沙箱优势,但认为不值得增加复杂度。强烈的个人立场、真实困惑和动手记录,不是通稿也不是教程复读机。
评论点赞收藏978 天前

斐波那契矩阵

一篇以强烈个人风格重新审视斐波那契数列的深度博客。作者从"矩阵即状态机"的视角切入,用线性代数的特征分解(eigendecomposition)工具,展示如何用矩阵乘法和特征向量在 log(n) 甚至"常数时间"内计算斐波那契数——然后诚实指出浮点误差使这一方法在实践中不可行。 全文贯穿个人观察(为何讨厌用递归教编程)、互动可视化、对黄金比例的直觉解释,以及脚注里的冷知识(如斐波那契数可用于英里/公里换算)。 不是教科书翻版,而是一个人带着好奇和幽默分享他真正觉得酷的东西。
评论点赞收藏1158 天前

我心目中的 REPL

一位程序员分享他过去六年几乎唯一的测试方法——让源文件变成持久化 REPL:你在编辑器里写表达式,按快捷键,运行结果直接回写到文件里,这些记录下来的会话自动成为活着的测试用例。 这种"读取-求值-修补"循环(read-eval-patch loop)彻底改变了他的编程工作流。核心洞见:当写测试的摩擦足够低时,你自然会写得更多;当测试立刻就有用(而不仅是留给未来的自己),你自然会写出更好的代码。 作者在 Janet、OCaml、Rust 中实际使用,JavaScript 的 Jest 也支持类似的内联快照测试。这不是哲学说教,而是一种可复用的机械式工作流改进。
评论点赞收藏1183 天前

广义宏:一种可以重写程序任意形式的宏系统

一位独立程序员在Janet语言中实验了一种全新宏系统构想:让宏不仅能改写自身,还能改写整个程序中的任意语法形式。这种"广义宏"可以出现在表达式任何位置、能感知左右两侧代码、并能返回匿名一级宏实现递归展开。 从更简洁的defer/def语法到中缀运算符、泛型函数应用到if/else改写,文章展示了多个有实用潜力的用例,同时坦率讨论了REPL兼容性问题、宏组合失效和可读性灾难三大现实缺陷。 附有可运行的原型实现代码。文笔幽默,有鲜明个人立场和一手实践经验,是真正的设计探索与思想实验,而非泛泛的趋势介绍。
评论点赞收藏1261 天前

登录芦苇

登录后关注作者、收藏内容和参与讨论。