[{"content":"上篇文章我们谈了高阶函数在函数式语言中的妙用，文末留了一个引子，点出了「柯里化」和「部分应用」两个概念。如果你还没有看过，我非常建议你先看一下 上一篇文章。\n这篇文章，我将深入解释这两个概念的细节，并说明两个概念在函数式语言中的价值。\n1. 什么是柯里化 我们上篇文章最后给出了一个相当简略的 Haskell 解法，就是 map (*2) [1, 2, 3, 4, 5]。这个解法看起来很直观，可是对于不熟悉函数式写法的朋友，细想一下可能还是会觉得这里的 *2 写法颇有些怪异：从类型的角度，我们知道这里应该是一个函数；可是这个「乘二」为什么可以是一个函数呢？\n想了解为何这种写法成立，必须先解释什么是柯里化。简单来讲，柯里化就是把一个多参数函数，转化为了多个串联起来的单一参数函数。\n我们写一个最简单的加法函数举个例子：\nadd :: Int -\u0026gt; Int -\u0026gt; Int add x y = x + y 在传统非柯里化形式的函数定义中，其类型应包括一个含有两个整数的元组，返回一个整数。也就是(Int, Int) -\u0026gt; Int。如果用非柯里化形式写这个函数，应当写为：add' (x, y) = x + y。这是在其他语言中常见的多参数函数写法。\n而在我们的例子中，这个函数实际的函数签名是：Int -\u0026gt; Int -\u0026gt; Int。这其实是经过柯里化处理后的标准函数类型。由于 Haskell 的函数签名是右结合的，柯里化后的函数签名，完整写法是是Int -\u0026gt; (Int -\u0026gt; Int)。\n也就是说，原先是两个参数一起输入，柯里化后转变为先输入一个参数，返回一个单一参数的函数；第二个参数输入给这个单一参数函数，最终得到计算结果。\n在 Haskell 中，柯里化后的类型形式尽管发生了变化，但是行为和原先是一致的。换言之，现在的add x y = x + y和非柯里化形式add' (x, y) = x + y行为是一致的，都是计算两数之和。\n那你可能要问了，既然柯里化后和非柯里化的函数行为完全一致，为什么我们需要这样一个东西呢？\n这就要提到我们的下一个概念：柯里化解锁的全新函数使用方式──「部分应用」。\n2. 柯里化带来的部分应用 仔细阅读上文对柯里化的说明，我们会发现一个关键点：没有人规定函数必须一次提供所有参数。\n也就是说，我们完全可以只提供一部分参数，使用返回的中间函数。而这，就是部分应用的概念。\n仍然拿上文最基本的add函数举例：\nadd :: Int -\u0026gt; Int -\u0026gt; Int add x y = x + y add3 :: Int -\u0026gt; Int add3 = add 3 add3 10 -- 13 add3 20 -- 23 add10 :: Int -\u0026gt; Int add10 = add 10 add10 10 -- 20 add10 20 -- 30 在这个例子中，add3和add10两个函数就是add函数部分应用的结果。我们可以发现，对于只传入部分参数的情况，add函数返回值的类型的确是一个函数，符合我们的预期。\n对于 Haskell 中的多参数函数，我们完全可以一次性只提供部分参数，得到的新函数可以等待其他参数需要传入的时候再做最后计算。\n像这样通过部分应用得到的新函数，我们想要多少就可以定义多少。\n3. 柯里化和部分应用的价值 这两个概念的核心价值，在于函数式语言对于代码逻辑的一种特殊的复用方式。我们上面举的例子比较简单，但对于更复杂的函数，使用部分应用的形式可以大大简化代码重复的现象。\n在 Haskell 中，所有多参数函数都可以被理解为一种高阶函数，因为它们都可以通过部分应用的范式返回另一个函数。\n用一个形象的比喻，就是多参数函数可以被理解成一个「函数工厂」，我们通过传入部分参数生成各式各样的新函数，这些函数被应用在了需要的地方。这极大增加的代码的抽象程度和使用的灵活性。\n回到我们开头的例子，我想现在大家就理解map (*2) [1, 2, 3, 4, 5]为什么是一种合法且推荐的写法了：\n*是一个双参数函数，这里我们只传入了一个参数2，得到了一个新的单一参数函数；而map正好需要第一个传入的参数是单一参数函数，我们通过函数的组合，最终得到了正确的结果。\n这里，我们看到了柯里化和部分应用的另一个实用价值：通过灵活控制参数传入的时机，我们可以更自由地组合各个函数，用灵活的方式完成需求。\n下一篇文章，我打算谈一谈函数式流程控制的问题，说明代数数据类型(ADT)和模式匹配这一对完美的组合。\n如果你对更多函数式语言特性感兴趣，欢迎关注我！\n","date":"2025-08-22T00:00:00Z","permalink":"/post/250822-currying-partial-application/","title":"函数式心法 (3)：巧用柯里化和部分应用"},{"content":"上篇文章我们从全局宏观的角度，了解了 Haskell 语言的特性和学习方法，这篇文章我们来谈一个具体的函数式语言特性：高阶函数。\n我选择这个问题，一个重要的原因是许多其他范式的语言纷纷在新版本、新标准中引入了高阶函数特性。在更纯粹的函数式语境下理解这个概念设计的必要性，我觉得对于我们更好使用高阶函数是有帮助的。\n换言之，哪怕你以后再也不碰 Haskell，了解一下高阶函数绝对不亏。\n1. 为什么需要高阶函数？ 我们先来看一个简单的例子：假设我们有一个由整数构成的列表，现在想要得到一个新列表，新列表的每个元素是原列表的两倍。\n如果使用命令式的思路来解决这个问题，常见的做法是使用 for 循环。我们来看一个 Python 的示例代码：\ndef double_list(num_list: list[int]) -\u0026gt; list[int]: result = [] for i in num_list: result.append(i * 2) return result print(double_list([1, 2, 3, 4, 5])) # [2, 4, 6, 8, 10] 在这个简单的需求下，命令式的写法其实也是很直观的。只需要在 for 循环中，每步向 result 列表添加计算好的新元素，就可以得到正确结果。\n如果我们希望在函数式语言下达到类似的效果，又应该怎么办呢？\n由于在纯函数式语言 (如 Haskell) 中，我们要避免使用可变状态，无法在函数式语言使用类似 result 这种可变的变量。换言之，函数式语言中我们不能用传统的、基于可变状态的 for 循环来解决这个问题。\n要想实现类似效果，一个自然的想法就是要把函数式语言中的“一等公民”，也就是函数，将其作用发挥到极致。\n我们可不可以通过函数的组合，来代替 for 循环呢？这时，我们就需要引入这篇文章的主角：高阶函数。\n2. 高阶函数 map 什么是高阶函数 (Higher-Order Function) 呢？我们一般认为，可以将其他函数作为一个函数的参数传入进去，或者返回值是一个函数的函数可以被称之为高阶函数。\n在上述这个案例中，Haskell 语言的常见做法是使用 map 这个高阶函数。\n比如，我们可以通过定义一个 doubleNum 函数，将这个函数传入 map 的方式来解决这个问题：\ndoubleNum :: Int -\u0026gt; Int doubleNum x = x * 2 result :: [Int] result = map doubleNum [1, 2, 3, 4, 5] -- [2, 4, 6, 8, 10] 这里 map 的原理是什么呢？首先，我们定义了一个可以把任意整数翻倍的函数；然后，我们通过 map 高阶函数，把这个 doubleNum 函数应用到了列表中的每一个值；最终得到了预期的计算结果。\n在这里，map 函数接受一个函数和一个列表，返回一个新列表。新列表中的每个元素，都是原列表对应元素经过传入函数计算后的结果。\n实际上，这个思路和命令式是类似的，但是使用的工具不同。这里的 map 是一个纯粹的函数，唯一特殊的地方，就是接受第一个参数实际上也是一个函数。\n当然，如果你觉得这样写有点麻烦，我们实际上可以使用「匿名函数」这个特性来进一步简化代码：\nresult :: [Int] result = map (\\x -\u0026gt; x * 2) [1, 2, 3, 4, 5] 匿名函数允许我们在不给函数起名字的情况下，使用这个函数。这在一些函数特别简单的场景下很有用，比如这里把值翻倍这种简单的需求，用匿名函数可以简化代码。\n这里的 \\x -\u0026gt; x * 2，本质上和上文中起名的函数作用是一致的。\n3. 进一步讨论 通过上面的例子，我想大家对高阶函数的一些常见使用场景有了进一步的理解。\n实际上，尽管提到高阶函数，大家一般会想起函数式语言中特别常用的 map\\filter\\foldl\\foldr，开发者完全可以根据自己的需求，自由拼装组合各种函数，灵活实现自己的需求。\n当需求变得更复杂的时候，我们会发现使用高阶函数的代码往往比传统 for 循环的代码更具表达力，而纯函数特性又会使高阶函数具备行为确定性、可测试性。\n当然，上文提到的 Haskell 例子就是最简略的写法了吗？其实不是。我们还可以这么写：\nresult :: [Int] result = map (*2) [1, 2, 3, 4, 5] 至于为什么「*2」这也可以是一个函数，就不得不提到函数式语言的另外两个特性：柯里化和部分应用。这些概念，我将会在本专栏下一篇文章详细说明。\n当然，即便到这里，我想我们仍然不能说这种高阶函数的写法就一定比 for 循环更出色。\n但如果我们继续深入学习 Haskell 的知识，在掌握 Functor 概念后，让 map 推广到 fmap，函数式写法的优雅性才会真正展现。当然，这些内容就要在专栏更靠后的文章才会讨论了。\n在文章最后，我还是想再强调一遍：我们不是只能在函数式语言中才可以使用高阶函数。\n比如，开头用 Python 写的代码片段，我们完全可以使用函数式风格写法实现：\ndef double_list(num_list: list[int]) -\u0026gt; list[int]: return list(map(lambda x: x * 2, num_list)) print(double_list([1, 2, 3, 4, 5])) 我们同样可以使用 Python 内置的 map 函数来完成这个任务。这里的 lambda x: x * 2 是和上文类似的匿名函数。\n尽管高阶函数写法在 Python 中并非强制的实现方式，我想掌握更多的解决问题工具，对于理解别人的代码、写出表达能力更强的代码都是有帮助的。\n如果你喜欢这篇博客，欢迎关注我。共勉！\n本系列的下一篇文章：函数式心法 (3)：巧用柯里化和部分应用\n","date":"2025-08-21T00:00:00Z","permalink":"/post/250821-higher-order-functions/","title":"函数式心法 (2)：理解运用高阶函数"},{"content":"上篇文章主要谈了 NixOS 的高可复现性、可维护性和原子化更新特性如何恰到好处地解决了我的需求。如果你还没有看过，请参考 本系列上一篇文章。\n从这篇文章开始，我们将开始提一些学习配置 NixOS 开发机时更具体的建议。\n1. NixOS 真的对新手很不友好吗？ 首先，我想要回答一个问题：NixOS 真的对新手很不友好吗？\n实际上，我觉得起码从系统安装的角度来看，其实并不难，想要尝鲜体验一下的难度远低于大多数人想象。\nNixOS 官网提供了包含图形化界面的安装镜像，以及命令行安装的最小安装镜像。其中，图形化镜像的安装过程和那些公认新手友好的 Linux 发行版并无太大区别，且图形化安装界面会引导你选择你喜欢的桌面环境。\n也就是说，如果你拿不准主意不知道 NixOS 是否适合你，我的建议就是从官网上下载一个 ISO 镜像在虚拟机内安装试试，亲自尝试过才知道适不适合。不论后续配置困难与否，至少安装过程是很丝滑的，可以进入系统以后再慢慢体验和斟酌。\n事实上，对于下定决心要学习使用 NixOS 的朋友而言，我仍然建议在虚拟机内先尝试一下，并做一些基本的配置工作。不同于其他发行版，我们在虚拟机内做的所有配置调整，未来都是可以迁移到物理机上的，不必担心有重复劳动的问题。\n也就是说，我们完全可以想在虚拟机内尝试多久就尝试多久，直到觉得系统达到相对比较顺的时候再考虑真正在物理机上安装。\n系统安装的流程很直观，如有任何问题，或者你是 Linux 资深用户并想尝试无图形界面镜像的最小化安装，请查阅官方手册的对应部分。\n2. NixOS 在当下的正确打开方式 在这篇文章写成的时间点而言 (2025 年 8 月)，即便 Nix Flakes 仍然是测试阶段，我建议一定要从开始就使用 Flakes 和 home-manager 来管理自己的配置。这两个工具分别解决了不同方向的问题，配合起来使用才可以实现真正高可复现性的 NixOS 环境。\nFlakes 作为新一代 Nix 工具链的重要一环，即便现在名义上还没有完全稳定，也已经具备了极佳的可用性和开发者体验。向 NixOS 配置中引入 Flakes，才可以真正实现精确到 commit 级的软件源。\n这主要是因为，引入 Flakes 后系统会自动生成并维护一个 flake.lock 文件，这个 lockfile 本质上和 JavaScript 项目中的 packages.lock，Rust 项目中的 cargo.lock 的功能是一致的。这个由系统自动维护的文件会详细记录软件源 (如 nixpkgs) 的 commit 值并锁定，这在根本上避免了重新构建时可能出现的版本漂移的问题。\n也就是说，只有引入了 Flakes，才真正实现了系统层面近乎完美的可复现性。\n而 home-manager，解决的是用户层软件配置可复现性差的问题。按照原来的配置模式，所有软件均为系统层全局安装，而配置文件和传统 Linux 系统一样放在用户 home 目录下软件指定的文件夹中，这就意味着需要用户自己想办法处理软件配置文件的备份和版本管理问题。\n引入 home-manager 后，一方面，一部分软件可以为具体用户安装而不是全局安装，对于有多用户管理需求的使用场景要友好很多，可以实现严格的用户间开发环境隔离。\n另一方面，由于 home-manager 源软件在打包的时候往往提供了一些 API，用于把原先 YAML、TOML、JSON 或者其他配置文件类型，转化为 Nix 语言配置 (当然用户也可以选择使用原版配置文件)，并在新配置 rebuild 过程中自动在 home 目录下对应位置创建指向 /nix/store 目录的软链接。这使得用户层一些软件的配置信息也可以方便地使用 git 进行版本管理。\n简单来说，home-manager 让我们对于终端模拟器、shell 等很多用户层配置也可以轻松进行版本管理和跨设备配置精确复刻。配合 Flakes，从系统层到用户层的几乎全部细节，都被一套配置文件定义了，用户可以轻松实现备份、版本管理、迁移等工作。\n关于 Flakes 和 home-manager 的具体使用和配置，我非常推荐知友 @於清樂 写的 NixOS \u0026amp; Flakes Book。全文使用清晰流畅的中文，用深入浅出的方式介绍了许多稍显复杂的概念。\n我自己基本就是看这套教程入门的，哪怕只是把基础部分看完，对于 NixOS 终端用户而言就完全够用了，在知识上就具备了定制化 NixOS 开发环境的条件。\n3. 遇到问题怎么办？ 当然，考虑到 NixOS 的复杂性，我想我们在使用过程中不可避免会遇到一些问题。\nNixOS 被人诟病的一点就是文档混乱分散，质量比 Arch wiki 确实存在差距。但是，当你掌握了一定的检索技巧后，我觉得很多问题实际上还是能找到解决办法的 ── 文档只是分散，并非完全不存在。\n首先，对于想要找“保姆级教程”的朋友，追求文章的可读性和逻辑性，首先推荐的就是上文提到的NixOS \u0026amp; Flakes Book，此外使用英语创作的 Nix Pills。\n对于 Nix 语言本身，建议先从 官方 Nix 语言入门教学 看起，先学一下基本的数据结构，有这些基本知识，做简单的系统配置完全够用。\n至于后续其他深入的 Nix 语言特性，比如柯里化、高阶函数等函数式语言特性，可以等需要的时候慢慢研究，或者移步我目前正在撰文的另一个专栏：《函数式心法》。\n对于具体软件的配置问题，首先还是建议先查 NixOS Wiki。但是，需要注意的是，Wiki 的各个页面良莠不齐，有的页面事无巨细基本配置抄了就能用；有的页面语焉不详，令人不知所云。所以，我们在使用的时候应当好好甄别。\n而且 NixOS Wiki 实际上还有两个版本，这是 官方版本，这是 用户维护的非官方版本。\n对于 NixOS Wiki 没有覆盖的软件配置，通常下一步是查阅 Arch Wiki 和 mankier 看有没有信息供参考。\n当然，查到非 NixOS Wiki 上的配置指引，想要翻译成 Nix 配置还是需要再转化一下的。这时候就可以查官方的 NixOS Search 和 MyNixOS。\n到这一步，绝大多数问题都基本可以解决。官方提供的 NixOS Search 是一个兜底性工具，毕竟所有的软件包、系统全局配置选项、home-manager 配置选项以及 Flakes 配置选项等信息都可以查到，并且一般伴有比较详细的说明和示例。\n如果你仍然有疑问，也欢迎到知乎上提问并邀请我，我非常乐意回答大家的问题。\n这篇文章主要谈了 NixOS 安装、Flakes 和 home-manager 的必要性以及遇到问题时的正确解决思路三点。后续几篇文章，我将深入谈一些 NixOS 使用时的技巧，并开始展示如何定制适合自己工作流的桌面环境 (Niri 窗口管理器) 和其他开发工具。\n","date":"2025-08-20T00:00:00Z","permalink":"/post/250820-nixos-install-and-search-basics/","title":"打造 NixOS 开发环境 (2)：NixOS学习与配置入门指南"},{"content":"NixOS，一个用户评价两极分化相当严重的 Linux 发行版。\n最近一个星期，我终于下定决心尝试在旧笔记本上安装使用 NixOS，打算将开发环境彻底迁移到这个声明式配置的操作系统上。事实上，从我的个人经验来看，配置使用 NixOS 的难度比我原先预想的要小，最终达到的效果十分令我满意，我终于开始理解为何知乎、Reddit 上不少资深 Linux 用户最后选择 NixOS“养老”了。NixOS 的声明式配置和原子化特性，完美解决了我多设备维护和更新的痛点。\n这篇文章将作为后续一整个系列的开篇，详细讲述我选择尝试 NixOS 的目的和核心考量，以及从宏观角度谈谈这个操作系统适合的用户画像。\n1. 问题描述 虽然说我觉得自己其实是有一定的 Linux 使用经验的，之前体验过多个不同的发行版，但从个人开发环境的角度说，除非有特殊的项目需要，我大部分的开发环境都在主力 Windows 笔记本上。\n实际上，绝大多数时候 Windows 环境配置还是很容易的，对于资源比较充裕的语言和开发工具，支持 Windows 哪怕麻烦也值得尝试去做，绝大多数情况下往往不存在什么问题。但当我开始研究一些相对小众的语言，如 Haskell，或者涉及到底层 C 编译器的问题时，Windows 的解决方案要么不够简单优雅，要么完全不可能。手边有 Linux 开发环境就显得尤为重要了。\n当然，你可能说这完全可以通过虚拟机和 WSL 配合完成。一方面，使用这些解决方案对日常携带的笔记本电脑有着不低的性能要求；另一方面，对一些特殊的开发需求，比如 GUI 软件开发，想要理顺开发环境同样是不轻松的。\n考虑到我自己手边有一些相对老旧硬件，我当时非常自然的想法是把这些多年前淘汰下来的设备利用起来，把一些非 CPU 密集型任务迁移到这些设备上，同时通过物理机使用 Linux，倒逼自己提升 Linux 的实际使用能力。\n事实上，我大一的时候就是这么做的，在家和学校各有一台配合使用的 Linux 设备，并将相当一部分开发环境从 Windows 主力机上迁移了出去。\n但后来放假的时候又遇到了两个问题：\n其一，在一台设备上配置好的环境，换到另一地的另一个设备上工作时，自然发现完全没有同步配置。很多东西要么自己维护一个自动化配置脚本，要么就必须自己手动再踩坑配置一遍，完全是时间的浪费；\n其二，在一地的时候另一地的设备处于不使用状态。对于个人开发机而言，选取滚动发行版并尽量追新软件是一个自然的选择，但滚动发行版的一个重要使用准则就是要紧跟发行版更新，长期不更新的设备突然更新可能会出现各种依赖解析错误。\n我之前碰到过长期不更新 OpenSUSE Tumbleweed 再次更新后的 Python 依赖版本冲突问题，虽不致命，但很多时候被破坏的环境也要花不少时间修复，这在我看来同样是时间的浪费。对我一些使用 Arch Linux 的朋友而言，长期不更新的设备进行更新，往往体验会更“刺激”一些。\n于是我开始思考：有没有一种解决方案，能够实现开发环境的一劳永逸呢？至少，它应当在极大程度上降低切换设备的维护成本，并尽可能把开发环境的痛点问题提前暴露解决。\n于是，我注意到了 NixOS。\n2. 为什么选择 NixOS 事实上，NixOS 通过其高可复现性的特征，帮助我达成了“配置跟着人走”的目的，可以灵活决定更新的方式和频率。\n我们来看看对于上文提到的两个问题，NixOS 的解决方式：\n关于“配置跟着人走”的需求，NixOS 让我实现了通过前期打磨一套系统层、用户层和具体项目的配置文件，把整个操作系统中的各组件和开发环境清晰地、声明式地描述出来，这套配置文件是可以通过 git 版本管理跟着人走的。这意味着，换一台设备，只需安装系统时对硬件相关配置稍作调整，就可以实现环境的完美复刻。\n关于滚动更新和长期停机更新的矛盾问题，NixOS 提供的解决方案同样很令人满意。即使用 Unstable 源，在 A 设备上能正常构建的配置，B 设备即便一段时间未更新，升级时也基本不会出现依赖冲突问题。即便真的有问题，NixOS 的原子化升级特性也可以兜底，确保了系统不至于陷入“升级了一半”这种尴尬境地，开发者有充分的时间慢慢排查配置错误。\n总而言之，对于我自己特定的需求而言，NixOS 是一个远优于其他方案的解决方案。\n3. NixOS 适合所有人吗？ NixOS 是一个适合我的选择，但这个发行版适合所有人吗？尽管我非常喜欢这个发行版，我仍然不觉得这是一个适合所有人的解决方案。原因有以下几点：\nNixOS 并不简单。这显然不能完全称之为一个新手友好的发行版，我个人建议是至少要有一种别的发行版的使用经验，熟练使用命令行排查问题，对 Systemd 有着基本的了解，这种情况下迁移到 NixOS 的过程才会相对顺滑； Nix 是一门函数式语言。这门语言其实语法很简单，官方文档看两个小时基本就能学个大概，但是毕竟需要一点函数式思维。如果你接触过 Clojure、Haskell 等函数式语言，上手 Nix 会相当简单。 NixOS 无法遵守 FHS 标准。为了达到 NixOS 无与伦比的可复现性，你会发现许多 Linux 系统目录内都是向 Nix store 内的只读软链接，而不是像传统发行版那样的真实文件。这个问题并非无解，绝大多数时候主要是开发者在打包一些软件(尤其是闭源软件)需要头疼，对终端用户是无感的。但如果你有特殊需求必须依赖 FHS 标准，NixOS 很可能不是一个方便的选择，需要你深入理解 Nix 打包软件的方法。 当然，如果你是一位 Linux 资深用户，我认为学习使用 NixOS 是完全值得的。\n我一直觉得，一些需要付出一定的初始学习成本，但是后期可以获得高回报的技能、工具都是值得学习的，也就是坚持长期主义。之前学习 Rust 语言，强迫自己适应内存安全的代码规范是如此；练习双拼输入法，提高博客写作效率也是如此；而 NixOS，我觉得是在操作系统层面提供的一个类似的“长期主义”路径。\n这篇文章将作为一个系列文章的开篇，我将在后续文章中继续分享我是如何打造基于 NixOS + Niri 窗口管理器的桌面环境，使用 Flake 和 home-manager 分别管理系统层和用户层配置，开发环境配置，以及探讨一下在部署时 Nix 和 Docker 容器化技术关系等问题。\n如果你同样对多设备开发环境感到困扰，或者单纯对 NixOS 的设计哲学感兴趣，欢迎关注我，也欢迎与我随时交流！\n本系列下一篇文章：打造 NixOS 开发环境 (2)：NixOS 学习与配置入门指南\n","date":"2025-08-19T00:00:00Z","permalink":"/post/250819-nixos-intro/","title":"打造 NixOS 开发工作流 (1)：为什么选择 NixOS"},{"content":"Haskell 是一门风格极其独特的语言。网络上我们往往能够看到各式各样对其函数式特性的评价，我相信不少人对其的印象就是一门“函数式纯度高、学术风格浓厚、学习曲线陡峭”的语言。\n最近机缘巧合下我尝试了一下这个语言。从我个人的感觉而言，这门语言其实并没有想象中那么难，很多特性的引入不仅在我看来很自然，对于代码抽象程度和表达能力的提升也是肉眼可见的。\n这篇文章我将以一个有其他函数式语言经验的 Haskell 初学者视角，谈谈我的看法。\n如果你还没有决定是否要学这门有趣的语言，或者刚刚开始你的 Haskell 旅程，我想看完本文以及计划中的系列文章后，你会有一些全新的想法。\n1. 为什么要学 Haskell？ 对我而言，理由其实很简单：之前接触了不少语言，有些是混合范式语言中具备不少函数式风格，有些虽然是函数式语言，但由于各种设计上的考量最后还是引入了一些没那么函数式的特性。随着对函数式风格理解的逐渐深入，最终接触最纯粹的函数式语言 Haskell 几乎是必然的。\n当然，除了对我自己，我始终觉得对于任何一个开发人员而言，学习掌握函数式风格语言独特的抽象方式都是有价值的。\n1.1 有助于学习其他新语言 首先，学习掌握函数式语言，特别是 Haskell，对于深入理解其他语言在新版本中引入的特性是有很大帮助的。\n比如，掌握了模式匹配、匿名函数等概念，在写 Python 的时候就会发现，新版本引入的模式匹配和匿名函数语法糖其实还是很有用的，合理使用可以提高代码的表达能力和可读性。\n再比如，理解 Haskell 的高阶函数以及应用场景，会发现可以在 JavaScript 等很多语言用高阶函数简化层层嵌套的循环结构。\n许多主流语言在近十年间积极吸收函数式语言的发展成果，这使得学习函数式语言，客观上也达到了学习其他语言创新成果的目的。而且，由于 Haskell 的纯粹性和体系性，这些函数式风格的威力和精妙之处，往往在 Haskell 编码实践中才能得到最深刻的体会。\n总而言之，即便你以极其功利的目的，学习 Haskell 也不亏。从来没有浪费的学习。\n1.2 有助于获得全新的抽象思维 其次，由于函数式风格和传统面向对象的差异很大，使用的抽象思维方式也大相径庭。学习 Haskell 可以让我们在面临同样的工程需求时用截然不同的抽象视角来完成工作。\n面向对象需要工程师思考类之间的职责划分、继承层次和对象交互；而函数式编程则依赖代数数据结构的设计、不同函数的组合以及副作用的严格隔离等。\n在一些工程中的重要问题上，Haskell 的函数式特性也带来了和其他语言间的巨大差距。例如，全局不可变性和无副作用函数彻底规避了共享状态竞争，设计并发程序兼具简洁性和工业级效率；基于代数数据类型和 Monad 抽象的 Maybe/Either 等类型，将错误处理编码进类型系统，通过严格的编译期检查覆盖所有分支，避免了许多运行时崩溃。\n显然，二者所需的工程思维和架构方法是完全不同的，对于同一个现实问题，掌握函数式编程的人就可以使用一种风格迥异、许多时候表达能力更强的方式将问题转化成逻辑和代码语言。\n1.3 坚若磐石的稳定性 进一步来说，我觉得即便你不喜欢上述所有提到的特性，有一点是必须说明的：Haskell 通过严格的类型系统使得所有能编译运行的代码有相当的质量保障。\nHaskell 的严格编译期检查，有一些人可能不太喜欢，会觉得编译期检查影响了编写代码的速度。\n实际上，这些检查的逻辑在于把很多运行时测试才可能发现的问题提前到编译期一起解决掉，确保可编译代码的质量，极大提升了软件上线后的稳定性和可维护性。\n实际上，如果我们充分对比动态语言软件和 Haskell 在测试、维护、重构与拓展等多阶段的情况，我们很可能发现后者的严格检查虽然在编译期需要更多时间处理，但从全局来看反而是提高了效率的。\n当然，Haskell 社区内流传的传说“编译通过即正确”固然是夸张的说法，毕竟编译期无法验证软件业务逻辑是否正确，该有单元测试、集成测试一样都不能少。然而，编译通过意味着代码排除了所有编译期涵盖的错误类别（如类型不匹配、分支覆盖不完备、副作用处理不当等）。这种编译期给开发者带来的安全感和对核心正确性的信心，是其他语言无法比拟的。\n从这个角度而言，我个人觉得唯一可以和 Haskell 实现类似“把错误提早到编译期”理念的语言就是 Rust。不过 Rust 为了实现底层内存操作，引入了所有权、生命周期等概念。如果你觉得某个项目不追求手动控制内存带来的极致性能，可以接受由 GC 来回收内存垃圾带来的便利性和开销，那么 Haskell 是在更高抽象层次上践行把问题提前解决的极佳选择。\n1.4 极高的 AI 驱动编程契合度 顺着上一点往下说，我一直觉得强调严格编译期检查的语言，特别适合 AI 辅助编程，乃至完全由 AI 智能体驱动的开发。\n原因很简单：编译期错误信息通常是确定性的、局部的，并且直接指向代码中违反语言规则的具体位置。大语言模型可以相对容易地通过上下文和详细报错信息，尝试推断问题根源并修复错误。\n对于 Haskell 和 Rust 而言，如果代码有问题，可以直接向大语言模型提供详尽的编译器报错信息，让 AI 根据报错和上下文自主排查错误根源，排除类型错误。\n反之，如果是 Python 的代码，可能有些时候看似正确的代码其实是有问题的，只有进入运行时才能够被发现。运行时排查问题，除非这个软件本身就有完善的日志系统可以清晰向 AI 提供相关信息和问题复现条件，很多时候解决问题还得靠人类开发者。\n当然，这不是说 Haskell 生成的代码就一定比 Python 好。实际上，由于 Python 训练语料更多，一般而言 Python 代码的生成质量是不错的。\n但即便 Python 的代码是正确的，我在使用前往往需要大量测试才敢用；对于 Haskell，AI 如果能生成可编译的代码并且人工复核业务逻辑无误，那么代码的基础健壮性就有了一定的保证，显著降低 AI 生成代码引入隐蔽运行时错误的可能性。\n2. 核心概念一览表 (中英对照) 学习任何具备一定体系性的知识，首先都需要把“要学什么”的路径图规划清楚。\nHaskell 作为一种语法设计高度统一的语言，把握住正确的学习方向和路径就尤为重要。而把学习方向的第一步，就是要对核心概念有一个基本的印象。\n由于 Haskell 中文社区很多大佬的文章往往会混用一些中文表达的概念和英文表达的概念，我觉得有必要提供一个简单的中英文概念对照。很多时候让你一头雾水的某个概念，可能换一个语言环境解释就清楚了。\n这里，我提供一个中英文对照的概念表，以及一个非常简略的介绍。我认为初学阶段需要掌握的概念包括：\n中文概念 英文概念 说明 函数定义 Function Definition 使用 = 定义函数、参数、返回值 基本类型 Basic Types Int, Char, Bool, String/List/Tuple 等 模式匹配 Pattern Matching 可以代替大量嵌套 if...else... 结构，舒适定义代码的控制流 匿名函数 Anonymous Function \\x-\u0026gt;x + 1，不需要命名就可以使用的函数 高阶函数 Higher-Order Function 接受函数作为参数，或者返回函数的函数，如 map/filter/foldl 柯里化 Currying 所有函数本质上只有一个参数，(a,b)-\u0026gt;c 等价于 a-\u0026gt;b-\u0026gt;c 部分应用 Partial Application 只提供函数部分参数，与柯里化高度相关 代数数据类型 Algebraic Data Type (ADT) data 关键字定义类型，可以是枚举、结构体或它们的组合 Maybe 类型 Maybe Type Maybe a = Just a | Nothing 类型类 Typeclass 类似接口，定义一组函数，类型通过实现函数来声明支持该行为 惰性求值 Lazy Evaluation Haskell 一大语法特点 函子 Functor 可被映射的数据容器或上下文 应用函子 Applicative 一种承载了函数的容器，可被应用于 Funtor 单子 Monad 代表顺序计算链，用于处理副作用、可选值、错误等上下文中的计算 do 语法 do Notation 好用的编写 Monad 计算的语法糖 这个列表大体上遵循了从上到下由易到难，如果你还没有开始学 Haskell，我觉得完全可以按图索骥一步一步掌握语言的核心思想。\n掌握这个列表中的概念，是学习 Haskell 语言和阅读社区代码的关键基础。当然，实际项目中还需要理解更多细节和工具链的使用，但把握住这些概念就具备了构建复杂软件的基本能力。\n3. Haskell 怎么学 我觉得 Haskell 学习过程中最重要的一点，就是多找教程交叉比对。\n我之前专门写过一个回答论证“你学不明白不见得是你的问题，可能是没找对教程的问题”，我觉得理解这一点在学习 Haskell 中十分重要。\nHaskell 语言很多高级抽象工具，像是 Monad，想要单凭一篇文章讲明白其实是很困难的，但如果你读了二三十篇技术博客谈这一个概念，即便仍然不能完全理解，起码多篇不同侧重点的文章建立起对一个概念的立体理解。\n总而言之，找到适合自己理解的教程是相当重要的，而唯一能确保这一点的就是先浏览大量材料，再从中挑选契合的文章重点精研。\n此外，我还想说一点就是实践优先，在做中学。\nHaskell 本身的语言设计可能有不少学术成分，网络上一些人也有论及 Haskell 必谈抽象代数、范畴论的习惯。\n我不是说理解背后的深层次数学思想无用，只是如果你对这些数学工具了解不多，大可不必被吓到。实际上，Haskell 选择这些语言特性更多是出于实践因素的考量，只有不断在项目中实践，才能够真正理解深层次的设计理念。\n须知，不必深入理解数学思想，也可以高效运用 Haskell 解决问题。理解底层数学原理有助于揣摩设计动机，但绝不是使用 Haskell 的门槛。\n最后，如果你对这个语言感兴趣，欢迎关注我，我将在未来的一段时间持续讲解编写 Haskell 的心得体会和实践经验。\n本系列下一篇文章：函数式心法 (2)：理解运用高阶函数\n","date":"2025-07-19T00:00:00Z","permalink":"/post/250719-haskell-intro/","title":"Haskell 心法 (1)：初识 - 为何学、学什么和怎么学"},{"content":"上篇文章我们聊了 Clojure 语言的基本特性和语言生态，这篇文章我想讲讲 Clojure 的语法，以及最有特色的 REPL 驱动编程。\n欢迎查看这个系列的上一篇文章，也欢迎关注我的全栈开发笔记系列专栏，我将更新更多精彩内容。\nClojure 一系列神奇的特性，比如非常好用的 REPL 驱动开发、基于宏的元编程等。\n究竟是什么带来了这一系列可以极大提高开发者体验的特性呢？我希望在这篇文章初探一下。\n1. Clojure 的基本语法结构 如果你看过 Lisp 语系的语言，一定会对其满屏的()小括号印象深刻。\n不同于 C 系的语言普遍使用{}来划分代码段，Lisp 系语言使用的是层层叠叠的小括号。\n我们来看一个 Clojure 官网提供的示意图：\n这个示意图上使用了一个非常基本的 Clojure 表达式：\n(+ 3 4) 这和其他语言的3 + 4的含义是一样的，最后计算出的结果也是 7。\n用 Clojure 的语法概念来分析，+加号一个符号，这个符号代表的是加法函数。后续的数字3和4，是传入函数的参数。\n从另一个角度说，这整个表达式不仅仅表示了一个函数。如果我们不考虑这行代码在执行时的作用，只看字面形态，这个()由小括号括起来的结构实际上也可以代表 Clojure 原生数据结构：列表(List)。\n也就是说，每个 Clojure 的表达式实际上也是一个 List，我们可以去评估这个表达式的执行结果，也可以像操作一个列表一样来操作这个表达式。\n这个特性，实际上对于 Clojure 实现方便的宏编程有着重要的作用。当然，要讲到宏，还需要再写 2~3 篇文章，欢迎大家关注我。\n2. 独特的代码评估过程 如果你接触过 Java，一定熟悉 Java 的代码评估流程。我们来看一个同样援引自 Clojure 官网的流程图：\nJava 的源代码被编译器javac编译成可以在 JVM 上运行的字节码。\n那么，同样作为运行在 JVM 上的语言，Clojure 的策略是什么样的呢？\n源代码在被编译成字节码前首先经历了一步，就是被Reader读取成了 Clojure 数据结构 ── 也就是上文提到的 List。\n这种设计就意味着：\nClojure 代码的最小执行单元不再是源代码文件，而是一个由()括起来的表达式； 由于源代码文件实际上也被读取成一个个表达式，那么其实完全可以由开发者交互性输入一个个需要的表达式然后执行，这就为 REPL 驱动开发提供了可能； 由于把源代码转化成数据结构的Reader和编译为字节码的Compiler两个步骤被严格分开，用代码生成代码的思路（也就是宏）成为可能。宏在编译阶段（Compiler）的早期进行展开，而这个编译阶段的输入是 Reader 产生的数据结构。宏展开发生在将数据结构编译成字节码之前； 下面，我们具体重点讨论第二点，也就是 REPL 驱动开发。\n3. REPL 驱动开发 所谓 REPL，指的是“Read-Eval-Print-Loop”，也即读取 ── 评估 ── 打印循环。\n如果你用过 Python 的 Jupyter Notebook 或者是 Wolfram Mathematica，我相信你一定不会对 REPL 感到陌生。每次需要修改代码时，无需从头到尾执行一遍代码文件，而是可以只执行一个较小的代码片段，立刻看到这个代码片段的执行结果。\n事实上，Clojure 的 REPL 要比 Python、Wolfram 语言的 REPL 要好用得多。究其原因，还是因为 Lisp 语系独特的语法结构。\n由于任何一个由()小括号括起来的表达式都可以执行，我们可以尝试按照任意顺序、执行任意嵌套层级的表达式。\n举一个简单的例子：\n这是我配置好的 VSCode + Calva 插件的环境，这是我从一个代码文件中间随便挑出的代码片段：\n我可以随性所欲地执行代码片段看看结果：\n这种即时反馈和探索能力，是不是极大地提升了开发体验？\n总结来说，Clojure 基于 S 表达式(S-expression)的语言特性，使其天然具备了实现强大开发工具（如 REPL）和语言扩展能力（如宏）的基础，带来了许多独特且高效的开发体验。\n今天这篇文章着重讲了 REPL 驱动开发的基本概念。下面几篇文章，我将谈谈 Clojure 的基本数据结构，流程控制，以及函数式风格的高阶函数写法等问题。\n关注我，不错过更多有趣的技术博客！\n","date":"2025-06-22T00:00:00Z","permalink":"/post/250622-clojure-2-repl/","title":"Clojure 笔记 (2)：基本语法结构和 REPL 驱动编程"},{"content":"这篇文章我们来说说 Clojure 这个语言，Clojure 在 web 领域的生态情况，以及如何配置自己的本地环境使其能够顺畅编写 Clojure 代码。\nClojure 语言介绍 我之前了解到这个语言，主要是一次偶然的机会对 Lisp 系的语言产生了兴趣。\n作为一种设计远远超前于当时时代的语言，Lisp 语言生不逢时，未能获得与其优秀设计理念相匹配的广泛普及和应用。\n然而，即便如此，从 Lisp 中诞生的一系列优秀的设计理念并没有被埋没，反而在后续大量的其他语言中被继承，被发扬光大。\n举例而言，像是函数式编程的范式、基于宏的元编程思想等等，这些思想在其他各种语言中或多或少地得到了继承和融合。\n当然，还有一些更贴近原本 Lisp 的语言。这些语言一般都是在 Lisp 语言基础语法上稍加修改，我们一般称之为 Lisp 方言。\nClojure 就是这么一种 Lisp 方言。这个语言依托于 JVM 生态，可以将 Lisp 风格的代码编译成字节码在 JVM 上运行。这样设计，就可以让 Clojure 可以比较方便地调用 Java 生态下的库和函数，方便开发者开发复杂的项目。\n从结果上来看，Clojure 这种“背靠大树好乘凉”的策略是成功的。在目前所有 Lisp 方言中，可以说目前 Clojure 的生态是相当不错的，社区也很活跃。\nClojure 的 web 生态 既然背靠 JVM，一个绕不开的话题就是 web 后端服务。这是我们这个专栏后续会主要关注的方面。\n实际上，从我目前的学习体验来看，Clojure 是非常适合用于 web 后端开发的。\nClojure 本身的函数式特性使得网络请求处理的生命周期清晰明确；宏和元编程赋予了 Clojure 无与伦比的表达能力，可以用很少的代码就完成复杂的业务需求；基于 REPL 的开发模式大大提高了开发效率；由于背靠 JVM，即便出现了某个冷门库缺失的情况，也可以轻松调用 Java 生态的库来解决。\n不同于在 Java 内具备统治性地位的 Spring 全家桶系列，Clojure 语言没有一个大而全的后端框架，而是有一系列松散的库的合集。\nClojure 生态似乎比较排斥“框架”的概念，很多人都觉得框架太重了，而“库”这个体量刚刚好。\n实际上，Clojure 的后端生态由围绕在 Ring 这个基本库构建的一系列互相配合的库构成。既然被称为库，绝大部分库的核心代码行数往往就在 1k~10k 这个水平，每个库各司其职，需要开发者掌握的接口数量一般都很少。\n不少库甚至不需要一个专门的文档网站，只需要读完一个不长的 README 文件就掌握了全部核心技巧了。\n所以，尽管 Clojure 的 web 生态看上去要学很多东西，但实际上只需要掌握几个核心库就可以启动项目了，其他库可以根据需要渐进式添加。\n在本专栏后续的文章中，我们会首先简要讲解 Clojure 语言的基本特性，然后深入介绍围绕 Ring 的各个 Clojure 后端库。\n关于环境配置 作为专栏第一篇写 Clojure 的文章，我自然要稍微提一下 Clojure 的环境配置问题。\nClojure 目前主流的项目管理方案有两个：一个是比较老的 leiningen 工具，另一个是更新一点的 Clojure CLI + deps.edn。\n如果你之前从来没有接触过 Clojure，我强烈建议从入门时就使用更新一些的 Clojure CLI 作为主要练习的工具，这是目前社区的主流。\n安装 Clojure CLI 的步骤很简单，参照官网上说的操作流程即可。\n对于 Windows（非 wsl）用户而言，官网上建议的是用一个由社区维护的 msi 安装包，但我个人倒是更建议使用如下方式配置好 scoop，这样就可以使用 scoop 进行方便的包管理和更新了。\nscoop bucket add java scoop bucket add scoop-clojure https://github.com/littleli/scoop-clojure scoop install temurin-lts-jdk clj-deps leiningen 这里我们安装了开源的 JDK 实现 temurin JDK 的长期维护版。此外，cljs-deps 涵盖了 Clojure CLI 以及一些其他可能需要的周边工具，以及旧项目可能需要的 leiningen。\n安装完后，执行clj命令，看到如下结果：\n到这里，就算环境配置成功了。\n专栏的下一篇文章，我打算接着聊如何配置 VSCode，使其能够完美实现 REPL 驱动的开发模式，显著提高开发效率；并尝试开始讲 Clojure 的基本语法。\n","date":"2025-06-19T00:00:00Z","permalink":"/post/250619-clojure-intro/","title":"Clojure 笔记 (1)：语言生态介绍和环境配置"},{"content":"从这篇文章开始，我将新开一个全栈开发笔记的专栏，专门记录我从基础开始学习前后端开发的收获和心得。\n欢迎收藏本站或者关注我的专栏。\nECMAScript 标准 我们在学习前端技术的时候常听说要打好JavaScript基础，那么什么是JavaScript基础呢？\n在我看来，其中很重要的一个环节就是要学习符合最新 ECMAScript(也就是我们常说的 ES)标准的JavaScript语言。\n过去，我们总能看到网友用各种段子、梗图调侃JavaScript语言。实际上，这个语言确实在设计之初有着不少缺陷，给人的感觉就是不严谨。\n但随着JavaScript和浏览器运行环境彻底绑定，成为前端事实标准后，有不少行业大佬在尝试引入新的语言风格和标准，来让这个语言变得更好。\n此外，如果想要学习 TypeScript这个在JavaScript基础上更进一步、引入更严谨的类型系统的语言，则必须要学习和接受这些JavaScript的新标准，主流前端框架同样要求我们对这些内容有着较高的熟悉度。由此看来，学习 ES6+的JavaScript语法是非常有必要的。\nJavaScript语言的新标准是由国际标准组织 ECMA International 推动的，所以这些标准又称ECMAScript 标准，简称 ES。\n这些标准在制定、形成共识后，会由主流的 Chrome、Firefox 等浏览器推动兼容，Node.js等主流运行时也跟进新特性支持，从而实现从语言设计到运行环境落地的过程。\n其实 ES6 及以后的新标准内容是相当丰富的，我们一篇文章也很面面俱到。这篇文章，我将从最基本的变量声明内容讲起，一步步深入到更复杂的内容。\nconst/let的引入 过去JavaScript定义变量的唯一关键字就是var，而从 ES6 开始，我们一般建议使用const和let关键字来定义变量。\n顾名思义，const和let的区别主要在可变和不可变上，其中let为可变引用，可以重复赋值；const声明的是不可重新赋值的绑定。\n需要注意的是，这里const的不可变性主要是指这个引用(或者再直白一点，内存地址)不能改变，但是如果被引用的对象或数组发生变化，这是合法的。\n换言之，就是const对于原始类型是值不可变，对于对象、数组这样的复合类型就是地址不变。\n举个例子：\nconst t = [1, -1, 3]; t.push(5); console.log(t); // 合法，打印出[1, -1, 3, 5] t = [1, -1, 3, 5]; // 不合法，不可重复赋值 这段代码是完全合法的，因为尽管t引用的数组新加入了一个数字 5，但是引用本身(内存地址)是没有改变的。\n那么，你可能会问，这么做的好处是什么呢？\n最显而易见的好处就是把可变和不可变引用分开了，代码更清晰，但是实际上还有更深层次的原因。\n一个非常重要的原因就是原先var在设计变量作用域时存在缺陷。\nvar采用的是函数作用域，也就是说一个变量一经定义，在整个函数中都是可访问的。这种情况下可能会导致变量重复声明带来的数值覆盖，或者其他的意外行为。\n反之，let和const采用的是相对更严谨的块作用域定义。也就是说，二者的作用域是明确限定在一对大括号{}里的。\n我们来看一个例子：\nfunction printing() { for (var i = 0; i \u0026lt; 10; i++) { console.log(i); } console.log(i); } 这个函数打印出来的结果，除了for循环打印出的 0~9，for 循环外的console.log(i)会打印出 10。这一点，其实是不够理想的。\n但是如果我们换用let来定义这个i变量：\nfunction printing() { for (let i = 0; i \u0026lt; 10; i++) { console.log(i); } console.log(i); // ReferenceError: i is not defined } 此时，for循环外的console.log(i)就会报出ReferenceError，因为let定义的变量是块级作用域，离开了 for 循环的块这个变量就不可以再访问了。\n下一步写作计划 相信通过这简短的介绍，大家对 ES6+中最基本的变量定义问题有了初步的了解。\n在后续几篇文章中，我将进一步展开讲解最新 ES 标准中的函数定义、解构赋值、异步编程等问题，然后逐步延伸到Solid.js前端框架以及基于Rust的后端框架，尽全力将全栈开发的知识用通俗易懂的语言讲清楚、讲明白。\n欢迎关注我，我将给你带来更多精彩文章！\n","date":"2025-06-03T00:00:00Z","permalink":"/post/250603-js-basics/","title":"前端笔记 (1)：ES 标准和 ES6+ 变量定义方法"},{"content":"我们不妨换个角度思考一下：难道我们存在的意义，在于自己知道多少知识吗？\n如果真的如此，那岂不是知识越多，人生意义越大？而一个知识有限、探索欲有限的人，人生就近乎毫无价值了？\n果真如此吗？\n知识本身不是人生的意义和价值 其实我们仔细思考一下便会发现，这个问题其实没有那么复杂，AI 的崛起也远不至于让大家那么悲观。\n人生的意义和价值从来不简单取决于个体掌握知识的多少。\n的确，了解知识的广度再广一些，深度再深一些，很多时候会让我们的人生变得更从容一些。\n但是从来没有人规定，我们人生的意义、价值，所感受到的幸福感，对社会的贡献等等，一定与知识的多少呈绝对的正相关。\n换言之，即便一个人对学习知识没有那么热衷，可能终其一生没有在很大程度上推动人类知识和思想的进步，但是这个人的人生同样可以是有价值的，可以是对社会有利的。\n那么，你可能就要问了，人生的真正意义和价值在于什么呢？\n关键在于实践 当然，我刚刚提的问题显然没有标准的正确答案。\n对我来说，人生的真正意义和价值，核心在于实践，在于切实改变我们所处的世界。\n了解更多的知识固然很好，但是很多时候仅了解知识是不足以扩展我们人生内涵的。\n要想真正让知识在我们的人生中体现出足够的价值，势必要把知识运用于实践，要在现实生活中运用这些知识，让自己和所处环境变得更好。\n举个例子，比如我现在正在回答的这个问题，其实自己想明白了这个问题当然也不错，但是真正能给我带来价值感的是把这篇文章发表出来，哪怕能帮助看到这篇文章的一两个人，也足够让我满足了。\n从这个角度，其实 AI 的出现当然不会让我丧失存在的意义。\n为什么呢？\nAI 固然知道很多东西，掌握的知识和信息不可不谓全面，但是就目前的技术水平而言，AI 距离独立的实践能力还有着相当的距离。\n实际上，AI 的大部分知识其实就来源于互联网信息资源。这些资源过去就是存在的，有人会觉得人类文明所积累起的浩瀚知识存量使我们的人生变得毫无意义了嘛？\n在 AI 这个多领域基础知识的集大成者面前保持敬畏之心，我觉得这没错；但如果因此感到自我价值被否定，那似乎就不太符合逻辑了。\n我再举个例子：\nAI 或许了解的环境保护知识远远多于常人，对风沙治理的理解入木三分，可是真正让我们感受到可以称得上“伟大”的，是一代又一代人种起的三北防护林。\n植树造林的知识固然很好，可是要说意义价值层面的问题，只有把知识落实到实践中去才能体现，只有三北防护林这样的宏伟工程落到实处时才会让人倍感震撼。\nAI 可以是实践的助力 既然实践是价值和意义的根源，那么其实这一轮 AI 浪潮对我们人生的影响反而明确了：AI 可以帮助我们实践，显著加速我们通过实践实现人生意义和价值的进程。\n也就是说，经过一定的论证，我们得到了一个和问题相反的结论，也即 AI 对我们人生价值和意义的影响是积极而非消极的。\n其原因在于，AI 可以在知识积累层面提供巨大的帮助。只要有恰当的和 AI 配合工作的办法，原先缓慢的知识积累过程可以显著加速，在实践中迭代理论的过程同样可以显著加速。\n有了 AI 的帮助，许多搜集信息、积累背景知识等拖慢实践周期的步骤都可以加快节奏。随着 AI 推理能力的提升，一些复杂的矛盾综合体也可以交由 AI 进行分析，AI 甚至可以参与到我们人生的重大决策中，提供意见供我们参考。\n这些助力，或许就可以让一些后悔和遗憾不再发生，让我们更有信心面临实践中遇到的各种困难。\n总结 最后总结一下，其实问题的核心还是在于实践。\n大家不要被 AI 的知识量吓住了。AI 知道的东西再多，最后还是要依赖我们每一个人把这些知识和理论转化成切实改变我们的生活的实践。\n意义和价值源自实践，而实践的根本，永远在于人本身。AI，终究是我们手中的工具，而非实践的主体。\n","date":"2025-06-02T00:00:00Z","permalink":"/post/250602-meaning-of-life-given-ai/","title":"当我们发现毕生追求的知识在AI面前毫无价值时，如何重建存在的意义？"},{"content":"这篇文章回答了这个知乎问题：5 月 28 日 DeepSeek R1 模型完成小版本试升级并开源，具体有哪些提升？使用体验如何？\n总结了一下我目前直接观察到的特点，包括：\n鼓励提问者，说话更有“人味儿” 开始使用 ta 来代表提问者 输出更为复杂的流程图 1. 鼓励提问者，更有“人味儿” 之前的 DeepSeek-R1 给人的感觉更像是一个知识渊博但是比较严肃的专家，回答我们的问题时不苟言笑，一般都是在分析完用户需求后直截了当地提供解答。\n这次更新完之后，我最直接的一个感受就是 R1 的回答开始鼓励提问者了，特别是你在表达了面临的一些困难时。\n举个例子，如果我们尝试提提问：我最近在学编程，但总是写 bug，有点沮丧。你能给我这个初学者一些建议吗？\n让我们看看 R1 提供的回答：\n非常标准的先表现同理心，安慰一下，再提供解决方案这样的回答思路。\n翻到结尾：\n总的上来说，比起原来目的明确的回答，R1 这个版本的回答开始越来越像真正的对话了。\n当然，这个方面可能对实际产品的使用表现而言没有什么实质性的提升，只是说给提问者带来了更多的情绪价值。\n但无论如何，这的确在一定程度上提高了使用体验。\n2. 更多使用“ta”来代表提问者 如果你仔细看了上面一个问题在推理阶段的截图，你可能会发现 R1 开始大量使用更中性的称呼“ta”来代表提问者。\n为了更好看请这个现象，我们再提一个问题：假设我有个开发者朋友遇到了技术难题，你会如何帮助我的朋友？\n这里我专门用了“开发者朋友”这个称呼，就是为了不在提问中规定明确的性别。我们看看 R1 的推理：\n好嘛，上来先揭穿“我有个朋友”大概率就是我。\n再看看正式回答部分：\n我们会发现，这一轮迭代后的 R1 开始大量使用 TA（有些时候是小写的 ta）作为主要的人称代词，而不是原来经常使用的“用户”。\n同样的，这一点并不会使得模型的硬核表现有显著提高，但是可以看到开发团队在细节用户体验上的努力。\n3. 更复杂的流程图 这一点我觉得是对模型的用户体验有更直接提升的一个方面，那就是 DeepSeek 开始生成更复杂、逻辑更严密的流程图了。\n过去虽然也偶尔看到 DeepSeek 通过 mermaid 生成流程图，但总得上来说频率比较低，且流程图结构相对简单。\n换言之，以往 R1 生成的流程图给我的感觉主要就是一个添头，基本上就是起到回答的装饰性作用，大部分信息点还是在正式文字回答中。\n流程图本身很少提供额外的信息点。\n但是，这轮升级后 R1 模型开始可以生成一些相对复杂得多的流程图，而且这些流程图看完其实是有一些额外的收获的。\n再举个例子，我们直接让 R1 帮我们梳理一个复杂业务的流程：绘制一个“宠物寄养平台”的用户注册、下单、支付、服务确认的全流程，包含异常分支（如退款）。\n我们看看 DeepSeek 生成的流程图：\nR1 总共为我生成了 4 张图，可以看到每张图仔细思考一下逻辑都挺合理的。尤其是第一张图，逻辑清晰、配色合理，的确非常有帮助。\n或者，我们也可以让 DeepSeek 帮我们通过 mermaid 生成甘特图来描述项目计划，这项功能同样是原先就存在但是现在看来更强大了：\n注意哦，这些都是我直接从生成回答中直接下载的图片，未作任何修改。怎么样，是不是现在这个版本的生成效果还是挺不错的？\n总的来讲，我觉得这一轮更新的确在很多细节问题上做了不少优化。更进一步的代码和写作能力还需要在不断日常使用中进行体验，回头有机会我再写写更深入的使用感受。\n","date":"2025-05-29T00:00:00Z","permalink":"/post/250529-deepseek-r1-0528/","title":"5 月 28 日 DeepSeek R1 模型完成小版本试升级并开源，具体有哪些提升？"},{"content":"这主要是为这个知乎问题写的回答。\n错误信息带来的危害远比信息缺失更严重。\n换言之，知道自己不知道、承认自己的无知，对我们做出正确决策的意义和价值比人们想象的要更大。\n信息过载实际上也是在最近十几年才开始成为问题的。\n在互联网普及前，对于我们绝大多数人而言，获取信息的渠道是相对有限的，了解实时信息的体量、效率、即时性都处于相对较低的水平。这也就是题主所说的信息闭塞。\n当然，有人可能会说，难道不是很早前就有了大量书籍可以阅读了嘛？怎么前人没有出现这样的问题呢？\n事实情况是，人们往往对当下发生的事情更感兴趣，而需要耗费更多精力阅读品评、时效性更差的书籍在大众中从来没有广泛流行过。\n阅读书籍所必须花费的物质和时间成本，使其不具备形成信息洪流的条件。\n而这一切在互联网普及、人类进入信息时代后发生了改变，人类通过极低成本获取信息成为可能。\n最近几年的时间中，随着短视频的流行，不仅使得信息获取的门槛进一步降低，上到花甲老人、下到三岁孩童都可以浏览天下大事；更促使全体社会成员的表达欲可以得到充分满足，谁都可以在互联网上给大家讲上两句。\n于是，过去存在的信息闭塞问题似乎逐步缓解了，但出现了题主注意到的新问题：信息过载。\n信息过载出现的根本原因，在于虽然互联网上可以低成本获取的高价值信息的总量大幅增加，可是高价值信息所占比重却在不断降低。\n举个例子：原先所有专业信息都要靠理论专著和论文来获取，自然信息密度高、门槛也高；\n后来有了知乎这个主打专业化讨论的社区，大家看各行各业信息的门槛低了一些，但是与此同时信息密度、高价值信息比例也有所降低；\n再后来知乎开放公众注册，不少追热点、抖机灵、宣泄情绪的短回答获取了大量流量，而长文创作比重显著降低，更别提原先那种旁征博引、论证严密的回答了，以至于开始有了“知乎遗风”的说法。\n我不否认很多短回答在某些特定环境下发挥了有利的历史性作用，但是从信息密度、启发性的角度来说，确实比原来更低了。\n可是，如果我们仔细想想，知乎上优秀的回答真的变少了嘛？其实并非如此，每年知乎上都会涌现出大量优秀的新回答新文章，日积月累之下应当高质量内容越来越多才对。\n究其原因，就是比重问题。只看总量，不看有效信息占全部资料的比重，是无法解释这样的现象的。\n信息过载的一个根本性问题，在于有毒的信息对于我们人生重大决策很可能是灾难性的。\n如果我们知道自己对于某件事不甚了解，尚且存在对该命题的敬畏之心。真到了不得不了解的时候，也会尽己所能地找到合适的材料辅助自己决策。\n然而，互联网广泛存在的良莠不齐的信息，就使得这样的敬畏之心很难继续存在，很多时候我们看了几个不知道几手的信息就觉得自己已经对某个领域了如指掌了。\n此外，还有一个很重要的原因，就是不少人经常会把零散的碎片化的信息，和体系化知识混为一谈，以为二者是一回事。\n有些时候某些信息并不是完全错误的，但是只说明了问题正确的一个方面、一个角度，很难把复杂的矛盾综合体掰开揉碎讲清楚。在这种情况下，看了两篇网络报道、几个视频，哪怕所有信息都是对的，实际上距离真正体系化的判断还差着很远。\n所以大刘才会说，弱小和无知并不是生存的障碍，傲慢才是。\n知道自己不知道，大不了从头学起、从头实践就是了，只要有时间早晚有一天会研究明白；可如果不知道自己不知道，那只会固步自封、狂妄自大，问题就很严重了。\n事实上，只要信息质量够高，我们完全没必要追求自己永远紧跟当下热点，把握住时代大势即可。\n有些时候，不妨让子弹飞一会儿。\n从马克思主义基本原理的角度来说，真正能驱动我们认知水平提高的，绝不仅仅是靠不加选择地大量获取信息，而是要在实践中获取感性认识，利用这些感性认知迭代自己的理性认识。\n换言之，如果题主对很想提高自己的认知水平，与其担心没有了解到必要的信息，不如思考思考如何多实践，在实践中提高自己的理论水平。\n那么，在当下的时代中，我们又应当怎么做才能应对普遍存在的信息过载问题呢？\n我想就是记住一条：实践是检验真理的唯一标准。\n例如现在你就在知乎上看到了很多观点、很多想法，你并不清楚哪些人的观点是有价值的。\n最简单的方法，就是把这些观点记录下来，挨个放在生活中尝试一遍，很快你就知道哪些答主的回答值得仔细拆解研究，哪些答主的理论不具备实践价值了。\n在此过程中，要仔细规划自己的信息获取渠道白名单。\n既然在网络上，高价值信息的总量是在不断上升的，那么我们就应当在信息获取渠道上学会做取舍、做扬弃。\n有些时候，当我们发现特定的信息渠道存在被某些利益原因发布不实信息，或者是只说部分真话的嫌疑时，就应当果断抛弃掉这种信息渠道，不要让这样可能存在问题的信息渠道消耗自己宝贵的注意力。\n这样反复迭代更新自己的信息渠道白名单，坚持半年到一年，我相信很快你就会发现所掌握的信息价值高于大多数同龄人了。如果与此同时辅以恰当的实践，整个人的认知水平也将会上一个台阶。\n如果这些文字让你有所共鸣，欢迎关注交流。愿与君共勉！\n","date":"2025-05-26T00:00:00Z","permalink":"/post/250526-information-overload/","title":"信息闭塞和被信息洪流冲刷，哪个弊端更大？谈错误信息的危害性"},{"content":"大家好，我是硅上观道。这篇文章我们将接着上篇文章谈错误处理，使用交互式输入方式获取用户输入的正确文件路径。\n欢迎查看这个系列的上一篇文章，也欢迎关注这个系列专栏，我将更新更多精彩内容。\n上篇文章发布后，在和一位网友的讨论中，对方建议我考虑在用户输入错误文件路径后使用交互式的方式获取新的路径信息。\n这种方式不仅方便了用户不必重复输入命令调整参数，也使得整个软件的执行逻辑更流畅丝滑。\n我听了觉得很有道理，这就来研究一下怎么实现这个功能。\n事实上，如果只是简单读取用户输入信息，完全可以在不使用第三方库的前提下完成。\n但是，考虑到实现的简洁性、可拓展性以及可视化效果，我决定还是用一个社区内还在维护、文档相对更好的库inquire来实现这个功能。\n这是这个库的项目地址。\n好，那么我先上代码：\nuse anyhow::Result; use clap::Parser; use inquire::Text; #[derive(Parser)] struct Cli { pattern: String, path: std::path::PathBuf, } fn main() -\u0026gt; Result\u0026lt;()\u0026gt; { let args = Cli::parse(); let content = std::fs::read_to_string(\u0026amp;args.path).or_else(|e| { eprintln!(\u0026#34;Failed to read {}: {}\u0026#34;, args.path.display(), e); prompt_for_file_interactively() })?; content .lines() .filter(|line| line.contains(\u0026amp;args.pattern)) .for_each(|line| println!(\u0026#34;{line}\u0026#34;)); Ok(()) } /// Prompts user to enter a file path interactively until successful read or cancellation fn prompt_for_file_interactively() -\u0026gt; Result\u0026lt;String\u0026gt; { loop { let input_path = Text::new(\u0026#34;Enter file path (ESC to cancel):\u0026#34;) .prompt() .map_err(|_| anyhow::anyhow!(\u0026#34;Operation cancelled by user\u0026#34;))?; match std::fs::read_to_string(\u0026amp;input_path) { Ok(content) =\u0026gt; return Ok(content), Err(e) =\u0026gt; eprintln!(\u0026#34;Error reading file: {}: {}\u0026#34;, input_path, e), } } } 你可能会发现相比上一版做了一些比较大的调整。下面，我来详细说说每个调整的位置。\n在深入细节前，请确认安装inquire和anyhow库。\ncargo add inquire anyhow 1. or_else函数和eprintln!宏 这回，我们在文件没办法读入的时候，不是简单地直接上抛错误结束main函数。相反的，我们在这里再加入了一个or_else函数。\n这个函数只有在前面文件读取出现错误的时候才执行。如果的确出错，那么将会执行这两行代码：\neprintln!(\u0026#34;Failed to read {}: {}\u0026#34;, args.path.display(), e); prompt_for_file_interactively() 也即，我们通过eprintln!宏先打印出错误，然后再调用prompt_for_file_interactively()函数。\n这里为什么我们使用eprintln!而不是一般的println!呢？\n在绝大多数操作系统上，有两种输出流，stdout和stderr。其中，stdout一般用于软件的实际输出，而stderr是专门用于打印错误信息的输出流，独立于stdout存在。\n在这里，eprintln!打印的信息就是通过stderr。相比于把错误信息通过println!通过stdout打印，现在这种方式更有利于用户将错误集中保存到文件，或是导入到其他软件进行分析。\n当然，这里的重头戏在于prompt_for_file_interactively()函数。\n2. 交互式获取用户输入新路径 这里，我们将交互式输入部分的逻辑抽象到了prompt_for_file_interactively()函数中，我们来看看这个函数：\nloop { let input_path = Text::new(\u0026#34;Enter file path (ESC to cancel):\u0026#34;) .prompt() .map_err(|_| anyhow::anyhow!(\u0026#34;Operation cancelled by user\u0026#34;))?; match std::fs::read_to_string(\u0026amp;input_path) { Ok(content) =\u0026gt; return Ok(content), Err(e) =\u0026gt; eprintln!(\u0026#34;Error reading file: {}: {}\u0026#34;, input_path, e), } } 我们使用一个loop来让这个循环重复进行。\n首先，我们通过input_path来获取新的输入路径。这里，我们直接使用了inquire库中导入的Text结构体，通过简单的prompt函数就可以直接获取输入的字符串信息。\n需要注意的是，有可能用户不想输入新路径，而是想直接退出。inquire帮我们实现了用户在按下ESC键后退出的功能，可以看到这里我们做了map_error来处理用户按下退出键后触发的inquire::Error(这里的anyhow我们有机会再解释)。\n也就是说，出现这类错误在处理后我们直接上抛，退出loop，直接返回函数。\n反之，如果用户的确输入了东西，那么我们就检查一下这次的路径是否合法。\n如果读取文件无问题，直接返回Ok(content)退出循环和函数；如果还是不对，那就再eprintln!打印出问题，循环重来一遍。\n是不是非常清晰的逻辑？\n3. 查询内容逻辑方面的小调整 此外，我还做了一个小调整，这是原版的代码：\nfor line in content.lines() { if line.contains(\u0026amp;args.pattern) { println!(\u0026#34;{}\u0026#34;, line); } } 这是现在修改后更富有函数式风格的代码：\ncontent .lines() .filter(|line| line.contains(\u0026amp;args.pattern)) .for_each(|line| println!(\u0026#34;{line}\u0026#34;)); 实际上，无论是使用过程式的循环，还是函数式的函数链式调用，只要能完成任务都没问题。这里改成函数式风格，很大程度上只是为了展示这种写法，且我自己更喜欢而已。\n简单来说，这里就是在content.lines()后，filter过滤出符合条件的行，在对过滤后的内容逐行打印出。\n4. 结果 我们最后来看一下这样设计的效果。\n如果直接输入了正确的结果，那么当然是继续能运行的：\n如果是错误的呢？会成为现在这个样子：\n我们可以选择按下 ESC 直接退出：\n如果我们继续输入，可能输入的路径仍然是错的：\n直至我们输入正确的路径：\n好啦，这就是我们第四篇文章的全部内容。\n细心的读者可能注意到了代码中的anyhow库，同时可能也会觉得需要再丰富一下目参数设计、可视化等更高级的功能。这些内容，我们将会在专栏后续文章中慢慢讲。\n希望这篇博客能帮助到你，也欢迎关注交流。愿与君共勉！\n","date":"2025-05-20T00:00:00Z","permalink":"/post/250520-rust-cli-4/","title":"Rust CLI 实战：手搓微型 grep (4) 交互式处理错误文件名问题"},{"content":"完成了最基本的文本文件搜索功能，下一步就是让项目的错误处理更优雅。\n欢迎来到我的 Rust 命令行软件开发入门教程第三篇，如果你还没看过上篇文章的内容，欢迎查看。\n也欢迎关注这个系列专栏，我将更新更多精彩内容。\n1. 初版实现代码拆解 上篇文章结束时，我们实现了grrs的基本功能，给出了第一版实现方案：\nuse clap::Parser; /// Search for a pattern in a file and display the lines that contain it. #[derive(Parser)] struct Cli { /// The pattern to look for pattern: String, /// The path to the file to read path: std::path::PathBuf, } fn main() { let args = Cli::parse(); let content = std::fs::read_to_string(\u0026amp;args.path).expect(\u0026#34;could not read file\u0026#34;); for line in content.lines() { if line.contains(\u0026amp;args.pattern) { println!(\u0026#34;{}\u0026#34;, line); } } } 我们已经详细讨论过和Cli结构体相关的信息，现在，请大家将注意力集中在main函数上。\n在完成参数解析后，我们使用content来储存被查询文件的内容。这个文件读取的过程是怎样的呢？\n注意到，我们调用了标准库中的std::fs::read_to_string函数，并且给这个函数传入了\u0026amp;args.path，也就是刚刚我们读取的文件路径。\n这里的\u0026amp;符号表示的是引用，因为这里我们无需向函数转移args的所有权。这是rust语言的内存管理机制之一，如果还不是很熟悉的朋友可以再查阅一下官方语言教程。\n这里就有个问题：要是用户传入的文件根本不存在，怎么办？显然，这是有可能的。\n比如：\n目前这个版本，是在完成这个函数之后链式调用了另一个函数：.expect()。这个函数的作用是，如果出现了无法读取的路径，立刻崩溃退出程序。显然，这不算是一种足够优雅的错误处理办法。\n2. 理解Result和模式匹配 为能够更好实现错误处理，我们必须理解read_to_string到底返回了什么。实际上，返回的是一个Result\u0026lt;String, Error\u0026gt;结构。\nResult\u0026lt;T, E\u0026gt;是rust语言中一种用于辅助错误处理的独特枚举结构，返回的结果既可以是包含成功值的Ok(T)，也可以是包含错误信息的Err(E)。\n在这里，我们可以使用模式匹配来完成对这个函数结果的处理，比如：\nlet result = std::fs::read_to_string(\u0026#34;nonexistent.txt\u0026#34;); match result { Ok(content) =\u0026gt; { println!(\u0026#34;File content: {}\u0026#34;, content); } Err(error) =\u0026gt; { println!(\u0026#34;Oh noes: {}\u0026#34;, error); } } 就像拆快递一样，模式匹配会拆开Result这个包裹：如果是Ok就取出值，如果是Err就处理错误。如果你之前有过函数式语言的经历，这样的模式匹配语法相信你一定倍感亲切。\n利用这种机制，我们可以稍稍改写一下之前的代码。\n3. 通过unwrap进行错误处理 再rust中，panic!宏可以让程序立刻崩溃并退出。所以，开头我们的代码也可以这么写：\nlet result = std::fs::read_to_string(\u0026#34;nonexistent.txt\u0026#34;); let content = match result { Ok(content) =\u0026gt; { content }, Err(error) =\u0026gt; { panic!(\u0026#34;Can\u0026#39;t deal with {}, just exit here\u0026#34;, error); } }; println!(\u0026#34;file content: {}\u0026#34;, content); 简单来说，就是如果Ok()那么我们继续，如果不幸报Err()那么我们立即崩溃退出。\n实际上，这还有一种简写版代码：\nlet content = std::fs::read_to_string(\u0026#34;nonexistent.txt\u0026#34;).unwrap(); 我们把所有这个代码整合进本来的main函数：\nfn main() { let args = Cli::parse(); let content = std::fs::read_to_string(\u0026#34;nonexistent.txt\u0026#34;).unwrap(); for line in content.lines() { if line.contains(\u0026amp;args.pattern) { println!(\u0026#34;{}\u0026#34;, line); } } } 尝试执行一个不存在的命令，我们会看到：\n和我们预计的一样，程序在读取文件失败后立即崩溃退出。\n事实上，这也就是为什么大家经常看到提醒，生产环境中一定要慎用unwrap()函数。\n除非我们十分确认程序不会崩溃，或者像目前我们的程序一样，崩溃了不会产生太严重的后果，否则尽量不要使用这个函数，还是深思熟虑一下如何进行错误处理吧。\n4. 更优雅的无panic错误处理 你可能会说，那这个unwrap还是立刻崩溃退出，没什么区别啊。别着急，铺垫这么多就是为了最后这一步优化的。\n既然直接退出不好，而结果可能成功也可能有报错，那我们为什么不也使用一个Result呢？\n请看示例代码：\nfn main() -\u0026gt; Result\u0026lt;(), Box\u0026lt;dyn std::error::Error\u0026gt;\u0026gt; { let result = std::fs::read_to_string(\u0026#34;nonexistent.txt\u0026#34;); let content = match result { Ok(content) =\u0026gt; { content }, Err(error) =\u0026gt; { return Err(error.into()); } }; println!(\u0026#34;file content: {}\u0026#34;, content); Ok(()) } 这里，如果说result结果是报错，我们不会崩溃结束程序，而是返回一个Err(error.into())值。反之，则返回Ok(())。\n你可能注意到最后的Ok(())前没有用关键字return。事实上，由于rust中所有由大括号包裹的代码块最后一行都是默认返回值，这里我们可以简写忽略return。\nrust需要开发者对所有有返回值的函数进行详细的类型标注。这里，我们看到返回类型是Result\u0026lt;(), Box\u0026lt;dyn std::error::Error\u0026gt;\u0026gt;。\n这里的Box\u0026lt;dyn std::error::Error\u0026gt;实际上是一种可以存储任何错误的“盒子”，只要该错误类型实现了Error trait。而main函数中的error.into()实际上在做类型转换，将原本的 Error 转换为我们指定的Box\u0026lt;dyn Error\u0026gt;结构。\n如果你不确定这段讨论的细节，没关系，继续往下读，这不影响我们后续的开发。\n事实上，和unwrap一样，我们刚刚通过模式匹配完成的错误处理也有一种简写法：\nlet content = std::fs::read_to_string(\u0026#34;nonexistent.txt\u0026#34;)?; 对，你没看错，只是加了个问号?就搞定了，这个?就是等同于match模式匹配 +return Err(error.into)的语法糖。是不是很方便呢？\n5. 总结 现在，我们将刚刚学会的?上抛错误法整合进项目，你应该得到的完整项目代码是：\nuse clap::Parser; /// Search for a pattern in a file and display the lines that contain it. #[derive(Parser)] struct Cli { /// The pattern to look for pattern: String, /// The path to the file to read path: std::path::PathBuf, } fn main() -\u0026gt; Result\u0026lt;(), Box\u0026lt;dyn std::error::Error\u0026gt;\u0026gt; { let args = Cli::parse(); let content = std::fs::read_to_string(\u0026amp;args.path)?; for line in content.lines() { if line.contains(\u0026amp;args.pattern) { println!(\u0026#34;{}\u0026#34;, line); } } Ok(()) } 我们尝试一个可以成功执行的搜索命令：\n再尝试一个应当报错的命令：\n可以看到，之前一直有的程序崩溃提示，现在就不存在了！在错误处理上，我们向前走了坚实的一步。\n在rust中，?是传播错误传播的惯用方式。如果你之前有过别的编程语言的经验，你应当发现这种错误处理方式比一般的try...catch...finally甚至if err != nil要方便的多，对，说的就是你，golang。\n不知道本文所讲的Result类型和?上抛错误的方式，你是否学会了呢？\n到这里，我们会发现出错以后返回的 Error 是Debug格式展示的，对于开发者来说没什么问题，但对终端用户不够友好。\n下篇文章，我将聊聊如何让命令行应用的输出结果更优美。\n希望这篇博客能帮助到你，也欢迎关注交流。愿与君共勉！\n","date":"2025-05-18T00:00:00Z","permalink":"/post/250518-rust-cli-3/","title":"Rust CLI 实战：手搓微型 grep (3) 更优雅的错误处理"},{"content":"上篇文章讲完了项目的目标和准备流程，本文目标是实现grrs命令行软件的初始版本。\n如果你还没有读过上文，欢迎查看。\n也欢迎关注这个系列专栏，我将更新更多精彩内容。\n1. 安装clap库 首先，我们在处理业务逻辑前，首先要解决命令行参数传入的问题。\n换言之，我们最先要做的，就是正确接收用户传入的搜索模式和搜索文件参数。\n在rust生态中，一个名为clap的库基本已成为实现命令行软件的事实标准。这个库集成了大量命令行软件必备的功能，可以帮助我们快速搭建起一个命令行应用原型。\n首先，为我们的项目安装clap库：\ncargo add clap --features derive 注意在安装过程中需要传入参数--features derive，其原因是clap需要使用派生宏来方便开发者开发。\n如果你目前不知道什么是宏，没关系，这不影响我们的代码实践，完全可以等到对rust更熟悉之后再研究宏的知识。\n你只需要注意，在安装clap时同时安装了派生宏特性即可。当你运行完上述命令后，请检查项目根目录下的Cargo.toml文件。\n其中应该有：\nclap = { version = \u0026#34;4.5.38\u0026#34;, features = [\u0026#34;derive\u0026#34;] } 当然，可能到你看到这个教程时clap已经有了新版本。但是总之，当你看到类似上面的内容时，你就成功为你的项目引入了第一个依赖了！\n2. 命令行参数传入 现在，我们开始使用clap来完成命令行参数传入的工作。\n请你将./src/main.rs修改成如下内容：\nuse clap::Parser; /// Search for a pattern in a file and display the lines that contain it. #[derive(Parser)] struct Cli { /// The pattern to look for pattern: String, /// The path to the file to read path: std::path::PathBuf, } fn main() { let args = Cli::parse(); println!(\u0026#34;pattern: {:?}, path: {:?}\u0026#34;, args.pattern, args.path) } 这些代码是什么意思呢？\nuse clap::Parser; 这第一行的use就是引入了clap库的中的Parser。这个Parser很快就在下文用到：\n#[derive(Parser)] struct Cli { /// The pattern to look for pattern: String, /// The path to the file to read path: std::path::PathBuf, } 这里，我们构造了一个名为Cli的结构体，并对这个结构体使用了Parser派生宏。这个派生宏的作用，很快我们就会看到。\n结构体中有两个字段，pattern和path。\npattern保存的是用户需要搜索的样式，使用String结构；而对于查询文件路径，我们使用了标准库中的std::path::PathBuf来保存，你可以理解为这是一种特殊的字符串。\n这里的PathBuf，在实现上可以兼容不同操作系统的路径名风格（正斜杠、反斜杠），同时还可以和其他标准库 API 进行集成，总之就是很好用。\n最终，我们在main函数中有：\nlet args = Cli::parse(); 你可能会问，这就完了？\n对，这就结束了，就是这么简单。我们所做的只是定义了一个结构体，剩下的由clap库中的Parser派生宏帮我们办完了。\n现在，我们一起看看效果：\n首先，我们尝试不传入任何参数，直接运行项目。\ncargo run 结果是：\n你看这个错误提示，是不是有模有样？注意，这些都是clap直接替我们生成的哦。\n如果我们尝试传入一下参数\ncargo run some-pattern some-path 结果是：\n果然，运行结果 print 出了一行结果，这和我们刚刚代码中写的println!(\u0026quot;pattern: {:?}, path: {:?}\u0026quot;, args.pattern, args.path)预期相符。\n到这里，我们就完成了传入命令行参数的准备工作。\n其实，clap还有很多高级功能可以探索，可以帮助命令行软件实现丰富的功能。如果大家需要，欢迎评论区告诉我，我可能后续会在专栏更新更多clap库的进阶功能。\n3. 实现grrs的第一个可用版本 现在将main.rs修改成如下状态：\nuse clap::Parser; /// Search for a pattern in a file and display the lines that contain it. #[derive(Parser)] struct Cli { /// The pattern to look for pattern: String, /// The path to the file to read path: std::path::PathBuf, } fn main() { let args = Cli::parse(); let content = std::fs::read_to_string(\u0026amp;args.path).expect(\u0026#34;could not read file\u0026#34;); for line in content.lines() { if line.contains(\u0026amp;args.pattern) { println!(\u0026#34;{}\u0026#34;, line); } } } 很简单吧？我们只是稍微修改了main函数中的内容，加入了一些简单的业务逻辑。现在，让我们一起看看这段简单的代码效果如何。\n此时，我们输入如下命令：\ncargo run -- content ./src/main.rs 命令中的--用于隔开cargo自己的参数和我们创建的命令行软件的参数。\n你应当看到我在第一篇文章开头，我们展示的结果：\n如果我们尝试传入一个错误的参数，比如传入一个不存在的文件名：\ncargo run -- content false-file-path 此时你应该看到：\n说实话，这不是一个很完美的实现，错误处理这块儿还是有瑕疵的。但是起码现在这个代码看起来能用，还要什么自行车！\n你可能还是很好奇，这段代码是如何实现这样的功能的，我们在main函数中添加的代码细节具体有何作用，为什么我说现在的错误处理不够好。\n受篇幅限制，我无法将所有细节全部放在这篇文章中。这些问题，我们下回再详细解释吧！\n希望这篇博客能帮助到你，也欢迎关注交流。愿与君共勉！\n","date":"2025-05-17T00:00:00Z","permalink":"/post/250517-rust-cli-2/","title":"Rust CLI 实战：手搓微型 grep (2) 实现软件第一版"},{"content":"CLI 工具是 rust 非常擅长的一个领域。从这篇文章开始，我将带大家从零开始做一个简易 grep 命令行工具。\n本专栏主要参考了该教程，也欢迎关注这个系列专栏，我将更新更多精彩内容。\n1. 目标 我们的目标主要是为了能够实现一个简易的grep命令行工具。\n英文教程原作者给这个软件起名grrs(读作 grass)，那么我们也叫这个名字吧！\n简单而言，这个软件可以对纯文本文件的内容进行查询。\n我们希望实现的基本内容查询命令是：\ngrrs.exe \u0026lt;PATTERN\u0026gt; \u0026lt;PATH\u0026gt; 也就是说，我们需要传入一个搜索模式，和一个被搜索文件的路径。这样的设计，和grep的原版设计是一样的。\n例如，我们可能想要查./src/main.rs文件中包含的content变量名的内容：\n这就是我们第二篇文章全部完成后将达到的效果，是不是很清晰呢？\n也就是说，读完本专栏头两篇文章，就可以实现grrs最初可用原型。\n2. 前置知识 这个系列教程假定读者是掌握其他编程语言，但是对 rust 不够熟悉的开发者。我将尽我所能地介绍一些 rust 独有的编码风格和语言特性，帮助对 rust 特有机制不够熟悉的朋友理解这个语言。\n当然，如果你在阅读过程中遇到任何问题，欢迎在评论区留言，也可以查阅 rust 语言的官方入门教程。\n在开始我们正式讲解前，你需要准备的是可以在本地运行的 rust 环境。本文写作时使用的 rustc 版本为：\nrustc 1.87.0 (17067e9ac 2025-05-09) 我建议读者在运行本项目的代码时还是使用最新的 rust 工具链版本。即便 rust 未来有了新的变化，这个项目的代码基本可以肯定还是能够继续运行的。\n3. 新建项目 rust 的工程管理工具是 Cargo。这个工具基本集成了 rust 开发全部所需的周边支持基础设施，包括包管理、代码格式化(fmt)、测试(test)、代码风格检查(clippy)等，总之就是非常好用。\n我们新建一个项目：\ncargo new grrs cd grrs 进入目录后，我们可以看到 cargo 为我们建立的文件夹结构是：\n./ ├── .gitignore ├── Cargo.lock ├── Cargo.toml └── src/ └── main.rs 这些文件的作用是：\n.gitignore：帮助 git 理解需要忽略哪些文件和文件夹，不被计入版本管理 Cargo.lock：Cargo 自动生成的详细依赖信息，不需要开发者阅读理解 Cargo.toml：开发者自己维护的项目元数据和依赖信息。用好这个 toml 文件，就可以对项目的各种依赖库进行轻松的增加、删除、升级等操作 src文件夹：一般源代码放在这个文件夹下 main.rs：Cargo 自动生成的 hello world 代码文件。注意 rust 代码后缀是.rs 用你最喜欢的代码编辑器打开main.rs，你应该看到如下内容：\nfn main() { println!(\u0026#34;Hello, world!\u0026#34;); } 让我们运行一下这个初始项目：\ncargo run 你应该能够看到如下输出结果：\n如果你的程序正常输出了\u0026quot;Hello world!\u0026quot;，那么恭喜你，你的环境配置成功，你已经具备进入正式工程的全部基础了！\n下一篇文章，我们将一起实现这个 CLI 工具的初始版本。\n希望这篇博客能帮助到你，也欢迎关注交流。愿与君共勉！\n","date":"2025-05-16T00:00:00Z","permalink":"/post/250516-rust-cli-1/","title":"Rust CLI 实战：手搓微型 grep (1) 新建项目"},{"content":"这主要是为这个知乎问题写的回答。\n选择多方资料对比中学习，这是编程技术学习中的核心技能之一。\n不知道有多少人在自学时被同济大学的那版线代教材困扰过。\n我不否认这是一本数学上严谨的教材，事实上，当我们学完线性代数拿来复习一遍会觉得这本书还是很精准凝练的。\n但从自学的角度，这本教材的确不太适合，特别是对于零基的学生。\n事实上，很多时候学不明白一个知识点，一种新技术，不是因为你不够聪明，而是自学材料找的不对。\n那什么是对的材料呢？\n这个问题的答案对于不同的人而言是不一样的，因为每个人的学习方式不同。\n比如，我自己就非常喜欢图文教程，视频教程我坚持下来的比较少。\n我自己总感觉视频有时节奏很慢很无聊，有时又太快跟不上。\n但我有朋友就特别喜欢视频教程，而且十分熟悉视频教程的教学节奏，能够在正确的时候选择正确的视频倍速。\n所以，对于你而言，我的建议就是在最开始就找几个不同媒介的教程，包括网络图文教程、视频教程、纸质书甚至播客等。\n把这些材料全部摆一起，每种材料都学一个小时，很快你就知道你适合什么类型的材料了。\n其他不喜欢的教程也别丢弃。\n你最喜欢的那版教程或许中间某个章节质量一般，这时候参考一下其他教程同章节内容，或许就能豁然开朗。\n总而言之，就是如果花了不少时间学一个知识点仍学不明白，这时候就该果断看看别的材料，别死磕一个教程不放。\n至于你问的买课方法，除非你现在有极大的就业压力，不得不在半年或一年的时间从零基础走到能就业，否则不建议轻易买课。\n绝大多数技术，特别是入门级知识，官网就有详细的技术文档。此外，社区内也有丰富的免费图文、视频教程。\n如果你打算长期从事这个行业，我建议从自己查找资料教程开始，毕竟查找资料和自学的技能，就是计算机领域的核心技能之一。\n哪怕最后你决定还是要买课，选择的课程也一定是经过了多方对比后深思熟虑的结果，而不是在一时冲动下做出的选择。\n最后，还是附几个我自己入门时很喜欢的教程吧：\n如果你想学 python 数据分析，推荐 Kaggle 的教程。你可以从 python 基础、数据可视化一路学到现在最火的人工智能，还不用自己配置环境 https://www.kaggle.com/learn 如果想学前端做做网页，建议看 MDN 的 html, css, javascript 前端基础语言入门，学完之后自然就知道该去学什么框架了 https://developer.mozilla.org/zh-CN/docs/Web/HTML 上面这两个属于学了立马就能画画图、做个网页，特别适合入门的方向，主要是见效快、成就感较高。如果想深入其他领域，推荐一个路线图网站，详细总结了各个领域学习的路线图 https://roadmap.sh/ 最后，如果有问题，当然可以继续来知乎提问。如果英语不错，也可以看看 Stackoverflow 上的回答，这上面的高赞回答一般质量都很好 https://stackoverflow.com/ 希望上述内容能够对你有所启发，编程愉快！\n","date":"2025-05-15T00:00:00Z","permalink":"/post/250515-resource-aquiring/","title":"学计算机有什么好的获取学习资料的方法？掌握高效的资料收集技巧"},{"content":"这篇文章是针对这个知乎问题的回答。\n内容分发网络，也即CDN(Content Delivery Network)。\n如果你看过云服务商的介绍，对这个技术，可能会看到类似于网站访问全球加速、边缘计算等高大上词汇。\n你可能会看到案例，告诉你 CDN 可以将原先访问速度在秒级、几百毫秒的网站，直接降低到几十毫秒，用户无感的程度。\n还有些时候，你会看到宣传，说 CDN 帮助网站挡住了海量 DDoS 网络攻击，大幅降低网站运行成本。\n你觉得这样的技术高级不高级？\n可如果我说，这项技术其实就是把网站文件复制个几十几百份，放在全国/全球各地呢？\n内容分发网络的兴起，本质上是因为许多网站上的信息以静态资源为主。\n所谓静态资源，主要指网站上的图片、视频，以及网页本身的 html、css、javascript 文件。\n这些文件中的大部分，特别是图片视频这种类型的，在上传到网络上后基本不需要再做后续更改，所以被称为静态资源。\n既然是静态资源，那为什么还一定要强求纽约的用户发送的网络请求飘洋过海到北京的服务器呢？我们直接把资源摆在用户家门口岂不是方便很多。\n你看，这就是所谓内容分发网络的原理，本质上就是复制黏贴式的缓存。\n同样的静态资源在全球各地多复制几份，用户请求获取资源的时候，根据用户访问的 IP 地址，从最近的位置的缓存节点调配资源。这的确能起到加速网站加载速度的效果。\n也就是说，你现在看到的这个问题下面图文并茂的回答，说不定获取网页、图片的网络请求根本没离开你所在的城市，这才让你在刷知乎的体验如此丝滑~\n虽然说穿了 CDN 就是复制黏贴，但是如果我们真的深入进去来看其实这项技术往深了看还是可以很复杂的。\n比如，在某些特定的场景下，你访问距离你最近的通信链路刚好就很拥挤，反倒是看起来离的更远的某个边缘节点访问速度更快。\n面对这种情况，这就要求 CDN 厂商需要有更复杂的调度策略，对于实时网络质量有着通盘了解，调配正确的节点给正确的用户。\n再比如，虽然说是静态资源，有些时候这些资源还是会发生改变的。\n这时候 CDN 需要在上游资源发生变化的时候及时反映这种变化，确保用户能够看到正确的内容，这就对需要 CDN 厂商对资源缓存时间要有更精准的控制。\n此外，随着现在技术水平的发展，边缘节点能做的事情不仅仅是加载静态资源，某些原先认为必须要由中央节点完成的后端逻辑，现在也可以下放到边缘完成。这其中代表服务包括 Cloudflare Workers, Lambda@Edge。\n边缘计算的概念，随着时间的推移，正在逐渐超脱出原先简单的含义，变得愈加丰富精彩。\n这么看来，这个简单粗暴的技术，其实真要说有些技术含量，多少还是有点道理的嘛。\n好，现在你知道 CDN 的原理了——它就像在每条街上克隆一家连锁超市。但千万别告诉你的老板，他每年花几百万买的“全球智能加速”，实际是多买几台复印机。\n注：图片源自网络，如侵权请联系删除。\n","date":"2025-05-14T00:00:00Z","permalink":"/post/250514-cdn/","title":"有哪些看起来很高端的技术其实原理很暴力很初级？浅析CDN内容分发网络原理"},{"content":"这是我对一个知乎问题的回答。\n简单来说，学习编程可以带来理论与实践紧密结合的机会，解决实际问题的能力以及深入参与开源社区的基础。\n1. 理论与实践的紧密结合 编程这项技能，或者再推广一点来说，计算机这门学科，几乎是整个理工科领域最容易把理论和实践紧密结合的领域。\n原因很简单，编程的实践成本远远低于其他学科。\n学生物化学，想实践必须要有专业的实验室；学机械工程，想实践也得有专门的仪器和设备。计算机呢？不论是研究基础理论还是做偏向工程的实践，有台笔记本电脑就可以运行代码，可以随时随地进行探索。\n由于这个领域的实践门槛实在是很低，主流的编程语言和技术方向往往在网络上有着大量的教程以及疑难解答。想要在入门之后进一步精进自己的能力，我们会发现在网络上有太多公开资源可以参考。\n换言之，学习编程不仅可以让我们立刻实践，还可以在未来进行更深入地实践。\n理论与实践紧密结合的好处，主要在于能够给我们带来更快速的反馈。积极的反馈可以帮助积累信心，消极反馈同样能促进我们及时调整方向，小步快跑，敏捷试错。\n正如教员在《实践论》中阐述的认知规律，编程完美契合理想的以实践驱动的认知迭代过程。\n也就是说，编程允许我们通过反复的实践，快速获得对于一个复杂问题的感性认知；在感性认知大量积累后，运用我们的逻辑思维，就可以实现从感性认知到理性认知的质变；最终，我们运用新的迭代后的理性认知去指导实践。\n这样的“实践——认识——再实践”的循环多完成几次，螺旋上升，回望曾经的自己，大家一定会惊叹自己获得的知识和进步远超预期。\n题主在问题描述中提到已经在 python 爬虫、游戏以及数据科学方向有所实践和探索，我想可能题主对于这个问题已经有了些体会。相信通过进一步的实践，这样的体会将会更加深刻。\n2. 解决实际问题的能力 编程技能不仅可以通过紧密结合理论与实践的方式，实现能力和信心的快速提升；还可以培养解决问题的能力，拓宽我们改造世界的边界。\n具体来说，就是很多时候碰到生活中的一些问题，具备和不具备编程能力往往在结果上是截然不同的。比如，就拿题主提到的 python 数据分析来说，当我们在生活中碰到一些复杂数据需要处理时，掌握编程后就可以将我们从枯燥的重复操作中解放出来，显著提高整个流程的自动化程度。\n我作为一个业余的天文爱好者，之前参与过一些公众科学活动，简单来说就是请志愿者肉眼对天文望远镜拍摄的照片进行分类，寻找之前未发现的小行星。\n听起来挺有意思对不对？可是当观察数量变多时，起初再有意思的事情都会变得无聊起来。后来我一想，既然自己人工分类的数据最后很可能就是为了帮助其他科研团队训练自动化分类 AI，为什么我自己不能尝试训练一个呢？\n说干就干。我很快根据哈勃望远镜的公开数据，通过微调 ResNet50 模型，实现了一个从自动下载图片到小行星识别的端到端流水线。\n两年前做的这个工作，现在看来，技术含量其实不高，纯粹就是自己做着玩，但是我的确是实际解决了人生中的一个切实存在的问题。解决这个问题，可能在学术上算不得什么，但却让我获得了莫大的成就感。\n举这个例子，主要其实还是想说明学会编程会给我们一种拓宽了能力边界和对世界影响力边界的感觉，编程本身就是一个能力杠杆，帮助我们撬动更大的社会资源。\n不知你是否感受到这其中独特的浪漫感呢？\n3. 参与开源社区的能力 我最后想提的一点就是开源社区的问题。\n学会了编程，不论语言、不论经验水平，你会发现自己就初步拥有了直接参与到开源社区的能力。\n开源社区的特殊性，就在于你可以轻易在这里看到行业内最优秀的产品，最优秀的实践方案。更重要的是，你可以直接和行业内众多大佬前辈直接交流合作。\n之前有不少朋友向我吐槽学校里的教授很难给他们的编码思维和习惯带来有针对性的指导，很多时候作业实验完成了之后，能力提升还是要打个折扣的。\n我不知道有多少朋友在大学里有这样的感觉。对于这种问题，我的统一解决方案就是：去开源社区。\n在选择了合适的社区和问题解决后，我们往往会发现，社区内真的有不少人愿意带带新人——前提是你在问题上有了充分的思考和研究。\n开源社区在很大程度上就是一些人用自己的时间换取别人的时间。只要你有心，付出了足够多的时间尝试解决一个问题，给出了你尽最大努力得到的解决方案，很多时候是会有人愿意拿自己的空闲时间认真审阅别人的代码的。\n在参与开源的过程中，我们不仅能得到给知名项目做出贡献的获得感，更重要的是有行业专家对我们的代码进行审阅、分析和反馈。\n这种即时获取高价值反馈的特性，恐怕是其他学科很难达到的，可以说是开源社区独有的特色。\n回到本文第一条提到的理论，那就是参与开源社区贡献可以从单个实践工作中能够得到海量的感性认知反馈，一次实践胜过别人数次实践。在此过程中，我们对于技术的认知水平必将获得更大幅度的提升。\n可以说，学习编程，掌握计算机科学和工程带来的思维方式，不断进行实践，这一系列工作深刻改变了我自己，在思想上和实践上的益处已足够让我受益终生。\n如果这些文字让你有所共鸣，欢迎关注交流。愿与君共勉！\n","date":"2025-05-12T00:00:00Z","permalink":"/post/250512-coding/","title":"学习编程可以为自己带来什么？- 理论与实践的紧密结合"},{"content":"大一学年即将结束，坐在图书馆回顾这一年，从对计算机科学的懵懂好奇到如今能独立完成开源贡献，这段旅程充满了挑战与惊喜。今天，我想分享这一年在技术领域的探索与成长，希望能给同路人一些启发。\n我将会从课程学习、课外参加的活动以及其他技术细节几个方面分别聊聊我的思考。\n课程学习 大一暑假通过学校考试，我选择直接跳过了基础课程，所以我大学的计算机课程是从计算机组成原理开始的。这门课程知识跨度很大，既有对于 C/C++ 这种高级语言内存管理思路的讲解，也有对于汇编语言乃至 CPU 电路等更底层原理的分析，总体上还是很有挑战性也很有趣的。这门课让我第一次真正理解了从高级语言到硬件执行的完整链条，那种“原来计算机是这样工作的！”的顿悟感令人难忘。\n这门课程一项令我印象比较深刻的实验是在模拟软件中设计一个电路，可以使 CPU 能够额外支持一种双精度加法命令，单周期完成双精度加法运算。这个任务本身其实难度不大，选取合适的逻辑门组合，处理好 flags 就可以比较轻松地完成，但关键难点在于拓展指令集后需要对寄存器、ALSU 等多个不同位置进行同步修改，需要十分细心才能将整个系统逻辑梳理畅通。\n此外，上学期我在选取的一门物理课中锻炼了一下写 Mathematica 的能力，见到了不少之前从未用过的命令和技巧。\n下学期，我选了门数据科学入门课程。由于高中的时候做了很多相关工作，这门课程介绍的许多概念之前就接触过，总得来说我感觉难度不算太大。这门课程的主要特色在于介绍了很多数据科学领域算法的数学原理，做了些比较复杂的数学推导，这让我对于线性代数等数学工具有了新的认识。这门课程的实验主要任务就是把课上讲的数学原理转化成程序语言，但同时也能看到教授在引导大家思考如何用更科学合理的方式去组织架构自己的代码，如何将复杂功能拆分成易读易维护的函数和类的组合，如何在实现算法时尽可能降低时间复杂度，总之收获还是很大的。\n这门课程的期末大作业其实只是要求选取课程中讲到的两三种算法应用在一个学生选取的数据集上，但我和同组的同学商量了一下主动加了点难度，决定探索一下业界结构化数据监督学习常用的 XGBoost 和 LightGBM 算法。我们也尝试参与了 Kaggle 上的一个 Playground 级别的比赛，尽管首次参赛成绩很一般，但也通过社区学习了不少他人的特征工程思路，开阔了视野。\n课程以外的活动 除了上课，这一学年中我也利用课余时间参与了一些相关活动，收获了不少新知识、新技能、新感悟。\n组队参加了周边几所学校联合举办的 Hackathon，并获得了不错的成绩。从上学期了解到这一比赛后我就开始着手组建队伍，同时开始储备项目想法和相关技术，这些准备工作对于获得最终的结果是有很大帮助的。\n产品想法其实很常规，就是设计一个校园活动信息聚合网站，算不得什么惊世骇俗的点子；但是我们在产品内整合了一个通过 Ollama (一个本地运行大模型的工具) 部署的 DeepSeek-7B 模型，并借助大预言模型实现了活动信息获取的自动化。尽管这个 AI 功能虽然只是最低限度的整合，但这一项亮点还是获得了评委的高度评价。此外，我主要使用的技术栈其实就是 Django 全栈，主要原因是我对 Python 最熟悉且整合 AI 比较方便。这个技术虽然在业界基本被淘汰了，但对于 Hackathon 这种需要极快速度完成原型设计、展示核心功能的场景下还是很好用的。\n这个活动给我的最大启发，就是平时还是要多动手实践。通过 Ollama 在本地部署模型这个技术之前纯粹是出于兴趣探索了一下，也是出于好奇研究了 Ollama API 的调用文档，没想到这些平时无心的积累在这种极短时间周期的 Hackathon 中起到很大的帮助，给我在短时间内实现自己想要的功能提供了信心和技术基础。\n学习了 rust 语言并初步参与了 Apache Datafusion 开源项目。如果说最开始学习 rust 纯粹是赛博追时髦 (总不能是因为面包蟹 logo 太可爱吧)，那么后来我这个 C++ 小白犯了各种内存管理错误被 Valgrind 反复军训，这之后再学 Rust 就开始逐步理解这个语言为什么被设计成这么特殊的样子了。\n在学了一段时间 rust 感觉自己掌握了基本语法和概念后，我当即打算尝试看一些开源项目，也通过这个机会开始学习如何参与到开源贡献中。当时也是初生牛犊不怕虎，我直接选择了 Apache Datafusion 这个比较热门的 rust 数据库项目，挑了个带有 \u0026ldquo;good-first-issue\u0026rdquo; 标签的问题就开始研究。研究了一段时间后发现虽然说整个项目代码有几十万行，但是如果定位清楚问题根源后其实相关代码不过几百行，解决起来其实没有想象中那么难。我花了点时间研究清楚了社区合作的规则，提交了 PR 并最终合入代码主分支。当我看到自己提交的代码被合并进主分支时，那种为全球开发者使用的项目做出贡献的成就感，是任何课程作业都无法比拟的。\n其实我觉得 Fust 是一门非常适合开源协作的语言。一方面，Cargo 作为一种极其优秀的包管理工具，极大程度简化了代码环境配置的复杂度，新的开发者往往只需要几步命令就可以让项目跑起来，提交 pr 前为确保代码质量跑的 nextest、fmt、clippy 等检查工具链也非常好理解和操作；由于语言本身具备很强的类型系统和严格的生命周期检查机制，只要写的代码能够编译、能通过各种测试，大概率就不容易出现一些隐藏错误，社区内的其他开发者往往只要审阅代码逻辑准确后就非常乐意接受新手贡献的代码。这两点基本可以确保我们在开始时不会卡在某个难点过不去，能帮助大家比较快速地开始做实际的贡献并建立起信心。\n参与了一个数据科学比赛，跟着大佬拿了个奖。今年三月参加了一个地区级的数据科学比赛，主要分析了一个房地产行业企业提供的海量数据中的趋势。这个其实我自己的贡献会相对小一些，主要还是跟着上学期计组课认识的一个大三学长见见世面。我负责的工作除了做了些 EDA 和数据清洗，还有就是写了个脚本调用 API 将数据中的街道信息转换成经纬度，使得数据更好进行分析和可视化。\n其实这场比赛主要是大三学长完成的统计学模型对于最终获奖起到了关键性作用。这场比赛给我的最大启发，就是想要做好数据分析和数据解释，必须学好数学，具备坚实的统计学基础。我打算在未来几年里多选点统计学课程。\n其他 上述这些算是完成的比较重要的工作，但除了这些还有一些零星的事务和感悟：\n梳理通顺了自己的环境管理工具链。我在自己的 Windows 笔记本上通过 Scoop + WSL 这个组合实现了比较丝滑的各种代码环境管理。此外，引入了 uv 这个工具管理 Python 项目（uv 是真的香）； 进一步优化了笔记系统。进入大学以后自学的时间更多，自学内容更广更深，这时候就需要通过更系统化的笔记系统把信息沉淀下来，最终转化成知识体系化为己用。这一年里，我进一步优化了自己的笔记系统，将 Obsidian 电子笔记和手写纸质笔记相结合，比起前一年信息向系统化知识转化率大大提升； 读书，特别是读毛选。除了学习研究技术问题，我还读了不少政治、经济、哲学方面的书。其中毛选虽已不是首次阅读，这次读来许多以前不太理解的问题茅塞顿开，并开始能够逐步将实践论、矛盾论等文章中描绘的马克思主义基本原理应用在生活中； 近期报名参与了腾讯组织的远程支教，给一群三年级的小朋友们聊聊人工智能。这是一段很有意思的体验，把一个比较复杂的问题将给没有任何前置知识的小朋友们这件事的难度，不见得比学习复杂技术本身更简单。看到他们中不少同学眼睛发亮的样子，我意识到技术传播的乐趣。 我想比较值得一提的事情恐怕就是上面列举的这一些了。一年过去，虽然说我的能力提升道路仍然任重道远，但我想无论如何还是取得了一些成果和进步的。\n这仅仅是我的起点，前方的技术海洋更加广阔。如果你也对计算机科学充满热情，欢迎关注我，愿与君共勉！\n","date":"2025-05-11T00:00:00Z","permalink":"/post/freshman-summary/","title":"长文 - 大一技术成长复盘：课程、竞赛与开源之旅"},{"content":"1. 背景 身处 AI 大语言模型高速发展的今天，各类 AI 工具正在深刻改变我们学习新知识的方式，尤其是代码相关技术的基本学习流程。以往我们在遇到未曾见过的技术时，往往需要借助搜索引擎、官方技术文档、StackOverFlow 论坛等多种工具来学习新知识或是解决问题，对个人的检索能力、分析判断能力、信息整合能力要求较高，并且频繁在代码编辑环境和浏览器之间切换容易给人较强的割裂感。GitHub Copilot 的出现对原先的代码学习和编写工作产生了革命性的影响，新时代的代码学习和工作变得更为简单，人们可以直接在代码编辑器、IDE 中实现和 AI 对话、AI 代码提示等功能。\n然而，GitHub Copilot 本身是付费使用工具，使用受到限制。是否存在可以免费使用的同类工具提高我们学习的效率呢？答案是肯定的。CodeGeeX - A Multilingual Code Generation Tool是一个对标 GitHub Copilot 的国产工具，模型本身基于 ChatGLM2，使用昇腾 910 高性能 AI 计算集群进行训练。该工具针对个人用户完全免费，支持 VSCode、IntelliJ IDEA、Pycharm 等主流 IDE(Integrated Development Environment)，可以作为个人学习的良好辅助工具。\n下面，我们将会介绍工具的安装方式和基本使用方法。\n2. 安装 以下安装过程以 VSCode 为例，其他 IDE 的安装过程基本类似。\n在 VSCode 插件商店中搜索 Codegeex：\n安装该扩展：\n安装完毕后，会发现左侧出现了 CodeGeeX 的图标。点击图标，插件提示需要到官网完成账号注册和登录。完成登录后，就可以开始使用了：\n3. 使用方法 3.1 直接和 CodeGeeX 对话 选中一段代码，我们发现这段代码自动出现在了提问区域。我们提出一个问题。这是一个例子：\nCodeGeeX 的回答是：\n总体来说回答还不错，解释清楚了这一小段代码的功能。\n3.2 让 CodeGeeX 自动生成代码注释 有些时候我们拿到一段别人的代码，或者是自己很久以前写的代码，很可能看不明白当时编写时的代码逻辑。我们可以让 CodeGeeX 自动生成注释，提高代码的可读性，帮助我们理解。\n选中一段代码，右键使用 CodeGeeX 自动生成注释：\n需要我们选择注释的语言类型，这里我们选择中文试试：\n结果如下：\n3.3 根据代码提示，直接让 CodeGeeX 编写代码 我们在注释中给出一段简单的 python 脚本编写需求，甚至可以做到一行代码不敲的情况下，让 CodeGeeX 自主生成代码。\n首先，我们在注释中写明需求，需要脚本自动生成一个 20 行的 txt 文件，每行一个在 0 到 1 之间的浮点数。CodeGeeX 提醒我们首先导入 random 库，符合预期。然后，开始写入 txt 文件：：\n生成 for 循环：\n最终完成代码：\n我们执行该脚本，结果符合预期：\n在实际工作中，CodeGeeX 的代码提示以灰色占位符的形式出现，随着我们不断输入代码、文字信息动态生成，如果合适可以按Tab键采纳；不合适的情况下可以继续从键盘输入内容，不会影响到我们最终的代码。\n4. 总结 以上就是对 CodeGeeX 基本使用方法的介绍。借助直接嵌入代码编辑环境的 AI 工具，我们可以将整体工作流程调整得更加高效、流畅，更好地学习新技术、处理代码中的 bug 等。掌握先进生产工具的使用方法，站在巨人的肩膀上，相信你一定可以走得更远。\n","date":"2023-11-05T00:00:00Z","permalink":"/post/ide-ai-tool-rec/","title":"推荐一个IDE中的AI工具 - CodeGeex插件和AI辅助编程"},{"content":"所谓“不朽” 长期以来，“人生的意义”一直作为我们面对的终极问题。在浩渺宇宙面前，人生如此短暂，那么人类又为何而存在？生命有何意义和价值？\n太上有立德，其次有立功，其次有立言，虽久不废，此之谓不朽。\n生命是短暂的，任何有机体都不可能实现物理意义上的不朽，这是自然的铁律。然而，《左传·襄公二十四年》中提出的“三不朽”，给予了我们在另一种意义上实现“物于我皆无尽也”的可能性。“立德”“立功”或许对我们普通人来说，有些可望而不可及；但是“立言”则是我们每个社会成员都可以尝试的途径。\n本站的建立，某种意义上是我对“立言而不朽”的初步实践，也是对知识共享、技术开源的互联网精神的贯彻。当然，将知识整理分享的过程本身就很有趣，足以充实心灵。\n涵盖领域 网站更新的文章将主要聚焦于以下若干领域：\n哲学：研读哲学最大的成功，莫过于能够以平时的语言传递原本晦涩的思想，用凝练的文字概括人类历史上最璀璨的哲思。在未来，我希望能将道家哲学、存在主义哲学、马克思主义思想等流派的核心思想和发展脉络展现在读者眼前； 技术：记录技术学习中遇到的问题和解决方案，思考人类信息技术的发展方向。现在我掌握的技术，主要聚焦于信息爬取、数据挖掘和分析、深度学习等领域； 天文：庄子云，“天地与我并生，而万物与我为一。”了解宇宙，研究在时间和空间上最宏观的自然现象，往往能够帮助我们从更高层面看待我们的人生和这个世界。未来，我将会对天文学发展史、现代天文学重大发现等话题作详细介绍，为科普事业贡献绵薄之力； 人生：最后，这也将会是我和诸位深入交流人生思考的一个场所。人生的意义何在？如何处理个体与世界的关系？怎样才能更好地管理自己的人生？这些问题或许我们永远无法得到确定的答案，但讨论本身便是意义所在。 下一步更新计划 聊聊加缪，以及存在主义哲学的核心思想 王德峰：马克思的哲学革命及其当代意义课程学习笔记 如何构建一套属于自己的知识管理系统 这是一篇旧文章，现在对同一个问题的感悟和思考都有所不同，这个博客网站的发展方向也是和最开始规划不太一样的。希望新的内容能够帮助到你。\n","date":"2023-11-04T00:00:00Z","permalink":"/post/knowledge-exchange/","title":"关于本站 - 一篇旧文章，我对本站内容更新计划的最初想法"},{"content":"导言 齐物论，有“齐物而论”的含义。以我的理解，所谓的齐物而论，便是在认识到“物”与“我”有所区别的基础上，将万事万物齐而论之，不去纠结是非对错，从对立中看到统一，看破事物的表象，从而达到“天地与我并生，而万物与我为一”的境界。很多人对“齐物“这二字很熟悉，但往往没有深入了解过庄子内篇《齐物论》这一章。要想比较全面的了解庄子思想，非读《齐物论》不可。\n齐物篇上承《逍遥游》，下接《养生主》，可以称得上庄子内篇的核心枢纽。逍遥游从鲲鹏出发，以一种宏大的视角展开论证，最终给出“至人无己，神人无功，圣人无名”的论断，可以认为是提出了总体目标和纲领——人生的终极目标在于追求逍遥。接下来的问题是：到底如何追求逍遥？对于庄子来说，逍遥的真正要义是“知我”，即知道什么才是真正的自我。如若想明白了“我是谁“这个哲学终极问题，便算是初步达到了”逍遥“。《齐物论》此篇，便是在试图解决“我是谁”这个难题，提供了一系列具体的认识论和方法论。当然，这一终极问题的解决不是一蹴而就的，所以当我们窥见”我“的真正面目时，就需要“葆光”，也就是在“知我之所在”后“养我之所在”——这是在《养生主》中讨论的核心问题。由此可见，“齐物”的思想在庄子整体思想中居于极为重要的地位。\n在这一篇文章中，我想具体谈谈我对这一篇文章的理解，不求一览《齐物论》全貌，只是希望能够从古老的文字中，从哲学层面回答当今世界的一些问题。《庄子》内篇通读数遍，但每次重读都有新的感悟和体会，不敢说一文说尽如此璀璨的思想。至于本文是否能够对解决“我是谁”等等终极问题有所帮助，是否能为解决现代人思想困境提供一些启示，则留给读者自行评判。\n1. 庄子的宏愿：逍遥 有很多人都认为庄子的哲学是极端消极的，给人的感觉就是“清静无为”，缺乏拼搏前进的活力。一直以来，我对这一观点都感到十分困惑。我们不可否认庄子在论述中时而出现消极出世的思想，但是从最经典的鲲鹏章、庖丁解牛章以及庄子作此鸿篇巨著行为本身，我都没有看到极端消极的形象。\n鲲鹏一章之所以能够放在整本书的最前面，自然有其独特的意义，我们在深入分析《齐物论》之前有必要先来思考一下所谓“逍遥”。“逍遥”这个词，古往今来都是人们追求遐想的目标，对于快节奏生活的现代人来说更是如此。人们当然想要像鲲鹏那样自由自在地翱翔与九天之上，大胆地从北冥飞向南冥，却往往忽视了寓言背后暗含的道理。鲲鹏的逍遥，是建立在长期积累下，厚积薄发、量变产生质变的逍遥，仍然要借助风势水势才能够达到。当我们在追求自己的逍遥人生时，同样需要像鲲鹏那样的勇气与毅力，最为关键的是，我们要清楚地明白自己想要什么——就像“我想要去南冥”一样。换言之，我们要全面的了解自我。\n2. 问题的起点：“吾丧我” 今者吾丧我，汝知之乎？\n知其然不代表知其所以然，生存与人世间不代表真正懂得自己的目标和价值。作为表象的“吾”，人生来就有；而作为本质的“我”，或许直至生命的终点也想不明白。我们现在常说的“现代人的精神危机”，核心矛盾就在于祛魅后的人类精神世界缺乏可靠的自我认知，缺乏实现个人价值的标准答案。有趣的是，尽管庄子所在的时代并不是祛魅后的现代，但当时儒道释等后来通行的流派尚未发展充分，人们的精神需求在动荡的时代中同样得不到满足，使得我们与庄子的处境在某种程度上呈现出一种相似感。\n尽管我们明白，想要逍遥就一定要想明白“我是谁”，但是人们又是天生丧“我”，不免让人感到备受打击，发出“吾丧我”的哀叹，但我们也应当用辩证的视角来看待这一问题。尽管人生来并不知道“我”的本质，人生本无意义，但同时也给予了我们创造自我价值的空间，保存了生命的无限可能。从存在主义的观点来看，“吾丧我”恰恰是绚烂人生的起点，而不是我们消极厌世的理由。单就这一层面上来说，中西方的唯心主义思想在此时此地形成了某种共鸣。\n3. 寻找“真我”，刻不容缓 一受其成形，不亡以待尽。与物相刃相靡，其行进如驰，而莫之能止，不亦悲乎！终身役役而不见其成功，苶然疲役而不知其所归，可不哀邪！\n“我是谁”的确是一个非常沉重的问题，这直接导致了当许多人在考虑这一问题时，给出的答案是：现在还不清楚，这个问题等我以后再解决。然而，人生的另一个特质便是时间有限，有效的时间更为有限，时间的流动是”行进如驰“，且”莫之能止“。这是庄子讨论这一系列问题，迫切地想要解决问题的重要原因——人生苦短。\n更加令人感到可悲的是，即便很多人知晓人生短暂，却仍然在“与物相刃相靡”，将时间花费在外物之上。这是没有探明本心的代价。生命总有一天会终结，这像是一团若有若无的阴云笼罩在每个人的人生之上。如此重压之下，如若选择一味回避“真我”，只是随着一个个看似合理的短期目标被迫前进，难免会走上弯路，甚至完全背离本心的选择。尽早探索自我，坚守自我，做出顺从本心的选择，不但不是浪费时间，而是在有限的人生中节省出时间来做真正重要的事。\n生命有限，这是生命赋予我们的压力，但事在人为。化压力为动力，竭尽全力去寻找、坚守真正的自我，才是正确合理的选择。\n4. 道隐于小成，言隐于荣华 解决问题的第一步是要分析好问题的成因。究竟是什么阻碍了我们去寻找本心所在？为什么坚守自我会这么困难？庄子认为，问题的关键总结起来就是“道隐于小成，言隐于荣华”。\n“道隐于小成”相对来说比较好理解。认识事物最忌讳的就是一知半解，当我们在一个领域有所小成时，往往难以静下心来继续前行，因为种种原因半途而废的人比比皆是。庄子告诫我们不要困守小成之道，不要为一时的成就而沾沾自喜。除此以外，“小成”代表我们的对大道、对自我的认知是有瑕疵的，这些缺憾会在我们进一步探索的途中带来巨大的阻碍——因为这是深植入思想的“成见”，往往难以破除。综合以上两点，大道小成之时的确很容易让我们松懈。\n大知闲闲，小知间间；大言炎炎，小言詹詹。其寐也魂交，其觉也形开，与接为搆，日以心斗。缦者，窖者，密者。小恐惴惴，大恐缦缦。其发若机栝，其司是非之谓也；其留如诅盟，其守胜之谓也；其杀若秋冬，以言其日消也；其溺之所为之，不可使复之也；其厌也如缄，以言其老洫也；近死之心，莫使复阳也。喜怒哀乐，虑叹变蛰，姚佚启态；乐出虚，蒸成菌。日夜相代乎前，而莫知其所萌。已乎，已乎！旦暮得此，其所由以生乎！\n相比而言，“言隐于荣华”所带来的的危机可能更为隐蔽，更为危险，相对的庄子也花费更多笔墨来书写。人本身是社会性的生物，不得不与其他社会成员发生关联，自然而然地会对当下存在的问题发表观点，不同的观点之间必然会有矛盾，这一点在庄子所在的春秋战国时期尤为明显。人们的出发点大多是积极的，本来是真理之争、理念之争，逐渐就演变为道统之争、面子之争，最终演变成各种形式的利益之争。求道途中由外物造成的纷纷扰扰，于我们认识自己的内心有何益处？显然，当我们的终极追求变成了外在肤浅的事物，总是渴望他人的认同，习惯于驳斥所有反对声音时，不但我们离真相越来越远，真实的本心早已销声匿迹，所做的事亦无非“日以心斗”，难怪庄子会感叹“已乎，已乎”！\n尽管我们现在并不是那个百家争鸣的年代，但是人们还是会经常陷于争执、困于外物。在庄子的眼中，世界被分为两类：本心和外物。任何困守于外物的行为，自然是背离了基本原则的，是无益于终极目标的。但是对于一个现代人来说，我们仍然很难做到完全辨别出本心与外物的界限，也难以完全摈弃外物对我们的影响。显然，在这一认识论之上，我们还需要有一套方法论来帮助我们探寻自身。\n5. 物化为表，以明为里 尽管庄子喜欢在各类论证中将世间万物分成本心、外物两类，但需要知道的是庄子希望达到的是天人合一的理想境界，而不是一味孤立地强调内心的作用。在上一部分中，我们论述了为什么面对外物我们需要高度警惕，但是庄子同时也点明了解决“我是谁”的方法恰恰隐藏在诸多外物之中。\n为是不用而寓诸庸，此之谓以明。\n庄子给出的解法是“物化为表，以明为里”，其核心便落在“以明”两个字上。以明，用庄子的话来解释就是“不用而寓诸庸”。对这一句话的理解不同的人之间往往差异很大。就我个人的想法而言，“用”指的是本人亲自参与，而“庸”则偏向于其他人处理事务。在此之下，不用而寓诸庸就是指不亲自经历而要通过观察他人的经历学习，也就是将我们对于自身的主体视角转化成了客体视角，从一个旁观者的角度看待这个世界。\n乍看上去，这似乎没有什么道理。我们一般的观点是“实践是检验真理的唯一标准”“不调查没有发言权”，怎么能够听凭旁观者的视角看待我们的人生呢？而且，当我们面对人生中的重大抉择时，又怎么可能完全做到不带入第一人称视角做判断呢？这是很容易想到的问题。对此，我们需要去思考为什么要鼓励采取旁观者视角来看待事件。作为旁观者，我们往往能够更为全面客观地对待事件，能够采取辩证的观点看待人生。从旁观者角度观察，让我们能够从情感、利益等牢笼中挣脱，看到更接近真实的真相，更关键的是：了解真实的自己。当我们能够以更为宏大的视角看待自己与他人的人生时，我们距离“心则以明”就不远了。当然，所谓旁观者的视角也是相对的，我们无需彻底摈弃主观立场，要做的便是在正确的环境下做正确的思考，维持好二者间的平衡罢了。\n昔者庄周梦为胡蝶，栩栩然胡蝶也，自喻适志与！不知周也。俄然觉，则蘧蘧然周也。不知周之梦为胡蝶与，胡蝶之梦为周与？周与胡蝶，则必有分矣。此之谓“物化”。\n当我们选择践行“以明”时，我们就已经开始用“庸”的办法接受外物了，所以说解决个人内心问题的策略还是要依靠外物，只不过是需要通过正确的策略，化用外物。这个策略就是“物化”。既然有“物齐之论”，外物本质上是相同的，那么便不要困于一种物质的表现形式中。庄周梦蝶，庄周可以是庄周，也可以是蝴蝶；蝴蝶可以是蝴蝶，也可以是庄周。我们的外在表现形式可能时常会变化，而不变的就是我们苦苦寻找赋予的本心。在这里，所谓“物化”又在某种意义上和存在主义哲学认为的“人生无意义，故具有无限可能”契合上了，不禁让我们感叹人类精神世界的神奇。\n后记 至此，《齐物论》的核心观点便已经梳理完毕，下面便是了解自我之后的“葆光”之法，这便是《养生主》篇的主要目的。总览全文，探讨的观点繁多，但从深度的尚且没达到足够理想的水平，还做不到用尽可能通俗的语言讲明白深刻的思想。\n《庄子》尽管是一本约 2500 年前的书，但许多观点在当下的人类社会中仍具有普适性，我们的工作便是辨别之并赋予当下时代的理解。一直以来，我坚信回答时代之问的最好办法，就是广泛借鉴古代思想的光辉、西方思想的结晶，并在此基础上推陈出新。或许本文从学术上以及实际应用价值上都不尽如人意，但终归是一次积极的尝试。\n大道不称，大辩不言。\n庄子曰：“大道不称，大辩不言。” 真正的哲学或许并不能够通过文字传递，但这不妨碍我们尽力追求、感悟。怕什么真理无穷，进一寸有一寸的欢喜，这还不够让人感到欣慰吗？\n","date":"2023-10-10T00:00:00Z","permalink":"/post/zhuangzi-qiwu/","title":"读书笔记：读庄子《齐物论》，探寻真正的自我"},{"content":"1. 原文观点归纳 资本主义社会在当下的矛盾： 一方面，生产力大幅提升，生活质量日益提高； 另一方面，社会中充斥着各种非理性因素： 对效率的极致追求枉顾个人的需要和才能的自由发展，增长的生产力和增长的破坏性相统一； 社会和平要通过战争威胁来达成，社会内部矛盾需要通过制造外部矛盾来转移； 资本的方案压抑了其他解决各个层面生存竞争的可能性； 新的控制形式：技术 当前社会中，对个人的控制方式不同以往，使用技术而非恐怖来全面地、深入地对个体施加影响 发达工业社会发展的两种趋势： 发达工业社会在遏制质变的发生，技术进步在多大程度上保证着共产主义社会的发展和吸引力，质变的概念就以多大的程度在一种非爆炸性发展的现实主义主张面前退却； 存在打破这一遏制并推翻现有社会的趋势 原本对立面的一体化： 两党政策趋同； 企业与劳工组织核心利益趋同，原本对立的两大阶级在现有社会的利益下联合了起来； 多元主义衰落； 无产阶级不再是社会变革的明显动因； 生产逐渐趋向极权性的行为： 决定社会需要的职业、技能和态度； 决定个人的需要和愿望； 结果：消除了私人与公众间、个人需要与社会需要间的对立 历史替代性选择被遏制、排斥： 眼前利益、虚假需求极大地降低了“质变”发生的可能 但与此同时，绝大多数人接受或被迫接受现状，不能减少发达资本主义社会的不合理性，使它少受指责； 资本主义社会的“谋划”： 发达工业社会是技术的社会，更是政治的社会——技术的合理性变成了政治的合理性； 指向最终的阶段：人类对自然做出的一切实验、改造和组织，都将变成统治的材料； 为实现这一谋划，资本主义社会对语言、文化、社会、意识形态领域做出了深入而广泛的控制。 2. 点评 以上是对马尔库塞《单向度的人》导言部分观点的整理。这篇文章不仅起到了总览全书观点立场的作用，与后续的论述形成了一个有机整体；文章本身就可以自成体系，对当代资本主义社会制度进行有利的批判。\n马尔库塞《单向度的人》可以被认为是法兰克福学派的代表作品，基本指明了西方研究马克思主义的主要思想路线，而这一篇《导论：没有反对派的社会》又是对全书的高度概括，值得读者深入研究。需要注意的是，尽管本书代表了法兰克福学派一众学者的观点，并曾经在美国青年群体中广为流传，我们仍然应当保持一个不偏不倚的态度来审视文中提出的思想观点，批判地吸收文中有益于我们的思想。\n尽管文章不长，但有一点奠定了整本书论证的基调：这是极权的社会。马尔库塞认为，当代资本主义社会的高速发展，并没有形成一个理想的、自由的社会，而是通过新的技术手段压服了社会的离心力量，通过在思想文化领域的控制，拒绝了其他一切可能的替代性方案。社会质变的可能性降到最低，社会成为了一个没有反对派、没有批判力量的社会。\n作者基于这一判断，对资本主义跨越式发展前后的文化、语言、思想乃至意识形态领域全面的剖析和批判。这种批判是基于哲学理论的批判，这种倾向是法兰克福学派观点的普遍倾向。\n马尔库塞是幸运的，他看到了马克思所不曾看到的 20 世纪资本主义进一步发展的时代，看到了更为发达的生产力水平同时也看到了更为复杂的社会问题。然而，马尔库塞以及整个法兰克福学派选择了使用纯哲学的方式来进行批判和斗争，而不是继承马克思恩格斯等人所使用的政治经济学批判方法，这在很大程度上影响了批判的深度以及相关策略的可执行性。在此基础上，他最终对社会变革的悲观态度自然也不难理解。\n不可否认的是，马尔库塞以及整个法兰克福学派为我们提供了批判资本主义社会的全新视角，其中对文化领域以及意识形态领域的分析具有一定的启发意义。然而，想要在根本上揭示这个社会的核心矛盾，不能只靠纯粹理论意义上的批判，而应当从更为基本、更为深入的政治与经济领域入手。\n21 世纪的今天，世界较马尔库塞那个时代又有了相当大的变化。书中反复提到的“大国对峙阶段”已经过去，互联网深刻改变拓宽了科技的边界，发达资本主义国家经济脱实入虚的结果已成定局，社会各阶层各团体间的矛盾进一步激化等等，这些新的社会现象是马尔库塞所不曾看到的。新的发展阶段，我们需要真正适合我们的理论来指导我们前行的路。\n研究当下发展的现状，提出科学的理论和策略，对未来做出大胆的尝试，这是我们这一代人无可推卸的使命和责任。当然，过去失败的尝试，未尝不能作为一种参考，帮助我们更为准确地找到前行的道路。\n","date":"2023-10-09T00:00:00Z","permalink":"/post/one-dimensional-man/","title":"读书笔记 - 单向度的人：核心思想，内容解析以及我的感悟"},{"content":"新闻 爸爸带着 10 岁的儿子走在步行街上，见到了一个批发市场的小女孩。小女孩说，爸爸过世了，妈妈生病了，只能靠乞讨为生。儿子给了小女孩 10 元钱。父子俩转身离开后，走在路上，儿子问爸爸：“如果她是骗人的，怎么办？”爸爸笑了，摸了摸儿子的头说：“那更好啊，说明她的妈妈没有生病。”\n时评 父亲与儿子关于“行善”的讨论，无疑代表了现代社会中人们内心深处的纠结。一方面，受人类普遍的道德观念以及中国独有的儒家文化影响，同时考虑到公民平均经济水平和受教育程度在近 40 年内的显著提高，越来越多人愿意乃至渴求行善举；另一方面，随着社会中不断出现欺诈行为，陌生人之间最基本的信任关系不复存在、沟通成本节节攀升，社会成员在面对真正需要帮助的人时变得越来越冷漠。以上两种互相矛盾的心理，直接导致了人们在“行善”一事上陷入争论或是没有必要的迷茫。\n在剖析完该现象的深层次的心理动因后，我们必须意识到：“儿子是否捐钱”与“小女孩是否真的急需钱”是两个独立事件，应当分开讨论。我们在面对社会中的弱势群体时是否行善，是对自身价值观念的考察；至于受助者是否真的需要帮助，这与受助者的道德底线、社会的诚信文化有关。二者是截然不同的，不可混为一谈。如果我们手上有证据证明受助者的确在从事欺诈行为，采取报警的方式处理自然无可厚非；如果仅仅是害怕存在这样的一种可能——一种概率极低的可能——就选择放弃行善，我们未免会对此而感到惋惜。\n这样的行为并不仅仅导致了许多本应该能得到救助的人未能如愿以偿，还反映了一种危险的心态：将行善当成一场交易。当人们把帮助他人当成商业行为来看待时，自然会希望自己付出的钱财取得更多的成效——从而获得最大化的感恩和最“高尚”的成就感。用有形的金钱换无形的情绪，这保证了此类心态的隐蔽性。我们当然希望自己在帮助最需要帮助的人，但这种交易的心态在多大程度上影响了我们的判断，值得深入思考。既然选择行善那便做纯粹的善事，何必去追求回报？“但问耕耘，莫问收获。”\n行善是对于我们内心中善意的坚守，实现了自身心灵的完满。行善举时，我们大可不必考虑受助者是否会记得所谓的“恩情”，会不会为我们提供“成就感”，行善这一行为本身就足够有价值。在当今社会，“善良”已逐渐从民众的共识演化为一种奢侈品，能在这样的环境中坚守内心的善良就足以称得上平凡人的壮举。与其说行善是在帮助他人，不如说是在帮助自己，证明自己心中善意犹存、帮助自己实现道德意义上的精神圆融罢了，没有必要将“行善”神化并为此感到沾沾自喜。\n或许有人仍然不赞同我的观点，其理由是这是善良的社会对欺骗者的放纵。但需注意：欺骗者自有法律来惩治，并不需要社会成员时刻以最恶来揣度他人。邪不压正，我们无需为犯罪者得不到应有的惩罚而担心，更不能因此把自己当成评定一切的“法官”，牺牲了自己乃至社会的全部善意。\n总而言之，行善时便用纯粹的善意去对待整个世界，世界才会用善意来对待你——这不是鸡汤或是口号。因为小概率事件而放弃行善是得不偿失的行为，甚至有助长冷漠的社会氛围之嫌，绝对不可取。\n","date":"2023-10-08T00:00:00Z","permalink":"/post/contradiciton-and-salvation/","title":"一篇时评写作练习：矛盾与救赎 - 关于行善"}]