<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Tony Bai &#187; Haskell</title>
	<atom:link href="http://tonybai.com/tag/haskell/feed/" rel="self" type="application/rss+xml" />
	<link>https://tonybai.com</link>
	<description>一个程序员的心路历程</description>
	<lastBuildDate>Sun, 12 Apr 2026 22:30:28 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.2.1</generator>
		<item>
		<title>硬核测评：哪门语言最受 AI 宠爱？13 种语言横向对比，Go 表现如何？</title>
		<link>https://tonybai.com/2026/03/09/hardcore-review-13-languages-ai-favorite-go-performance/</link>
		<comments>https://tonybai.com/2026/03/09/hardcore-review-13-languages-ai-favorite-go-performance/#comments</comments>
		<pubDate>Mon, 09 Mar 2026 00:02:44 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[AIAgent]]></category>
		<category><![CDATA[AIProgramming]]></category>
		<category><![CDATA[AI智能体]]></category>
		<category><![CDATA[AI编程]]></category>
		<category><![CDATA[benchmark]]></category>
		<category><![CDATA[BorrowChecker]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[ClaudeCode]]></category>
		<category><![CDATA[CodeGeneration]]></category>
		<category><![CDATA[CompilationSpeed]]></category>
		<category><![CDATA[DeveloperExperience]]></category>
		<category><![CDATA[DynamicLanguages]]></category>
		<category><![CDATA[FunctionalLanguages]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[Haskell]]></category>
		<category><![CDATA[InferenceCost]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[mini-git]]></category>
		<category><![CDATA[Ocaml]]></category>
		<category><![CDATA[PerformanceReview]]></category>
		<category><![CDATA[ProgrammingLanguages]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[Ruby]]></category>
		<category><![CDATA[Rust]]></category>
		<category><![CDATA[StaticLanguages]]></category>
		<category><![CDATA[TypeChecking]]></category>
		<category><![CDATA[代码生成]]></category>
		<category><![CDATA[借用检查器]]></category>
		<category><![CDATA[函数式语言]]></category>
		<category><![CDATA[动态语言]]></category>
		<category><![CDATA[基准测试]]></category>
		<category><![CDATA[开发者体验]]></category>
		<category><![CDATA[性能测评]]></category>
		<category><![CDATA[推理成本]]></category>
		<category><![CDATA[类型检查]]></category>
		<category><![CDATA[编程语言]]></category>
		<category><![CDATA[编译速度]]></category>
		<category><![CDATA[静态语言]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=6010</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2026/03/09/hardcore-review-13-languages-ai-favorite-go-performance 大家好，我是Tony Bai。 随着 Claude Code、Gemini Cli、Codex 等 AI 编程工具的全面普及，“让 AI 写代码”已经从极客的玩具变成了日常的生产力。随之而来的是一个触及灵魂的问题：哪种编程语言最适合交给 AI 去写？ 作为 Gopher，我们一直为 Go 语言的“极简语法”、“极速编译”和“强类型安全”感到自豪。我们理所当然地认为，这种没有任何隐式魔法、像白开水一样的语言，绝对是 LLM 的最爱。 然而，现实总是比直觉更骨感。近日，Ruby 核心提交者 Yusuke Endoh（@mame）发布了一份名为 ai-coding-lang-bench 的硬核定量测评报告。他使用 Claude Code（Opus 4.6 模型）对 13 种主流编程语言进行了系统性横向对比。 在这场涵盖了动态语言、静态语言和函数式语言的混战中，Go 语言的表现究竟如何？ 是力压群雄，还是黯然失色？那些备受人类推崇的静态类型系统，在 AI 面前是否成了累赘？ 本文和大家一起阅读和拆解这份报告，为你揭晓 AI 时代的语言偏好图谱。 实验设计：让 AI 写一个 Mini-Git 在评价这份报告之前，我们先来看看它的实验设计，这是目前业内少见的、针对 AI Agent 的工程化能力的量化评估。 任务目标：让 Claude Code (Opus 4.6) [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/2026/hardcore-review-13-languages-ai-favorite-go-performance-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2026/03/09/hardcore-review-13-languages-ai-favorite-go-performance">本文永久链接</a> &#8211; https://tonybai.com/2026/03/09/hardcore-review-13-languages-ai-favorite-go-performance</p>
<p>大家好，我是Tony Bai。</p>
<p>随着 <a href="http://gk.link/a/12EPd">Claude Code</a>、<a href="https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzIyNzM0MDk0Mg==&amp;action=getalbum&amp;album_id=4067128336651386882#wechat_redirect">Gemini Cli</a>、Codex 等 AI 编程工具的全面普及，“让 AI 写代码”已经从极客的玩具变成了日常的生产力。随之而来的是一个触及灵魂的问题：<strong>哪种编程语言最适合交给 AI 去写？</strong></p>
<p>作为 Gopher，我们一直为 Go 语言的“极简语法”、“极速编译”和“强类型安全”感到自豪。我们理所当然地认为，这种没有任何隐式魔法、像白开水一样的语言，绝对是 LLM 的最爱。</p>
<p>然而，现实总是比直觉更骨感。近日，Ruby 核心提交者 Yusuke Endoh（@mame）发布了<a href="https://github.com/mame/ai-coding-lang-bench">一份名为 ai-coding-lang-bench 的硬核定量测评报告</a>。他使用 Claude Code（Opus 4.6 模型）对 13 种主流编程语言进行了系统性横向对比。</p>
<p>在这场涵盖了动态语言、静态语言和函数式语言的混战中，<strong>Go 语言的表现究竟如何？</strong> 是力压群雄，还是黯然失色？那些备受人类推崇的静态类型系统，在 AI 面前是否成了累赘？</p>
<p>本文和大家一起阅读和拆解这份报告，为你揭晓 AI 时代的语言偏好图谱。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2026/agentic-software-engineering-qr.png" alt="" /></p>
<h2>实验设计：让 AI 写一个 Mini-Git</h2>
<p>在评价这份报告之前，我们先来看看它的实验设计，这是目前业内少见的、针对 AI Agent 的工程化能力的量化评估。</p>
<p><strong>任务目标</strong>：让 Claude Code (Opus 4.6) 从零开始实现一个 mini-git（简化版的 Git）。这是一个极具代表性的任务，它包含了文件 I/O、哈希计算、数据结构操作以及命令行接口，足以考验模型对语言生态的综合运用能力。</p>
<p>测试被巧妙地分为两个阶段，模拟了真实的软件生命周期：</p>
<ul>
<li>v1 (新项目构建)：实现基础的 init, add, commit 和 log。</li>
<li>v2 (特性扩展)：在 v1 的基础上，增加 status, diff, checkout, reset, rm, show 等复杂指令。</li>
</ul>
<p><strong>提示词（Prompt）极其极简</strong>：“阅读 SPEC-v1.txt，实现它，并确保 test-v1.sh 测试通过。”这种设计最大程度地减少了人类指令的干预，纯粹考验 AI 代理在闭环环境下的自主编码、调试和测试能力。</p>
<p><strong>参赛选手（13种语言/15种配置）</strong>：</p>
<ul>
<li>动态语言：Python, Ruby, JavaScript, Perl, Lua</li>
<li>动态+类型检查器：Python/mypy, Ruby/Steep</li>
<li>静态语言：TypeScript, Go, Rust, C, Java</li>
<li>函数式语言：Scheme (动态), OCaml (静态), Haskell (静态)</li>
</ul>
<p>每种语言配置运行 <strong>20 次</strong>，以消除 LLM 生成的随机性带来的误差，并统计其耗时（Time）、成本（Cost，即 Token 消耗）和代码行数（LOC）。</p>
<h2>核心发现：动态语言逆袭，Go 位居第二梯队</h2>
<p>如果仅看总耗时和总成本（v1 + v2），测试结果呈现出了令人瞩目的阶梯式分布。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2026/hardcore-review-13-languages-ai-favorite-go-performance-2.png" alt="" /></p>
<h3>第一梯队：Ruby, Python, JavaScript 的绝对统治</h3>
<p>在这场 AI 编程竞速中，Ruby（73.1s）、Python（74.6s）和 JavaScript（81.1s）组成了无可争议的第一阵营。</p>
<p>它们不仅生成速度最快、消耗 API 成本最低（均在 $0.40 以下），而且在 20 次测试中表现出了极高的稳定性（标准差极小）。</p>
<p>对于 AI 来说，生成这三种语言的代码就像呼吸一样自然。它们无需繁琐的项目初始化配置（如 Cargo.toml 或 package.json），可以做到“建个文件直接跑”，这种极简的工作流在 v1 阶段（新项目构建）优势极大。</p>
<h3>第二梯队：被“强类型”拖慢脚步的 Go 与 Java</h3>
<p>现在，来回答大家最关心的问题：Go 表现如何？</p>
<p>答案是：位居第二梯队。Go 的总耗时为 101.6s，平均成本 $0.50。中规中矩。Go 虽然在语法上非常克制，但依然落后于 Python 和 JS 等动态语言。与之类似，Java（115.4s）也因为繁琐的语法结构和强类型约束，留在了这一梯队。</p>
<p>尽管如此，Go 在整个 20 次测试中没有出现一次失败（0 次 fail），这证明了 <a href="https://tonybai.com/2026/01/04/stop-lying-to-the-compiler">Go 的编译器在防止 AI 产生“幻觉 Bug”方面，发挥了极其可靠的安全网作用</a>。</p>
<h3>“后进生”阵营：Rust 与 C 的挣扎</h3>
<p>备受人类极客推崇的 Rust（113.7s，且有 2 次失败）和底层的 C（155.8s）在测试中显得步履维艰。</p>
<p>尤为值得注意的是，在总共 600 次的独立运行中，只有 Rust (2次) 和 Haskell (1次) 出现了测试失败（未能最终跑通 Shell 脚本）的情况。这两门语言都以其极高的心智负担和“编译器教你做人”的严格程度而闻名。</p>
<p>这也是将Rust列入“后进生”阵营的主要原因。如果用《飞驰人生》的拉力赛来比喻，Rust 相当于在40站的赛季中，有两站没能完赛！</p>
<h2>深度剖析：为什么 AI 更偏爱动态语言？</h2>
<p>在传统的工程视角中，“静态类型防止低级 Bug”、“动态语言难以维护”是金科玉律。但在 LLM 驱动的 Agent 开发流中，这个逻辑为何失效了？作者 Yusuke Endoh 提出了几个关键的解释维度。</p>
<h3>训练数据的“虹吸效应”</h3>
<p>LLM 的能力直接取决于训练语料的规模和质量。Python、JavaScript 和 Ruby 是过去十几年 Web 开发的绝对主流。GitHub 上海量的这三种语言的开源代码、StackOverflow 上的问答，为 Claude Code 提供了极其丰富的“预训练肌肉记忆”。</p>
<p>当 AI 需要实现一个功能时，它在 Python 的隐空间（Latent Space）中寻找最优解的路径，远比在 Haskell 甚至 Rust 中要清晰、笔直得多。</p>
<h3>静态类型的“双刃剑”与重构阻力</h3>
<p>静态类型系统的初衷是约束人类，防止我们在重构时犯错。但在 AI 的“ Prompt -> 生成 -> 测试报错 -> 思考 -> 再生成”的迭代循环中，严格的类型检查反而成了巨大的“摩擦力”。</p>
<ul>
<li>编译成本与调试死锁：在 Rust 或 C 中，当 AI 生成的代码出现类型不匹配时，它需要花费大量的 Token 去阅读复杂的编译器报错信息。有时，为了解决一个简单的借用检查器（Borrow Checker）报错，AI 可能会陷入漫长的、无休止的“试错-编译失败”死循环。</li>
<li>重构牵一发而动全身：在 v2 特性扩展阶段，往往需要修改现有的数据结构。对于 Python，AI 只需要在字典里加个 key；而对于 Rust 或 Java，这可能意味着需要重构一系列的 Struct、更新类型签名、甚至修改与之相关的无数个函数的参数声明。这种“爆炸半径”极大地增加了耗时。</li>
</ul>
<h3>“附加类型检查”的巨大损耗</h3>
<p>报告中一个非常有意思的对照组是：原生动态语言 vs 附加类型检查器的动态语言。</p>
<ul>
<li>Python (74.6s) vs Python/mypy (125.3s) —— 变慢了 1.6~1.7 倍。</li>
<li>Ruby (73.1s) vs Ruby/Steep (186.6s) —— 变慢了 2.0~3.2 倍！</li>
</ul>
<p>这证明了，迫使 AI 在动态语言中编写严谨的类型注解（Type Annotations），是一项极其昂贵的任务。模型需要耗费额外的算力去推导类型、生成类型声明文件，并且在类型检查器报错时，还要去修复那些在纯动态模式下可能根本不影响运行的“伪 Bug”。</p>
<h3>代码量（LOC）的迷思：越短越好吗？</h3>
<p>我们通常认为，写得越少，跑得越快。但数据打破了这个迷思。</p>
<p>Haskell 和 OCaml 生成的最终代码行数（224行和 216行）是所有语言中最少的，甚至少于 Python 和 Ruby。然而，它们在生成时间上的表现却排在倒数（Haskell 耗时最长，达 174s）。</p>
<p>这表明，对于 AI 来说，函数式语言那种高度抽象、信息密度极大的代码，生成和推理的成本远高于像 Python、Go 那种稍微啰嗦但逻辑平铺直叙的“大白话”代码。浓缩的未必是精华，对于 LLM 来说，高度浓缩往往意味着更高的生成熵和更高的试错概率。</p>
<h2>行业启示：我们需要重新思考 AI 时代的技术栈选型</h2>
<p>面对这份详实的基准测试报告，无论你是 CTO 还是普通开发者，都必须开始重新审视未来的技术选型逻辑。</p>
<h3>动态语言是快速原型的“绝对王者”</h3>
<p>如果你正在启动一个新项目，或者需要用 AI Agent 快速验证一个业务流程，Python 和 TypeScript 是首选（报告中 JavaScript 表现优于 TS，但在实际工程中 TS 的综合权衡更佳）。</p>
<p>不要迷信“大型项目必须一开始就上强类型编译语言”。在需求快速变化的初期，让 AI 用动态语言狂飙突进，是获取业务反馈最高效的手段。</p>
<h3>性能王者们的困境：Go 与 Rust 在 AI 时代掉队了吗？</h3>
<p>看到测评数据，很多 Gopher 可能会感到失落：难道注重工程严谨性和系统级性能的静态语言，真的在 AI 时代掉队了吗？</p>
<p>结论并非如此悲观。我们需要明确一点：Agent 测评的速度，不等于软件最终运行的速度。</p>
<ul>
<li>业务试错 vs 基础设施：AI Agent 目前最擅长、也最快速能完成的，是写“胶水逻辑”和“业务 CRUD”。在这些领域，Python 确实快。但当你的系统涉及到高并发、内存精细控制、或者需要打包为轻量级容器部署时，人类依然需要 Go。</li>
<li>容错的底线：在这场 600 次的庞大测试中，只有 Rust 和 Haskell 出现了最终测试失败，而 Go 保持了完美的 100% 成功率。这恰恰说明，Go 在“极度灵活（易幻觉）”与“极度严格（难生成）”之间，找到了一个非常微妙的平衡点。它可能不是 AI 写得最快的，但它一定是 AI 写出来最让人放心的系统级语言。</li>
</ul>
<p>我们不应期待 AI Agent 能够像写 Python 脚本一样，如德芙般丝滑地生成出一个复杂的 Go 并发系统。但在 AI 给出的初稿之上，Go 语言极佳的可读性和统一的规范，将为人类工程师的最终审查（Code Review）节省巨大的精力。</p>
<h2>小结：下一个十年的编程语言，长什么样？</h2>
<p>ai-coding-lang-bench 给我们上了生动的一课。它揭示了当前 LLM 的偏好：<strong>它们喜欢有海量训练数据的、灵活的、不需要应对死板编译器的语言。</strong></p>
<p>但我们必须认识到，这只是一份基于 2026 年初模型（Claude Opus 4.6）的快照。未来的 AI 编程语言形态，可能会朝着两个方向演进：</p>
<ol>
<li>AI Native 语言的诞生：抛弃目前设计给人类阅读的语法，出现一种专门为了降低 LLM 生成 Token 成本、且天然抗幻觉的机器中间语言。</li>
<li>现有静态语言的“Agent 友好化”编译模式：Go 和 Rust 可能会进化出一种特殊的编译模式。在这个模式下，编译器不仅是冷冰冰地报错，还能以结构化的、对 LLM 更友好的方式提供“修复建议”，从而大幅缩短 Agent 修复编译错误的反馈回路。</li>
</ol>
<p>无论如何，浪潮已经来临。在 AI 主导代码生成的新时代，我们评价一门编程语言的标准，正在从“它对人类大脑是否友好”，悄然转变为<strong>“它对大模型推理是否友好”</strong>。</p>
<p>而在这场新赛道上，动态语言们，已经抢跑了。</p>
<p>本文核心数据与图表均来源于 GitHub 项目 <a href="https://github.com/mame/ai-coding-lang-bench">mame/ai-coding-lang-bench</a>。</p>
<hr />
<p><strong>你的 AI 编程初体验</strong></p>
<p>看完这个排名，你是感到意外，还是早已感同身受？在你日常使用 AI 编程时，你觉得它写哪种语言最让你省心？你是否也曾为了修一个 AI 写的编译错误而陷入“死循环”？</p>
<p>欢迎在评论区分享你的“AI 协作”红黑榜！</p>
<hr />
<p>“语言的严格性正在变成 AI 的摩擦力？在 AI 时代，掌握一套能驱动 Agent 自动化、自修复的‘工作流’比死磕语法更重要。我的新专栏 <strong>《<a href="http://gk.link/a/12EPd">AI原生开发工作流实战</a>》</strong> 将教你如何利用 Claude Code 结合 Spec 驱动开发，构建真正高产出的‘软件工厂’。”</p>
<ul>
<li>告别低效，重塑开发范式</li>
<li>驾驭AI Agent(Claude Code)，实现工作流自动化</li>
<li>从“AI使用者”进化为规范驱动开发的“工作流指挥家”</li>
</ul>
<p>扫描下方二维码，开启你的AI原生开发之旅。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/ai-native-dev-workflow-qr.png" alt="" /></p>
<hr />
<p>你的Go技能，是否也卡在了“熟练”到“精通”的瓶颈期？</p>
<ul>
<li>想写出更地道、更健壮的Go代码，却总在细节上踩坑？</li>
<li>渴望提升软件设计能力，驾驭复杂Go项目却缺乏章法？</li>
<li>想打造生产级的Go服务，却在工程化实践中屡屡受挫？</li>
</ul>
<p>继《<a href="http://gk.link/a/10AVZ">Go语言第一课</a>》后，我的《<a href="http://gk.link/a/12yGY">Go语言进阶课</a>》终于在极客时间与大家见面了！</p>
<p>我的全新极客时间专栏 《<a href="http://gk.link/a/12yGY">Tony Bai·Go语言进阶课</a>》就是为这样的你量身打造！30+讲硬核内容，带你夯实语法认知，提升设计思维，锻造工程实践能力，更有实战项目串讲。</p>
<p>目标只有一个：助你完成从“Go熟练工”到“Go专家”的蜕变！ 现在就加入，让你的Go技能再上一个新台阶！</p>
<p><img src="https://tonybai.com/wp-content/uploads/course-card/iamtonybai-banner-2.gif" alt="" /></p>
<hr />
<p><strong>原「Gopher部落」已重装升级为「Go &amp; AI 精进营」知识星球，快来加入星球，开启你的技术跃迁之旅吧！</strong></p>
<p>我们致力于打造一个高品质的 <strong>Go 语言深度学习</strong> 与 <strong>AI 应用探索</strong> 平台。在这里，你将获得：</p>
<ul>
<li><strong>体系化 Go 核心进阶内容:</strong> 深入「Go原理课」、「Go进阶课」、「Go避坑课」等独家深度专栏，夯实你的 Go 内功。</li>
<li><strong>前沿 Go+AI 实战赋能:</strong> 紧跟时代步伐，学习「Go+AI应用实战」、「Agent开发实战课」、「Agentic软件工程课」、「Claude Code开发工作流实战课」、「OpenClaw实战分享」等，掌握 AI 时代新技能。 </li>
<li><strong>星主 Tony Bai 亲自答疑:</strong> 遇到难题？星主第一时间为你深度解析，扫清学习障碍。</li>
<li><strong>高活跃 Gopher 交流圈:</strong> 与众多优秀 Gopher 分享心得、讨论技术，碰撞思想火花。</li>
<li><strong>独家资源与内容首发:</strong> 技术文章、课程更新、精选资源，第一时间触达。</li>
</ul>
<p>衷心希望「Go &amp; AI 精进营」能成为你学习、进步、交流的港湾。让我们在此相聚，享受技术精进的快乐！欢迎你的加入！</p>
<p><img src="http://image.tonybai.com/img/tonybai/gopher-and-ai-tribe-zsxq-small-card.jpg" alt="img{512x368}" /></p>
<hr />
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求，请扫描下方公众号二维码，与我私信联系。</p>
<p><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p style='text-align:left'>&copy; 2026, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2026/03/09/hardcore-review-13-languages-ai-favorite-go-performance/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>告别懵圈：实战派 Gopher 的类型理论入门</title>
		<link>https://tonybai.com/2025/10/30/type-theory-intro-for-gopher/</link>
		<comments>https://tonybai.com/2025/10/30/type-theory-intro-for-gopher/#comments</comments>
		<pubDate>Thu, 30 Oct 2025 00:04:05 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Add]]></category>
		<category><![CDATA[addr接口]]></category>
		<category><![CDATA[AdhocPolymorphism]]></category>
		<category><![CDATA[Animal]]></category>
		<category><![CDATA[any]]></category>
		<category><![CDATA[Area]]></category>
		<category><![CDATA[assignment]]></category>
		<category><![CDATA[bug温床]]></category>
		<category><![CDATA[bytes.Buffer]]></category>
		<category><![CDATA[C/C++]]></category>
		<category><![CDATA[calculate]]></category>
		<category><![CDATA[Cat]]></category>
		<category><![CDATA[ChanMap]]></category>
		<category><![CDATA[chanT]]></category>
		<category><![CDATA[Circle]]></category>
		<category><![CDATA[Closed]]></category>
		<category><![CDATA[DependentTypes]]></category>
		<category><![CDATA[DiscriminatedUnion]]></category>
		<category><![CDATA[Dog]]></category>
		<category><![CDATA[Duck]]></category>
		<category><![CDATA[DuckTyping]]></category>
		<category><![CDATA[Dynamic]]></category>
		<category><![CDATA[DynamicallyTyped]]></category>
		<category><![CDATA[dynamicType]]></category>
		<category><![CDATA[dynamicValue]]></category>
		<category><![CDATA[enum]]></category>
		<category><![CDATA[expression]]></category>
		<category><![CDATA[FunctionTypes]]></category>
		<category><![CDATA[Functor]]></category>
		<category><![CDATA[generics]]></category>
		<category><![CDATA[GitHubissue]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[go1.18]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[Gopher]]></category>
		<category><![CDATA[Go实现]]></category>
		<category><![CDATA[Go接口]]></category>
		<category><![CDATA[Go接口系统]]></category>
		<category><![CDATA[Go泛型]]></category>
		<category><![CDATA[Go类型系统]]></category>
		<category><![CDATA[Go设计哲学]]></category>
		<category><![CDATA[Go语言]]></category>
		<category><![CDATA[Go语言特性]]></category>
		<category><![CDATA[Haskell]]></category>
		<category><![CDATA[HigherKindedTypes]]></category>
		<category><![CDATA[HKTs]]></category>
		<category><![CDATA[HTTP中间件]]></category>
		<category><![CDATA[IDE]]></category>
		<category><![CDATA[Interface]]></category>
		<category><![CDATA[Interfaces]]></category>
		<category><![CDATA[intSlice]]></category>
		<category><![CDATA[invalidoperation]]></category>
		<category><![CDATA[io.Reader]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[Kind]]></category>
		<category><![CDATA[len]]></category>
		<category><![CDATA[main.go]]></category>
		<category><![CDATA[MakeItQuack]]></category>
		<category><![CDATA[map[K]V]]></category>
		<category><![CDATA[mismatchedtypes]]></category>
		<category><![CDATA[monad]]></category>
		<category><![CDATA[multiply]]></category>
		<category><![CDATA[net/url.go]]></category>
		<category><![CDATA[Nominal]]></category>
		<category><![CDATA[NominalTyping]]></category>
		<category><![CDATA[operator]]></category>
		<category><![CDATA[os.File]]></category>
		<category><![CDATA[overloading]]></category>
		<category><![CDATA[ParametricPolymorphism]]></category>
		<category><![CDATA[Person]]></category>
		<category><![CDATA[Point]]></category>
		<category><![CDATA[Polymorphism]]></category>
		<category><![CDATA[PrintArea]]></category>
		<category><![CDATA[ProductID]]></category>
		<category><![CDATA[ProductType]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[Quacker]]></category>
		<category><![CDATA[ReadAndPrint]]></category>
		<category><![CDATA[Rectangle]]></category>
		<category><![CDATA[Reverse]]></category>
		<category><![CDATA[RobPike]]></category>
		<category><![CDATA[Ruby]]></category>
		<category><![CDATA[Rust]]></category>
		<category><![CDATA[Scala]]></category>
		<category><![CDATA[sealed]]></category>
		<category><![CDATA[shape]]></category>
		<category><![CDATA[SimonThompson]]></category>
		<category><![CDATA[SliceMap]]></category>
		<category><![CDATA[static]]></category>
		<category><![CDATA[StaticallyTyped]]></category>
		<category><![CDATA[strconv]]></category>
		<category><![CDATA[stringSlice]]></category>
		<category><![CDATA[strong]]></category>
		<category><![CDATA[Structural]]></category>
		<category><![CDATA[StructuralSubtyping]]></category>
		<category><![CDATA[StructuralTyping]]></category>
		<category><![CDATA[SubtypePolymorphism]]></category>
		<category><![CDATA[SumType]]></category>
		<category><![CDATA[traits]]></category>
		<category><![CDATA[Tuple]]></category>
		<category><![CDATA[type-switch]]></category>
		<category><![CDATA[typeassertion]]></category>
		<category><![CDATA[TypeConstructor]]></category>
		<category><![CDATA[typeswitch]]></category>
		<category><![CDATA[TypeSystem]]></category>
		<category><![CDATA[TypeTheory&FunctionalProgramming]]></category>
		<category><![CDATA[UniversalMap]]></category>
		<category><![CDATA[untypedstring]]></category>
		<category><![CDATA[UserID]]></category>
		<category><![CDATA[value]]></category>
		<category><![CDATA[variable]]></category>
		<category><![CDATA[Variant]]></category>
		<category><![CDATA[vector]]></category>
		<category><![CDATA[Vector(n)]]></category>
		<category><![CDATA[weak]]></category>
		<category><![CDATA[[N]T]]></category>
		<category><![CDATA[[]T]]></category>
		<category><![CDATA[一等公民函数]]></category>
		<category><![CDATA[一阶类型]]></category>
		<category><![CDATA[不同类型]]></category>
		<category><![CDATA[严谨性]]></category>
		<category><![CDATA[乘法]]></category>
		<category><![CDATA[代码示例]]></category>
		<category><![CDATA[依赖类型]]></category>
		<category><![CDATA[值]]></category>
		<category><![CDATA[元组]]></category>
		<category><![CDATA[函数]]></category>
		<category><![CDATA[函数式编程]]></category>
		<category><![CDATA[函数签名]]></category>
		<category><![CDATA[函数类型]]></category>
		<category><![CDATA[函数重载]]></category>
		<category><![CDATA[加法]]></category>
		<category><![CDATA[动态值]]></category>
		<category><![CDATA[动态类型]]></category>
		<category><![CDATA[参数多态]]></category>
		<category><![CDATA[反射]]></category>
		<category><![CDATA[变体]]></category>
		<category><![CDATA[变量]]></category>
		<category><![CDATA[可维护性]]></category>
		<category><![CDATA[可读性]]></category>
		<category><![CDATA[可辨识联合]]></category>
		<category><![CDATA[可预测]]></category>
		<category><![CDATA[名义类型]]></category>
		<category><![CDATA[告别懵圈]]></category>
		<category><![CDATA[和类型]]></category>
		<category><![CDATA[复杂性]]></category>
		<category><![CDATA[复杂类型]]></category>
		<category><![CDATA[多态]]></category>
		<category><![CDATA[多种形态]]></category>
		<category><![CDATA[多返回值]]></category>
		<category><![CDATA[子类型]]></category>
		<category><![CDATA[子类型多态]]></category>
		<category><![CDATA[字段]]></category>
		<category><![CDATA[学术殿堂]]></category>
		<category><![CDATA[实战派Gopher]]></category>
		<category><![CDATA[容器]]></category>
		<category><![CDATA[封闭性]]></category>
		<category><![CDATA[局限性]]></category>
		<category><![CDATA[工具支持]]></category>
		<category><![CDATA[工程实用性]]></category>
		<category><![CDATA[工程实践]]></category>
		<category><![CDATA[工程师]]></category>
		<category><![CDATA[工程权衡]]></category>
		<category><![CDATA[底层结构]]></category>
		<category><![CDATA[开发者]]></category>
		<category><![CDATA[开放性]]></category>
		<category><![CDATA[弱类型]]></category>
		<category><![CDATA[强类型]]></category>
		<category><![CDATA[形状]]></category>
		<category><![CDATA[心智负担]]></category>
		<category><![CDATA[性能]]></category>
		<category><![CDATA[意图保证]]></category>
		<category><![CDATA[意图明确性]]></category>
		<category><![CDATA[技术提案]]></category>
		<category><![CDATA[技术沟通]]></category>
		<category><![CDATA[抽象]]></category>
		<category><![CDATA[抽象术语]]></category>
		<category><![CDATA[接口设计]]></category>
		<category><![CDATA[数组类型]]></category>
		<category><![CDATA[方法]]></category>
		<category><![CDATA[早期错误发现]]></category>
		<category><![CDATA[易于维护]]></category>
		<category><![CDATA[显式优于隐式]]></category>
		<category><![CDATA[显式转换]]></category>
		<category><![CDATA[更好]]></category>
		<category><![CDATA[模式匹配]]></category>
		<category><![CDATA[泛型]]></category>
		<category><![CDATA[泛型系统]]></category>
		<category><![CDATA[灵活性]]></category>
		<category><![CDATA[特质]]></category>
		<category><![CDATA[理论价值]]></category>
		<category><![CDATA[理论边界]]></category>
		<category><![CDATA[私有方法]]></category>
		<category><![CDATA[积类型]]></category>
		<category><![CDATA[稳健]]></category>
		<category><![CDATA[策略模式]]></category>
		<category><![CDATA[简洁性]]></category>
		<category><![CDATA[类型依赖于值]]></category>
		<category><![CDATA[类型兼容]]></category>
		<category><![CDATA[类型参数]]></category>
		<category><![CDATA[类型名称]]></category>
		<category><![CDATA[类型安全]]></category>
		<category><![CDATA[类型属性]]></category>
		<category><![CDATA[类型断言]]></category>
		<category><![CDATA[类型构造器]]></category>
		<category><![CDATA[类型检查]]></category>
		<category><![CDATA[类型理论]]></category>
		<category><![CDATA[类型理论入门]]></category>
		<category><![CDATA[类型类]]></category>
		<category><![CDATA[类型系统]]></category>
		<category><![CDATA[类型组合]]></category>
		<category><![CDATA[类型选择]]></category>
		<category><![CDATA[结构]]></category>
		<category><![CDATA[结构化子类型]]></category>
		<category><![CDATA[结构类型]]></category>
		<category><![CDATA[编程语言]]></category>
		<category><![CDATA[编译器]]></category>
		<category><![CDATA[编译时]]></category>
		<category><![CDATA[编译时常量]]></category>
		<category><![CDATA[编译时类型安全]]></category>
		<category><![CDATA[编译时类型检查]]></category>
		<category><![CDATA[编译错误]]></category>
		<category><![CDATA[翻译成果]]></category>
		<category><![CDATA[自动类型转换]]></category>
		<category><![CDATA[表达力]]></category>
		<category><![CDATA[表达式]]></category>
		<category><![CDATA[解释器]]></category>
		<category><![CDATA[计算机科学]]></category>
		<category><![CDATA[设计决策]]></category>
		<category><![CDATA[设计权衡]]></category>
		<category><![CDATA[软件工程]]></category>
		<category><![CDATA[运行时]]></category>
		<category><![CDATA[运行时信息]]></category>
		<category><![CDATA[逻辑错误]]></category>
		<category><![CDATA[重载]]></category>
		<category><![CDATA[隐式转换]]></category>
		<category><![CDATA[静态]]></category>
		<category><![CDATA[静态类型]]></category>
		<category><![CDATA[非侵入式]]></category>
		<category><![CDATA[非侵入式多态]]></category>
		<category><![CDATA[非法状态]]></category>
		<category><![CDATA[非结构化数据]]></category>
		<category><![CDATA[预防错误]]></category>
		<category><![CDATA[高度解耦]]></category>
		<category><![CDATA[高阶类型]]></category>
		<category><![CDATA[鸭子类型]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=5329</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2025/10/30/type-theory-intro-for-gopher 大家好，我是Tony Bai。 你是否曾有过这样的经历：在浏览一个关于 Go 泛型或接口设计的 GitHub issue 或技术提案时，评论区里的大佬们突然开始讨论 “Sum Type”、“Product Type”、“Parametric Polymorphism” 或是 “Higher-Kinded Types”。一瞬间，你感觉自己仿佛闯入了一个学术研讨会，这些看似熟悉又陌生的词汇让你一头雾水，只想默默关掉页面。 作为一名务实的 Gopher，我们习惯于用具体的代码和设计模式来思考问题。我们关心的是接口的解耦能力、struct 的组合性、goroutine 的并发效率。这些学院派的类型理论术语，似乎离我们的日常工作很遥远。 然而，事实并非如此。这些术语并非象牙塔里的空谈，它们是计算机科学家们经过几十年沉淀，用来精确描述和分类编程语言核心特性的“通用语言”。理解它们，就像给一位经验丰富的工匠配上了一套精准的图纸和测量工具。它能让你： 更深刻地理解 Go 的设计哲学：为什么 Go 的接口如此强大？为什么 Go 1.18之前 长期以来没有泛型？为什么 int 和 int32 不能直接相加？这些背后都有类型理论的影子。 更清晰地沟通技术方案：当你能用“Product Type”来描述 struct，用“Sum Type”的思想来解释接口的用途时，你的技术沟通会变得更加精确和高效。 看懂高阶的技术讨论：无论是 Go 语言的未来演进，还是与其他语言（如 Rust, Haskell, Scala）的对比，这些术语都是绕不开的基石。 本文的灵感来源于阅读Simon Thompson教授所著《Type Theory &#38; Functional Programming》一书时的感悟，但我们的目标并非成为类型理论的研究者。恰恰相反，我们的目标是做一个“翻译者”，将这些核心的理论概念，用我们最熟悉的 Go 语言特性和代码示例进行“转码”，彻底拉通学术殿堂与工程实践之间的鸿沟。 准备好了吗？让我们一起告别懵圈，开启这段实战派 Gopher [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/2025/type-theory-intro-for-gopher-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2025/10/30/type-theory-intro-for-gopher">本文永久链接</a> &#8211; https://tonybai.com/2025/10/30/type-theory-intro-for-gopher</p>
<p>大家好，我是Tony Bai。</p>
<p>你是否曾有过这样的经历：在浏览一个关于 Go 泛型或接口设计的 GitHub issue 或技术提案时，评论区里的大佬们突然开始讨论 “Sum Type”、“Product Type”、“Parametric Polymorphism” 或是 “Higher-Kinded Types”。一瞬间，你感觉自己仿佛闯入了一个学术研讨会，这些看似熟悉又陌生的词汇让你一头雾水，只想默默关掉页面。</p>
<p>作为一名务实的 Gopher，我们习惯于用具体的代码和设计模式来思考问题。我们关心的是接口的解耦能力、struct 的组合性、goroutine 的并发效率。这些学院派的类型理论术语，似乎离我们的日常工作很遥远。</p>
<p>然而，事实并非如此。这些术语并非象牙塔里的空谈，它们是计算机科学家们经过几十年沉淀，用来精确描述和分类编程语言核心特性的“通用语言”。理解它们，就像给一位经验丰富的工匠配上了一套精准的图纸和测量工具。它能让你：</p>
<ol>
<li><strong>更深刻地理解 Go 的设计哲学</strong>：为什么 Go 的接口如此强大？为什么 Go 1.18之前 长期以来没有泛型？为什么 int 和 int32 不能直接相加？这些背后都有类型理论的影子。</li>
<li><strong>更清晰地沟通技术方案</strong>：当你能用“Product Type”来描述 struct，用“Sum Type”的思想来解释接口的用途时，你的技术沟通会变得更加精确和高效。</li>
<li><strong>看懂高阶的技术讨论</strong>：无论是 Go 语言的未来演进，还是与其他语言（如 Rust, Haskell, Scala）的对比，这些术语都是绕不开的基石。</li>
</ol>
<p>本文的灵感来源于阅读<a href="https://www.kent.ac.uk/school-of-computing/people/3164/thompson-simon">Simon Thompson教授</a>所著《<a href="https://www.cs.kent.ac.uk/people/staff/sjt/TTFP/">Type Theory &amp; Functional Programming</a>》一书时的感悟，但我们的目标并非成为类型理论的研究者。恰恰相反，我们的目标是做一个“翻译者”，将这些核心的理论概念，用我们最熟悉的 Go 语言特性和代码示例进行“转码”，彻底拉通学术殿堂与工程实践之间的鸿沟。</p>
<p>准备好了吗？让我们一起告别懵圈，开启这段实战派 Gopher 的类型理论入门之旅。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/paid/the-ultimate-guide-to-go-module-qr.png" alt="" /></p>
<h2>地基与框架 —— 到底什么是“类型系统”？</h2>
<p>在深入具体的类型之前，我们首先需要建立一个宏观的框架。一个编程语言的<strong>类型系统 (Type System)</strong>，从学术角度来说，是一套规则集合，它为程序中的每个值（value）、变量（variable）和表达式（expression）都关联一个“类型”属性。</p>
<p>它的核心目的非常单纯且强大：<strong>在程序造成危害（比如运行时崩溃）之前，通过检查类型的合法性来预防错误</strong>。正如 Go 的领军人物 Rob Pike 所言：<strong>类型系统旨在“让非法的状态无法表示”</strong>。</p>
<p>为了系统性地理解它，我们可以从以下几个关键维度来对其进行分类和审视。</p>
<h3>类型检查的时机：编译时 vs. 运行时 (Static vs. Dynamic)</h3>
<p>这是对类型系统最基本、最重要的划分。</p>
<h4>静态类型 (Statically Typed)</h4>
<p><strong>定义</strong>：类型检查在<strong>编译时</strong>完成。编译器会像一位严谨的图书管理员，在程序运行前，通读你的全部代码，检查每一个变量的赋值、每一次函数调用，确保类型在所有地方都严格匹配。如果发现问题，程序将无法通过编译。</p>
<p><strong>优点</strong>：<br />
*   <strong>早期错误发现</strong>：绝大多数类型相关的 bug 在开发阶段就被扼杀在摇篮里。<br />
*   <strong>更高的性能</strong>：编译器确切地知道每个变量的类型和内存布局，可以生成高度优化的机器码。运行时无需再花费时间去检查类型。<br />
*   <strong>更好的工具支持和可维护性</strong>：类型本身就是最可靠的文档。IDE 能提供精准的自动补全、代码导航和安全的重构。</p>
<p><strong>Go 是一门不折不扣的静态类型语言。</strong> 它的编译器是你的第一道防线。</p>
<pre><code class="go">package main

func main() {
    var i int
    // 下面这行代码会导致编译失败，而不是运行时错误
    i = "hello"
}

// go build -&gt; ./main.go:6:4: cannot use "hello" (type untyped string) as type int in assignment
</code></pre>
<h4>动态类型 (Dynamically Typed)</h4>
<p><strong>定义</strong>：类型检查发生在<strong>运行时</strong>。变量本身没有固定的类型，它可以随时指向任何类型的值。只有当代码执行到某一行，需要对一个值进行特定操作时，解释器才会检查这个值的类型是否支持该操作。</p>
<p><strong>代表语言</strong>：Python, JavaScript, Ruby。</p>
<p><strong>Go 中的“动态”一面</strong>：虽然 Go 语言本身是静态的，但它通过 interface{} (自 Go 1.18 起的别名 any) 提供了一种强大的机制来处理不确定的类型，这在行为上<strong>模拟了动态类型</strong>的灵活性。</p>
<p>一个接口值可以看作一个“箱子”，它包含了两部分信息：值的动态类型（dynamic type）和动态值（dynamic value）。</p>
<pre><code class="go">package main
import "fmt"

func main() {
    // data 的静态类型是 any，它可以持有任何类型的值
    var data any

    data = "hello, world" // 编译通过，data 的动态类型是 string
    printValue(data)

    data = 42 // 编译通过，data 的动态类型是 int
    printValue(data)

    data = true // 编译通过，data 的动态类型是 bool
    printValue(data)
}

func printValue(v any) {
    // 使用类型断言(type assertion)或类型选择(type switch)在运行时检查动态类型
    switch val := v.(type) {
    case string:
        fmt.Printf("It's a string: %s\n", val)
    case int:
        fmt.Printf("It's an integer: %d\n", val)
    default:
        fmt.Printf("It's some other type: %T\n", val)
    }
}
</code></pre>
<p>这种机制是 Go 实现通用数据结构和处理 JSON 等非结构化数据的基石，但代价是放弃了部分编译时的类型安全，并将检查推迟到了运行时。</p>
<h3>类型的严格程度：强类型 vs. 弱类型 (Strong vs. Weak)</h3>
<p>这个维度的划分标准在学术界略有争议，但通常用来<strong>描述一门语言对于不同类型间隐式转换的容忍度</strong>。</p>
<h4>强类型 (Strongly Typed)</h4>
<p><strong>定义</strong>：语言严格限制不同类型之间的隐式转换。当一个操作需要特定类型时，你必须提供该类型的值。如果类型不匹配，要么编译失败，要么运行时报错，语言本身不会“自作主张”地进行不安全的转换。</p>
<p><strong>Go 的类型系统是出了名的“强硬”</strong>。</p>
<pre><code class="go">package main

import "strconv"

func main() {
    var a int = 10
    var b float64 = 5.5

    // 编译错误：不同数值类型之间不能直接运算
    // c := a + b // invalid operation: a + b (mismatched types int and float64)

    // 必须进行显式类型转换
    c := float64(a) + b // 正确

    var i int32 = 100
    var j int64 = 200

    // 即使是不同位数的整型，也必须显式转换
    // k := i + j // invalid operation: i + j (mismatched types int32 and int64)
}
</code></pre>
<p>这种严格性杜绝了许多在 C/C++ 或 JavaScript 中常见的、因隐式转换导致的难以察觉的 bug，让代码行为更加可预测。</p>
<h4>弱类型 (Weakly Typed)</h4>
<p><strong>定义</strong>：语言倾向于在操作中自动进行类型转换，以“尽力”让程序继续运行。</p>
<p><strong>代表语言</strong>：JavaScript 是典型代表，&#8217;5&#8242; + 1 会得到字符串 &#8217;51&#8242;，而 &#8217;5&#8242; &#8211; 1 会得到数字 4。这种灵活性有时很方便，但也是 bug 的温床。</p>
<h3>类型的等价性判断：名义类型 vs. 结构类型 (Nominal vs. Structural)</h3>
<p>这是判断“类型 A 和类型 B 是否相同（或兼容）”的规则，也是理解 Go 接口的关键。</p>
<h4>名义类型 (Nominal Typing)</h4>
<p><strong>定义</strong>：类型是否等价，取决于它们的<strong>名称</strong>。即使两个类型拥有完全相同的底层结构和字段，只要它们的类型名称不同，它们就是两个完全不同的、不兼容的类型。</p>
<p><strong>Go 的核心类型（structs, named basic types）遵循名义类型系统。</strong></p>
<pre><code class="go">package main
import "fmt"

type UserID int
type ProductID int

type Point struct {
    X, Y int
}

type Vector struct {
    X, Y int
}

func main() {
    var uid UserID = 123
    var pid ProductID = 123

    // 编译错误：尽管底层都是 int，但类型名称不同
    // if uid == pid { ... } // invalid operation: uid == pid (mismatched types UserID and ProductID)

    p := Point{1, 2}
    v := Vector{1, 2}

    // 编译错误：尽管结构完全相同，但类型名称不同
    // if p == v { ... } // invalid operation: p == v (mismatched types Point and Vector)
}
</code></pre>
<p>名义类型提供了非常强的意图保证。UserID 就是 UserID，它承载的业务含义与 ProductID 完全不同，编译器强制你区分它们，从而避免了将用户 ID 误用为产品 ID 的逻辑错误。</p>
<h4>结构类型 (Structural Typing)</h4>
<p><strong>定义</strong>：类型是否兼容，取决于它们的<strong>结构</strong>或“形状”（它们有哪些字段、哪些方法）。只要结构满足要求，类型就是兼容的，这与它们的名称无关。这通常被称为“<strong>鸭子类型</strong>”（Duck Typing）——“如果它走起来像鸭子，叫起来也像鸭子，那么它就是一只鸭子。”</p>
<p><strong>Go 的体现</strong>：<strong>Go 的 interface 系统是纯粹的结构类型系统。</strong></p>
<pre><code class="go">package main
import "fmt"

// 定义一个“会叫的”接口
type Quacker interface {
    Quack() string
}

// Duck 类型，它有一个 Quack 方法
type Duck struct{}
func (d Duck) Quack() string {
    return "Quack!"
}

// Person 类型，它也有一个 Quack 方法
type Person struct{}
func (p Person) Quack() string {
    return "I'm quacking like a duck!"
}

// 这个函数只关心传入的值是否满足 Quacker 接口的“结构”
func MakeItQuack(q Quacker) {
    fmt.Println(q.Quack())
}

func main() {
    var d Duck
    var p Person

    // Duck 和 Person 都没有显式声明 "implements Quacker"
    // 但因为它们都有 Quack() string 方法，所以它们都满足 Quacker 接口
    MakeItQuack(d) // 输出: Quack!
    MakeItQuack(p) // 输出: I'm quacking like a duck!
}
</code></pre>
<p>Go 的这一设计堪称神来之笔：<strong>在一个整体为名义类型的静态语言中，通过接口开辟了一块结构类型的区域，从而在不牺牲类型安全的前提下，获得了动态语言般的灵活性和强大的解耦能力。</strong> 你可以在不修改第三方库代码的情况下，让自己的类型去实现它的接口。</p>
<h3>Go 类型系统的定位</h3>
<p>综合以上维度，我们可以给 Go 的类型系统下一个精准的定义：</p>
<p>Go 是一门<strong>静态、强类型</strong>的语言。它主要采用<strong>名义类型系统</strong>来保证代码的严谨性和意图明确性，同时通过<strong>接口</strong>这一特性，创造性地引入了<strong>结构类型系统</strong>，以实现灵活、非侵入式的多态。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/type-theory-intro-for-gopher-2.png" alt="" /></p>
<p>现在，我们已经搭建好了理解类型系统的宏观框架。接下来，让我们深入到类型的“原子世界”，看看那些让 Gopher 们“懵圈”的术语，在 Go 中究竟是什么模样。</p>
<h2>类型的“和”与“积” —— Go 世界的 Sum &amp; Product Type</h2>
<p>在类型理论中，最基本的两种类型组合方式是“积”与“和”。它们就像算术中的乘法和加法，是构建更复杂类型的基础。</p>
<h3>Product Type (积类型)：A and B</h3>
<p><strong>学术定义</strong>：一个<strong>积类型</strong>（Product Type）的值由多个其他类型的值<strong>同时</strong>组成。如果一个类型 P 是类型 A 和类型 B 的积类型，那么 P 的一个值会同时包含一个 A 类型的值<strong>和</strong>一个 B 类型的值。</p>
<p>这听起来很熟悉，对吗？</p>
<p><strong>Go 的实现：struct</strong></p>
<p>struct 是 Go 对积类型的直接且完美的实现。</p>
<pre><code class="go">// Person 类型是 string 和 int 的积类型
type Person struct {
    Name string // 包含一个 string
    Age  int    // 和一个 int
}

// p1 这个值同时持有一个 string "Alice" 和一个 int 30
var p1 Person = Person{Name: "Alice", Age: 30}
</code></pre>
<p>学术上，积类型最简单的形式是<strong>元组 (Tuple)</strong>，例如 (string, int)。Go 不支持原生的元组语法，但 struct 在功能上是更强大的、带命名字段的元组。你甚至可以通过多返回值来模拟元组的使用：</p>
<pre><code class="go">func getPerson() (string, int) {
    return "Bob", 42
}

// name 和 age 在这里就像一个临时的元组
name, age := getPerson()
</code></pre>
<p>所以，下次当你在讨论中听到 <strong>Product Type</strong>，你就可以自信地在脑海里将它替换为：<strong>“哦，就是 struct 这种东西。”</strong></p>
<h3>Sum Type (和类型)：A or B</h3>
<p><strong>学术定义</strong>：一个<strong>和类型</strong>（Sum Type），也叫<strong>可辨识联合 (Discriminated Union)</strong> 或<strong>变体 (Variant)</strong>，它的值在任意时刻只能是几种可能性中的<strong>一种</strong>。如果一个类型 S 是类型 A 和类型 B 的和类型，那么 S 的一个值要么是一个 A 类型的值，<strong>要么</strong>是一个 B 类型的值，绝不可能同时是两者。</p>
<p>很多现代语言，如 Rust、Swift、Haskell，都有原生语法来支持和类型：</p>
<pre><code class="rust">// Rust 中的 enum 就是一个和类型
enum Result&lt;T, E&gt; {
    Ok(T),    // 要么是成功，里面包含一个 T 类型的值
    Err(E),   // 要么是失败，里面包含一个 E 类型的值
}
</code></pre>
<p>Go 语言没有提供上述那样的原生和类型语法。这是 Go 设计者在语言复杂性上做出的一个明确权衡。但是，Go 开发者每天都在使用和类型的思想，只是我们用的是另一种工具——<strong>接口</strong>。</p>
<p>一个接口类型定义了一个方法的集合。任何实现了这些方法的类型，都可以被看作是这个接口类型集合中的一员。因此，一个接口类型的变量，可以持有任何一个满足其要求的具体类型的值。这正是“A <strong>或</strong> B <strong>或</strong> C&#8230;”的核心思想。</p>
<p>让我们用一个经典的例子来具象化这个概念：一个图形应用需要处理不同的形状。</p>
<pre><code class="go">package main
import "math"

// Shape 接口定义了一个“和类型”，它可以是任何能计算面积的东西。
// 它可以是 Circle，或者是 Rectangle，或者是未来我们定义的任何其他形状。
type Shape interface {
    Area() float64
}

// --- 可能性 1: Circle ---
type Circle struct {
    Radius float64
}
func (c Circle) Area() float64 {
    return math.Pi * c.Radius * c.Radius
}

// --- 可能性 2: Rectangle ---
type Rectangle struct {
    Width, Height float64
}
func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}

// 这个函数接受一个 Shape 类型的值。
// 它不关心这个值到底是 Circle 还是 Rectangle，只关心它能调用 Area() 方法。
func PrintArea(s Shape) {
    // 这时，变量 s 的值可能是 Circle 或 Rectangle 之一
    fmt.Printf("Area of %T is %0.2f\n", s, s.Area())
}

func main() {
    c := Circle{Radius: 5}
    r := Rectangle{Width: 4, Height: 3}

    PrintArea(c) // 输出: Area of main.Circle is 78.54
    PrintArea(r) // 输出: Area of main.Rectangle is 12.00
}
</code></pre>
<p>在这个例子里，Shape 接口扮演了和类型的角色。一个 Shape 变量的值，在任何时刻，要么是一个 Circle，要么是一个 Rectangle。</p>
<p><strong>如何“辨识”具体的类型？—— type switch</strong></p>
<p>和类型的一个关键特性是“可辨识”（Discriminated）。这意味着我们必须有办法知道当前的值到底是哪个具体的类型。在 Go 中，我们使用 type switch 来实现这一点。</p>
<pre><code class="go">func PrintShapeDetails(s Shape) {
    fmt.Printf("Shape details for %T:\n", s)
    switch shape := s.(type) {
    case Circle:
        // 在这个 case 分支里，编译器知道 shape 的类型是 Circle
        fmt.Printf("  It's a circle with radius %.2f\n", shape.Radius)
    case Rectangle:
        // 在这个 case 分支里，编译器知道 shape 的类型是 Rectangle
        fmt.Printf("  It's a rectangle with width %.2f and height %.2f\n", shape.Width, shape.Height)
    default:
        fmt.Println("  It's an unknown shape.")
    }
}
</code></pre>
<p>type switch 是处理和类型值时的“模式匹配”，它安全地拆开接口这个“箱子”，并根据里面的动态类型执行相应的逻辑。</p>
<p><strong>模拟的代价：开放性与编译时检查的缺失</strong></p>
<p>Go 的接口模拟与原生和类型有一个本质区别：<strong>接口是开放的，而原生和类型通常是封闭的</strong>。</p>
<ul>
<li><strong>封闭性 (Sealed/Closed)</strong>：在 Rust 的例子中，Result只能是 Ok(T)中的T 或 Err(E)中的E，编译器知道所有可能性。如果你在 match（类似 switch）时漏掉了一种情况，编译器会报错。</li>
<li><strong>开放性 (Open)</strong>：在 Go 的例子中，任何包、任何地方都可以定义一个新的类型（比如 Triangle），只要它实现了 Area() 方法，它就可以被赋值给 Shape 变量。这意味着编译器永远无法保证你的 type switch 处理了所有情况，因此 default 分支变得至关重要。</li>
</ul>
<p>为了在 Go 中模拟一个更“封闭”的和类型，有时会使用一种技巧：在接口中定义一个私有方法。</p>
<pre><code class="go">type Shape interface {
    Area() float64
    isShape() // 私有方法
}
</code></pre>
<p>由于私有方法 isShape 只能在同一个包内被实现，这实际上就将 Shape 接口的实现者限制在了当前包内，从而模拟了一个封闭的和类型。这在 Go 标准库中（例如 net/url.go 中的 addr 接口）时有应用。</p>
<p>所以，下次当你看到 <strong>Sum Type</strong> 这个术语，你的脑海中应该浮现出这样的映射：</p>
<blockquote>
<p><strong>“哦，这是指一个值在多个类型中‘非此即彼’的概念。Go 没有原生支持它，但我们通过 interface 和 type switch 的组合，在工程实践中出色地模拟了它的核心思想。”</strong></p>
</blockquote>
<h2>抽象的力量 —— Go 中的函数与多态</h2>
<p>类型系统不仅用于组合数据，更强大的能力在于抽象行为。这主要涉及到函数类型和多态。</p>
<h3>函数类型 (Function Types)</h3>
<p><strong>学术定义</strong>：从类型 A 到类型 B 的一个映射，记作 A -> B。在函数式编程和类型理论中，函数本身就是一种可以被传递、存储和返回的值，即“一等公民”。</p>
<p><strong>Go 的实现</strong>：Go 完全支持一等公民函数。我们可以定义函数类型，这在 Go 代码中非常常见。</p>
<pre><code class="go">package main
import "fmt"

// 定义一个函数类型 Operator，它接受两个 int，返回一个 int
type Operator func(int, int) int

func add(a, b int) int {
    return a + b
}

func multiply(a, b int) int {
    return a * b
}

// calculate 函数接受一个 Operator 类型的函数作为参数
func calculate(a, b int, op Operator) {
    result := op(a, b)
    fmt.Printf("Result is: %d\n", result)
}

func main() {
    calculate(10, 5, add)      // 输出: Result is: 15
    calculate(10, 5, multiply) // 输出: Result is: 50
}
</code></pre>
<p>HTTP 中间件、策略模式等诸多设计模式在 Go 中都大量利用了函数类型。</p>
<h3>多态 (Polymorphism)</h3>
<p>“Polymorphism”源于希腊语，意为“多种形态”。在编程中，它指代<strong>一段代码可以处理不同类型的值</strong>的能力。类型理论通常将其分为几种。</p>
<h4>参数多态 (Parametric Polymorphism)</h4>
<p><strong>学术定义</strong>：编写的代码其逻辑对于操作的值的<strong>具体类型</strong>是通用的、不相关的。函数或数据结构可以被一个或多个类型<strong>参数化</strong>。例如，一个反转列表的函数，其逻辑（交换头尾元素）与列表里存的是整数、字符串还是用户自定义结构完全无关。</p>
<p><strong>Go 的实现：泛型 (Generics, Go 1.18+)</strong></p>
<p>在 Go 1.18 之前，Gopher 们只能通过 interface{} 和反射来模拟参数多态，但这牺牲了类型安全和性能。泛型的引入，为 Go 提供了实现参数多态的“正统”方式。</p>
<pre><code class="go">package main
import "fmt"

// 这个函数的逻辑对任何类型 T 都是一样的
// T 是一个类型参数
func Reverse[T any](s []T) {
    for i, j := 0, len(s)-1; i &lt; j; i, j = i+1, j-1 {
        s[i], s[j] = s[j], s[i]
    }
}

func main() {
    intSlice := []int{1, 2, 3, 4}
    Reverse(intSlice)
    fmt.Println(intSlice) // 输出: [4 3 2 1]

    stringSlice := []string{"a", "b", "c"}
    Reverse(stringSlice)
    fmt.Println(stringSlice) // 输出: [c b a]
}
</code></pre>
<p>当你听到 <strong>Parametric Polymorphism</strong>，你就可以直接联想到 <strong>Go 的泛型</strong>。</p>
<h4>子类型多态 (Subtype Polymorphism)</h4>
<p><strong>学术定义</strong>：一个函数或操作可以作用于某个类型 T，同时也能作用于 T 的所有<strong>子类型</strong>。例如，一个处理 Animal 的函数，应该也能处理 Dog 和 Cat，因为 Dog 和 Cat 都是 Animal 的子类型。</p>
<p><strong>Go 的实现：接口 (Interfaces)</strong></p>
<p>我们又回到了接口！在 Go 的世界里，子类型的概念正是通过接口来实现的。如果类型 T 实现了接口 I，那么 T 就可以被看作是 I 的一个“子类型”。</p>
<p>更准确地说，Go 实现的是<strong>结构化子类型 (Structural Subtyping)</strong>。</p>
<pre><code class="go">package main
import (
    "bytes"
    "fmt"
    "io"
    "os"
)

// 这个函数接受任何满足 io.Reader 接口的类型
// os.File 是 io.Reader 的一个“子类型”
// bytes.Buffer 也是 io.Reader 的一个“子类型”
func ReadAndPrint(r io.Reader) {
    data, err := io.ReadAll(r)
    if err != nil {
        panic(err)
    }
    fmt.Println(string(data))
}

func main() {
    // 从文件读取
    file, _ := os.Open("test.txt")
    defer file.Close()
    ReadAndPrint(file)

    // 从内存中的 buffer 读取
    buffer := bytes.NewBufferString("Hello from buffer!")
    ReadAndPrint(buffer)
}
</code></pre>
<p>ReadAndPrint 函数体现了子类型多态：它被编写用来处理 io.Reader 这一通用类型，但实际上它可以无缝处理 <em>os.File、</em>bytes.Buffer 以及任何其他未来可能出现的、满足 io.Reader 结构的类型。</p>
<h4>Ad-hoc 多态 (Ad-hoc Polymorphism)</h4>
<p><strong>学术定义</strong>：也称为<strong>重载 (Overloading)</strong>。同一个函数名可以有多个不同的实现，具体调用哪个实现取决于参数的类型。例如，add(int, int) 和 add(string, string) 是两个不同的函数。</p>
<p>Go <strong>不支持</strong>函数重载。Go 的哲学是“显式优于隐式”，函数签名（包括函数名、参数类型和返回值类型）是唯一的。</p>
<h2>理论的边界 —— Go 类型系统“做不到”的事</h2>
<p>理解一门语言，不仅要知道它能做什么，也要知道它的边界在哪里，以及为什么会有这些边界。这通常是设计者在“表达力”与“简洁性”之间做出权衡的结果。</p>
<h3>依赖类型 (Dependent Types)</h3>
<p><strong>学术定义</strong>：一种高级的类型系统特性，允许<strong>类型依赖于值</strong>。这意味着类型可以由程序中的常规变量来参数化。</p>
<p><strong>经典例子</strong>：定义一个“长度为 n 的向量”类型 Vector(n)。这样，Vector(3) 和 Vector(4) 就是两个完全不同的类型。编译器可以静态地保证你不会把一个长度为 3 的向量赋值给一个长度为 4 的向量变量，或者保证矩阵乘法的维度匹配。</p>
<pre><code>// 伪代码，Go 并不支持
func dotProduct(n: int, v1: Vector(n), v2: Vector(n)) -&gt; float64 {
    // ...
}

var vec3 Vector(3)
var vec4 Vector(4)
dotProduct(3, vec3, vec4) // 编译错误！vec4 的长度不是 3
</code></pre>
<p>Go完全不支持依赖类型。Go 的类型系统在编译时工作，而像 n 这样的值通常在运行时才知道。将运行时信息混入编译时类型检查会极大地增加语言和编译器的复杂性。Go 选择了简洁，将这类检查（如切片长度）的责任交给了程序员，通过 len() 函数和运行时 panic 来保障。</p>
<p>值得一提的是，Go 的数组类型 [N]T 具有依赖类型的“影子”。例如，[3]int 和 [4]int 是不同的类型，因为它们的类型定义依赖于值 3 和 4。但这并非真正的依赖类型，因为数组的长度 N 必须是一个编译时常量，而不能是一个运行时变量。这个限制正是 Go 的数组与依赖类型的本质区别，也是 Go 在追求更强类型安全与保持语言简洁性之间做出的一种工程权衡。</p>
<h3>高阶类型 (Higher-Kinded Types, HKTs)</h3>
<p>这是一个在函数式编程和高级类型系统讨论中频繁出现的术语，也是理解 Go 泛型设计边界的关键所在。乍一听可能有些吓人，但我们可以通过类比来轻松理解它。</p>
<p><strong>通俗解释：类型的“阶”</strong></p>
<p>想象一下我们熟悉的函数：</p>
<ul>
<li><strong>一阶函数</strong>：操作“值”。例如，func add(a, b int) int 接受 int 值，返回 int 值。</li>
<li><strong>高阶函数</strong>：操作“函数”。例如，func apply(f func(int) int, v int) int 接受一个函数 f 作为参数。</li>
</ul>
<p>现在，我们把这个概念“提升”到类型层面：</p>
<ul>
<li><strong>一阶类型 (或称普通类型)</strong>：就是一个具体的类型，比如 int, string, struct{}。在类型理论中，它们的“种类”(Kind) 被记为 *。</li>
<li>
<p><strong>高阶类型 (Higher-Kinded Types)</strong>：不是一个完整的类型，而是一个“类型的模板”或“类型构造器”(Type Constructor)。它接受一个或多个普通类型作为参数，然后“构造”出一个新的普通类型。</p>
<ul>
<li>[]T 就是一个类型构造器。[] 本身不是类型，你必须给它一个类型（如 int），才能得到一个完整的类型 []int。它的“种类”可以记为 * -> * (接受一个类型，返回一个类型)。</li>
<li>同理，map[K]V 也是一个类型构造器，它的“种类”是 * -> * -> * (接受两个类型，返回一个类型)。</li>
<li>chan T 也是 * -> *。</li>
</ul>
</li>
</ul>
<p><strong>高阶类型系统</strong>，就是指一门语言的泛型系统<strong>能够对类型构造器本身进行抽象</strong>的能力。换句话说，泛型参数不仅可以是 T（代表一个普通类型），还可以是 F（代表一个类型构造器，如 [] 或 chan）。</p>
<p><strong>Go 的现状：不支持高阶类型</strong></p>
<p>Go 的泛型系统被设计为只处理<strong>一阶类型</strong>。这意味着 Go 的类型参数 [T any] <strong>只能代表一个完整的类型</strong>。</p>
<ul>
<li>T 可以是 int。</li>
<li>T 也可以是 []int。</li>
<li>但 T <strong>不能</strong>是 [] 本身。</li>
</ul>
<p>让我们通过一个经典的 Map 函数的例子来具体说明这一点。我们的目标是写一个<strong>通用</strong>的 Map 函数，它能将一个容器里的所有元素通过一个函数进行转换，并返回一个包含新元素的<strong>同类容器</strong>。</p>
<p><strong>Go 能做到的：为每种容器编写独立的泛型函数</strong></p>
<p>由于 Go 不支持 HKTs，我们必须为 slice、channel 或其他任何我们想支持的容器类型，分别编写一个泛型 Map 函数。</p>
<pre><code class="go">// 为 slice 实现的 Map
func SliceMap[T, U any](s []T, f func(T) U) []U {
    result := make([]U, len(s))
    for i, v := range s {
        result[i] = f(v)
    }
    return result
}

// 为 channel 实现的 Map (简化版)
func ChanMap[T, U any](ch &lt;-chan T, f func(T) U) &lt;-chan U {
    result := make(chan U)
    go func() {
        defer close(result)
        for v := range ch {
            result &lt;- f(v)
        }
    }()
    return result
}
</code></pre>
<p>注意，SliceMap 和 ChanMap 的核心逻辑思想是一致的，但因为容器的操作方式（创建、遍历、添加元素）不同，且 Go 无法抽象“容器”这个概念，我们不得不重复编写。</p>
<p><strong>Go 做不到的：一个统一所有容器的 Map 函数（伪代码）</strong></p>
<p>如果 Go 支持高阶类型，我们就可以梦想编写一个 UniversalMap 函数。下面的代码使用了 Go 的语法风格，但它在 Go 中是<strong>完全无法编译</strong>的，它仅仅是为了展示 HKTs 的思想。</p>
<pre><code class="go">// ----------------------------------------------------
// !! 警告：以下是 HKTs 思想的伪代码，无法在 Go 中编译 !!
// ----------------------------------------------------

// 这里的 type F[T] any 是一种虚构的语法，
// 意在声明“F 是一个接受单一类型参数的类型构造器”。
func UniversalMap[type F[T] any, T, U any](container F[T], f func(T) U) F[U] {
    // 这段函数体在 Go 中是无法实现的，因为：
    // 1. 如何创建一个 F[U] 类型的新容器？make(F[U]) 语法无效。
    // 2. 如何遍历一个抽象的 F[T] 容器？range 关键字只认识内置类型。
    // 3. 如何向 F[U] 中添加一个元素？是 append 还是 &lt;- 发送？

    panic("This is pseudo-code demonstrating what HKTs would enable.")
}

func main() {
    ints := []int{1, 2, 3}
    intChan := make(chan int)

    // 在一个支持 HKTs 的理想世界里，我们可以这样调用：
    // strings := UniversalMap(ints, func(i int) string { ... })      // 期望返回 []string
    // stringChan := UniversalMap(intChan, func(i int) string { ... }) // 期望返回 chan string
}
</code></pre>
<p>这段伪代码清晰地揭示了 Go 泛型的边界：</p>
<ol>
<li><strong>语法限制</strong>：Go 没有定义 [type F[T] any] 这样的语法来表示“一个类型构造器”作为类型参数。</li>
<li><strong>实现限制</strong>：即使语法允许，Go 缺乏一个通用的接口来描述“容器”的基本操作（如 map, flatMap 等）。支持 HKTs 的语言（如 Haskell, Scala）通常会提供一套名为 Functor, Monad 的“类型类”或“特质”(traits) 来定义这些通用操作，程序员可以为自己的容器类型（比如自定义的 Tree[T]）实现这些接口。</li>
</ol>
<p><strong>为什么 Go 选择不支持 HKTs？</strong></p>
<p>这是一个深思熟虑的设计决策。Go 语言的核心哲学之一是<strong>简洁性</strong>和<strong>可读性</strong>。高阶类型的概念虽然强大，但它引入了更高层次的抽象，极大地增加了语言的复杂性和程序员的心智负担。对于 Go 团队来说，为 slice 和 chan 等几种常见类型编写独立的泛型函数，这种适度的代码重复，相比于引入整个 HKTs 体系所带来的复杂性，是一个更值得接受的权衡。</p>
<p>所以，当你听到 <strong>Higher-Kinded Types</strong>，你可以这样理解：<strong>“它是一种更强大的泛型，可以对像 []T 中的 [] 这样的‘类型模板’本身进行参数化，但 Go 为了保持简洁而没有支持它。因此在 Go 中，我们需要为不同的容器类型（如 slice, channel）编写各自的泛型工具函数。”</strong></p>
<h2>小结：从“懵圈”到“通透”</h2>
<p>我们从令人困惑的 GitHub issue 讨论出发，踏上了一段连接类型理论与 Go 语言实践的旅程。现在，让我们回顾一下我们的“翻译”成果，将那些抽象的术语牢牢地锚定在 Go 的具体实现上：</p>
<ul>
<li>
<p><strong>类型系统框架</strong>：我们确立了 Go 的定位——一个<strong>静态、强类型</strong>的系统，它以<strong>名义类型</strong>为基础保证代码的严谨性，同时通过<strong>接口</strong>这一卓越设计，巧妙地融合了<strong>结构类型</strong>的灵活性。</p>
</li>
<li>
<p><strong>Product Type (积类型)</strong>：这个概念不再神秘，它就是我们日常工作中构建复合数据的基石——<strong>struct</strong>。</p>
</li>
<li>
<p><strong>Sum Type (和类型)</strong>：我们揭示了 Go 是如何通过<strong>接口</strong>和<strong>type switch</strong> 这一组合拳，优雅地模拟出和类型的核心思想（“A 或 B”）。我们最熟悉的 error 接口，便是这一思想在 Go 生态中最无处不在的体现。</p>
</li>
<li>
<p><strong>Parametric Polymorphism (参数多态)</strong>：我们看到，Go 1.18+ 的<strong>泛型</strong>为其提供了原生的、类型安全的支持，让我们得以编写出与具体类型无关的通用算法和数据结构。</p>
</li>
<li>
<p><strong>Subtype Polymorphism (子类型多态)</strong>：这再次指向了 <strong>Go 接口</strong>的强大之处。它基于<strong>结构化子类型</strong>，构建了一个非侵入式、高度解耦的多态模型，这是 Go 强大组合能力的核心源泉。</p>
</li>
<li>
<p><strong>理论的边界 (Dependent Types &amp; HKTs)</strong>：我们不仅理解了这些高级特性是什么，更重要的是，通过具体的伪代码示例，我们清晰地看到了 <strong>Go 泛型的局限性</strong>——它只能参数化完整的类型，而无法抽象<strong>类型构造器</strong>（如 [] 或 chan）。我们明白了，这些“做不到”并非语言的缺陷，而是 Go 团队在<strong>追求简洁性、可读性和工程实用性</strong>方面做出的深思熟虑的<strong>设计权衡</strong>。</p>
</li>
</ul>
<p>掌握这些术语，并不仅仅是为了在技术讨论中显得“专业”。更重要的是，它为我们提供了一个更深刻、更系统的视角来审视我们每天使用的工具。它解释了 Go 为什么是现在这个样子，它的优势在哪里，它的取舍又在哪里。</p>
<p>希望这篇文章能成为你工具箱里的一件利器。当你下一次再遇到那些“学院派”术语时，你将不再“懵圈”，而是能够会心一笑，轻松地将它们映射到你熟悉的 Go 世界中，从而更加自信地去创造、去构建、去解决实际的工程问题。</p>
<p>毕竟，对于实战派 Gopher 而言，任何理论的最终价值，都在于它能否帮助我们写出更好、更稳健、更易于维护的代码。</p>
<hr />
<p>你的Go技能，是否也卡在了“熟练”到“精通”的瓶颈期？</p>
<ul>
<li>想写出更地道、更健壮的Go代码，却总在细节上踩坑？</li>
<li>渴望提升软件设计能力，驾驭复杂Go项目却缺乏章法？</li>
<li>想打造生产级的Go服务，却在工程化实践中屡屡受挫？</li>
</ul>
<p>继《<a href="http://gk.link/a/10AVZ">Go语言第一课</a>》后，我的《<a href="http://gk.link/a/12yGY">Go语言进阶课</a>》终于在极客时间与大家见面了！</p>
<p>我的全新极客时间专栏 《<a href="http://gk.link/a/12yGY">Tony Bai·Go语言进阶课</a>》就是为这样的你量身打造！30+讲硬核内容，带你夯实语法认知，提升设计思维，锻造工程实践能力，更有实战项目串讲。</p>
<p>目标只有一个：助你完成从“Go熟练工”到“Go专家”的蜕变！ 现在就加入，让你的Go技能再上一个新台阶！</p>
<p><img src="https://tonybai.com/wp-content/uploads/course-card/iamtonybai-banner-2.gif" alt="" /></p>
<hr />
<p><strong>想系统学习Go，构建扎实的知识体系？</strong></p>
<p>我的新书《<a href="https://book.douban.com/subject/37499496/">Go语言第一课</a>》是你的首选。源自2.4万人好评的极客时间专栏，内容全面升级，同步至Go 1.24。首发期有专属五折优惠，不到40元即可入手，扫码即可拥有这本300页的Go语言入门宝典，即刻开启你的Go语言高效学习之旅！</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/go-primer-published-4.png" alt="" /></p>
<hr />
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求，请扫描下方公众号二维码，与我私信联系。</p>
<p><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p style='text-align:left'>&copy; 2025, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2025/10/30/type-theory-intro-for-gopher/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>通过Go示例理解函数式编程思维</title>
		<link>https://tonybai.com/2024/08/11/understand-functional-programming-in-go/</link>
		<comments>https://tonybai.com/2024/08/11/understand-functional-programming-in-go/#comments</comments>
		<pubDate>Sun, 11 Aug 2024 13:46:57 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Clisp]]></category>
		<category><![CDATA[Clojure]]></category>
		<category><![CDATA[closure]]></category>
		<category><![CDATA[Either]]></category>
		<category><![CDATA[Erlang]]></category>
		<category><![CDATA[filter]]></category>
		<category><![CDATA[first-class]]></category>
		<category><![CDATA[fold]]></category>
		<category><![CDATA[FP]]></category>
		<category><![CDATA[fp-go]]></category>
		<category><![CDATA[Function]]></category>
		<category><![CDATA[functional]]></category>
		<category><![CDATA[generics]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[Haskell]]></category>
		<category><![CDATA[high-order]]></category>
		<category><![CDATA[Immutability]]></category>
		<category><![CDATA[imperitive]]></category>
		<category><![CDATA[Lamport]]></category>
		<category><![CDATA[LazyEvaluation]]></category>
		<category><![CDATA[logic]]></category>
		<category><![CDATA[map]]></category>
		<category><![CDATA[Martin]]></category>
		<category><![CDATA[monad]]></category>
		<category><![CDATA[OOP]]></category>
		<category><![CDATA[option]]></category>
		<category><![CDATA[PureFunction]]></category>
		<category><![CDATA[reduce]]></category>
		<category><![CDATA[Result]]></category>
		<category><![CDATA[Scala]]></category>
		<category><![CDATA[side-effect]]></category>
		<category><![CDATA[TLA+]]></category>
		<category><![CDATA[一等公民]]></category>
		<category><![CDATA[不可变性]]></category>
		<category><![CDATA[元组]]></category>
		<category><![CDATA[函数]]></category>
		<category><![CDATA[函数式]]></category>
		<category><![CDATA[函数式编程思维]]></category>
		<category><![CDATA[函数式设计]]></category>
		<category><![CDATA[函数组合]]></category>
		<category><![CDATA[列表]]></category>
		<category><![CDATA[副作用]]></category>
		<category><![CDATA[命令式]]></category>
		<category><![CDATA[惰性求值]]></category>
		<category><![CDATA[抽象]]></category>
		<category><![CDATA[接口]]></category>
		<category><![CDATA[映射]]></category>
		<category><![CDATA[模式匹配]]></category>
		<category><![CDATA[泛型编程]]></category>
		<category><![CDATA[纯函数]]></category>
		<category><![CDATA[递归]]></category>
		<category><![CDATA[逻辑编程]]></category>
		<category><![CDATA[闭包]]></category>
		<category><![CDATA[面向对象编程]]></category>
		<category><![CDATA[高阶函数]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=4244</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2024/08/11/understand-functional-programming-in-go 一个孩子要尝试10次、20次才肯接受一种新的食物，我们接受一种新的范式，大概不会比这个简单。&#8211; 郭晓刚 《函数式编程思维》译者 函数式编程(Functional Programming, 简称fp)是一种编程范式，与命令式编程(Imperative Programming)、面向对象编程(OOP)、泛型编程(Generics Programming)、逻辑编程(logic Programming)等是一类的概念。 注：尽管面向对象范式引入了新的编程思想和技术，但它本质上与命令式编程一样，都关注程序的状态和如何通过改变状态来控制程序的执行流程，因此，OOP仍然属于命令式编程的一个分支。OOP可以看作是命令式编程的一种扩展和补充，它增强了代码的模块化、复用性和可维护性。在接下来，我会统一使用命令式编程范式来指代它们。 但几十年的编程语言的演进和实践已经证明：函数式编程并非银弹，它有优势，更有不足。从编程社区的实际反映来看，纯函数式编程语言(比如：CLisp、Haskell、Scala、Clojure、Erlang等)始终处于小众地位。此外，即便很多主流命令式编程语言在近些年融入了一些函数式编程的语法特性，采用函数式风格的代码依旧比例极低，且不易被广泛接受。许多程序员在面对复杂的状态管理和副作用时，依然倾向于使用传统的命令式编程风格（包括OOP)。。 注：Go就原生提供了一些支持函数式范式编程的语法特性，比如：函数是一等公民(first-class)、高阶函数、闭包、函数迭代器以及泛型等。 造成这种局面的原因众说纷纭，但我认为有如下几个： 首先从人类这个物种的大脑的认知和思维方式来看，命令式编程更接近于人类的自然思维方式，其逻辑与人类解决问题时的逻辑思维相似，即都是以步骤的形式理解问题，且有明确的控制流：命令式语言的控制结构（如条件语句、选择语句和循环）使得程序的执行路径清晰可见，符合人类的直觉理解，这也使得命令式语言更容易被人类大脑所掌握。 其次，命令式编程强调状态的变化，程序员可以直接看到和控制变量的变化，这与人类处理现实世界事物的方式相似。 在上面原因的驱使下，久而久之，程序员便形成习惯与传统，有了积淀，便可以促进命令式编程语言在教育和产业中的广泛应用，使得大多数程序员习惯于这种编程方式（间接挤压了函数式编程的使用空间）。进而使得命令式语言有更丰富的学习资源和社区支持，程序员也更容易找到帮助和示例。 也就是说，命令式编程范式占据主流的根本原因是人类的大脑本身就是命令式的，而不是函数式的。不过也有极少数大脑是函数式思维的，比如发明了TLA+这门形式化建模和验证语言的Leslie Lamport老先生。 那么问题来了！既然学习函数式编程思维是违反人类大脑直觉的，且较为困难，那为什么还是有很多人学习函数式编程思维，并在实际开发中应用函数式编程范式呢？关于这个问题，我们可以从两方面来看。 从主观上说，程序员经常有探索新技术和新范式的内在动力，这种好奇心驱使他们尝试函数式编程，也就是我们俗称的“玩腻了，尝尝鲜儿”。并且，许多程序员视学习函数式编程为一种智力挑战，一种来自舒适区之外的挑战，这种挑战能带来成就感和个人成长。此外，在竞争激烈的IT行业，掌握多种编程范式可以使得个人技能多样化，增加个人的职业竞争力。 从客观上看，函数式编程也确实能帮助程序员提高抽象思维和系统设计能力，这种能力的提升不仅限于函数式编程，还能应用到其他编程范式中。并且，函数式编程为程序员提供了一个新的解决问题的视角和方法，特别是在处理并发和并行计算、复杂数据转换和流处理方面。 学习函数式编程范式，并不是说抛弃命令式范式(或其他范式)，而是融合，从主流编程语言对函数式编程的语法特性的支持也可窥见这般。 那么，到底什么是函数式编程范式？它与命令式范式对比又有怎么样的差异与优劣呢？在这篇文章中，我就来说说我的体会，并辅以Go示例来帮助大家理解。 1. 思维差异：命令式编程 vs. 函数式编程 在看过很多函数式编程的资料后（见文后的参考资料一节），我问了自己一个问题：面对同一个实际的问题，用命令式编程范式和用函数式编程范式的核心思维差异在哪里？为此，我基于现实世界的一个典型问题模型(数据输入 -> 数据处理 -> 处理结果输出)，并根据自己的理解画了下面两幅图： 命令式编程范式的思维 函数式编程范式的思维 我们先来描述一下上面两幅图中的数据处理流程： 命令式编程：通过I/O操作获取数据，然后解码为自定义类型进行处理，再编码为自定义类型以便I/O操作输出。处理过程中使用函数、带方法的类型和控制流结构（如for、if、switch等）。 函数式编程：通过带有副作用的操作（如I/O操作）获取数据，然后解码数据放入通用数据结构（如列表、元组、映射）进行处理，再放入通用数据结构以便通过副作用操作输出。处理过程中会使用纯函数、高阶函数以及它们的函数组合。 基于上述流程的说明，我们可以看出两种范式核心关注点的差异： 命令式编程范式：更关注类型的封装、类型间的耦合关系、行为集合的抽象(接口)以及对数据在类型实例间的传递的显式控制(if/for/switch)。 函数式编程范式：弱化类型的概念，使用通用数据结构，专注于通过纯函数/高阶函数、不可变数据和函数组合来实现对数据的处理逻辑。“控制流”更加隐含，比如会通过递归、模式匹配和惰性求值等方式实现。建立专门的抽象来应对与真实世界交互时的带有副作用(side effect)的操作。 下面我们通过一个具体的问题来大致体会一下不同编程泛型在解决问题的实现上的思维差异。这个问题很简单：编写一个程序从input.txt文件中读取数字(每行一个数字)，将每个数字乘以2，然后将结果写入output.txt文件中。 我们先来用命令式编程范式实现： // fp-in-go/double/go/main.go // NumberData represents the input data type [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/understand-functional-programming-in-go-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2024/08/11/understand-functional-programming-in-go">本文永久链接</a> &#8211; https://tonybai.com/2024/08/11/understand-functional-programming-in-go</p>
<blockquote>
<p>一个孩子要尝试10次、20次才肯接受一种新的食物，我们接受一种新的范式，大概不会比这个简单。&#8211; 郭晓刚 《函数式编程思维》译者</p>
</blockquote>
<p>函数式编程(Functional Programming, 简称fp)是一种编程范式，与命令式编程(Imperative Programming)、面向对象编程(OOP)、泛型编程(Generics Programming)、<a href="https://tonybai.com/2012/05/08/translate-seven-languages-in-seven-weeks/">逻辑编程(logic Programming)</a>等是一类的概念。</p>
<blockquote>
<p>注：尽管面向对象范式引入了新的编程思想和技术，但它本质上与命令式编程一样，都关注程序的状态和如何通过改变状态来控制程序的执行流程，因此，OOP仍然属于命令式编程的一个分支。OOP可以看作是命令式编程的一种扩展和补充，它增强了代码的模块化、复用性和可维护性。在接下来，我会统一使用命令式编程范式来指代它们。</p>
</blockquote>
<p>但几十年的编程语言的演进和实践已经证明：<strong>函数式编程并非银弹</strong>，它有优势，更有不足。从编程社区的实际反映来看，纯函数式编程语言(比如：<a href="https://tonybai.com/2011/08/30/c-programers-tame-common-lisp-series-introduction/">CLisp</a>、<a href="https://www.haskell.org">Haskell</a>、Scala、Clojure、Erlang等)始终处于小众地位。此外，即便很多主流命令式编程语言在近些年融入了一些函数式编程的语法特性，采用函数式风格的代码依旧比例极低，且不易被广泛接受。许多程序员在面对复杂的状态管理和副作用时，依然倾向于使用传统的命令式编程风格（包括OOP)。。</p>
<blockquote>
<p>注：Go就原生提供了一些支持函数式范式编程的语法特性，比如：函数是一等公民(first-class)、高阶函数、<a href="https://tonybai.com/2021/08/09/when-variables-captured-by-closures-are-recycled-in-go">闭包</a>、<a href="https://tonybai.com/2024/06/24/range-over-func-and-package-iter-in-go-1-23/">函数迭代器</a>以及<a href="https://tonybai.com/2022/05/20/solving-problems-in-generic-function-implementation-using-named-return-values">泛型</a>等。</p>
</blockquote>
<p>造成这种局面的原因众说纷纭，但我认为有如下几个：</p>
<p>首先从人类这个物种的大脑的认知和思维方式来看，命令式编程更接近于人类的自然思维方式，其逻辑与人类解决问题时的逻辑思维相似，即都是以步骤的形式理解问题，且有明确的控制流：命令式语言的控制结构（如条件语句、选择语句和循环）使得程序的执行路径清晰可见，符合人类的直觉理解，这也使得命令式语言更容易被人类大脑所掌握。</p>
<p>其次，命令式编程强调状态的变化，程序员可以直接看到和控制变量的变化，这与人类处理现实世界事物的方式相似。</p>
<p>在上面原因的驱使下，久而久之，程序员便形成习惯与传统，有了积淀，便可以促进命令式编程语言在教育和产业中的广泛应用，使得大多数程序员习惯于这种编程方式（间接挤压了函数式编程的使用空间）。进而使得命令式语言有更丰富的学习资源和社区支持，程序员也更容易找到帮助和示例。</p>
<p>也就是说，命令式编程范式占据主流的根本原因是<strong>人类的大脑本身就是命令式的</strong>，而不是函数式的。不过也有极少数大脑是函数式思维的，比如发明了<a href="https://tonybai.com/2024/08/05/formally-verify-concurrent-go-programs-using-tla-plus/">TLA+这门形式化建模和验证语言</a>的<a href="https://lamport.azurewebsites.net">Leslie Lamport老先生</a>。</p>
<p>那么问题来了！既然学习函数式编程思维是违反人类大脑直觉的，且较为困难，那为什么还是有很多人学习函数式编程思维，并在实际开发中应用函数式编程范式呢？关于这个问题，我们可以从两方面来看。</p>
<p>从主观上说，程序员经常有探索新技术和新范式的内在动力，这种好奇心驱使他们尝试函数式编程，也就是我们俗称的“玩腻了，尝尝鲜儿”。并且，许多程序员视学习函数式编程为一种智力挑战，一种来自舒适区之外的挑战，这种挑战能带来成就感和个人成长。此外，在竞争激烈的IT行业，掌握多种编程范式可以使得个人技能多样化，增加个人的职业竞争力。</p>
<p>从客观上看，函数式编程也确实能帮助程序员提高抽象思维和系统设计能力，这种能力的提升不仅限于函数式编程，还能应用到其他编程范式中。并且，函数式编程为程序员提供了一个新的解决问题的视角和方法，特别是在处理并发和并行计算、复杂数据转换和流处理方面。</p>
<p>学习函数式编程范式，并不是说抛弃命令式范式(或其他范式)，而是<strong>融合</strong>，从主流编程语言对函数式编程的语法特性的支持也可窥见这般。</p>
<p>那么，到底什么是函数式编程范式？它与命令式范式对比又有怎么样的差异与优劣呢？在这篇文章中，我就来说说我的体会，并辅以Go示例来帮助大家理解。</p>
<h2>1. 思维差异：命令式编程 vs. 函数式编程</h2>
<p>在看过很多函数式编程的资料后（见文后的参考资料一节），我问了自己一个问题：面对同一个实际的问题，用命令式编程范式和用函数式编程范式的核心思维差异在哪里？为此，我基于现实世界的一个典型问题模型(数据输入 -> 数据处理 -> 处理结果输出)，并根据自己的理解画了下面两幅图：</p>
<p><img src="https://tonybai.com/wp-content/uploads/understand-functional-programming-in-go-3.png" alt="" /><br />
<center>命令式编程范式的思维</center></p>
<p><img src="https://tonybai.com/wp-content/uploads/understand-functional-programming-in-go-2.png" alt="" /><br />
<center>函数式编程范式的思维</center></p>
<p>我们先来描述一下上面两幅图中的数据处理流程：</p>
<ul>
<li>
<p>命令式编程：通过I/O操作获取数据，然后解码为自定义类型进行处理，再编码为自定义类型以便I/O操作输出。处理过程中使用函数、带方法的类型和控制流结构（如for、if、switch等）。</p>
</li>
<li>
<p>函数式编程：通过带有副作用的操作（如I/O操作）获取数据，然后解码数据放入通用数据结构（如列表、元组、映射）进行处理，再放入通用数据结构以便通过副作用操作输出。处理过程中会使用纯函数、高阶函数以及它们的函数组合。</p>
</li>
</ul>
<p>基于上述流程的说明，我们可以看出两种范式核心关注点的差异：</p>
<ul>
<li>命令式编程范式：更关注类型的封装、类型间的耦合关系、行为集合的抽象(接口)以及对数据在类型实例间的传递的显式控制(if/for/switch)。</li>
<li>函数式编程范式：弱化类型的概念，使用通用数据结构，专注于通过纯函数/高阶函数、不可变数据和函数组合来实现对数据的处理逻辑。“控制流”更加隐含，比如会通过递归、模式匹配和惰性求值等方式实现。建立专门的抽象来应对与真实世界交互时的带有副作用(side effect)的操作。</li>
</ul>
<p>下面我们通过一个具体的问题来大致体会一下不同编程泛型在解决问题的实现上的思维差异。这个问题很简单：编写一个程序从input.txt文件中读取数字(每行一个数字)，将每个数字乘以2，然后将结果写入output.txt文件中。</p>
<p>我们先来用命令式编程范式实现：</p>
<pre><code>// fp-in-go/double/go/main.go

// NumberData represents the input data
type NumberData struct {
    numbers []int
}

// ProcessedData represents the processed output data
type ProcessedData struct {
    numbers []int
}

// NewNumberData creates and returns a new NumberData instance
func NewNumberData() *NumberData {
    return &amp;NumberData{numbers: []int{}}
}

// AddNumber adds a number to NumberData
func (nd *NumberData) AddNumber(num int) {
    nd.numbers = append(nd.numbers, num)
}

// Process doubles all numbers in NumberData and returns ProcessedData
func (nd *NumberData) Process() ProcessedData {
    processed := ProcessedData{numbers: make([]int, len(nd.numbers))}
    for i, num := range nd.numbers {
        processed.numbers[i] = num * 2
    }
    return processed
}

// FileProcessor handles file operations and data processing
type FileProcessor struct {
    inputFile  string
    outputFile string
}

// NewFileProcessor creates and returns a new FileProcessor instance
func NewFileProcessor(input, output string) *FileProcessor {
    return &amp;FileProcessor{
        inputFile:  input,
        outputFile: output,
    }
}

// ReadAndDeserialize reads data from input file and deserializes it into NumberData
func (fp *FileProcessor) ReadAndDeserialize() (*NumberData, error) {
    file, err := os.Open(fp.inputFile)
    if err != nil {
        return nil, fmt.Errorf("error opening input file: %w", err)
    }
    defer file.Close()

    data := NewNumberData()
    scanner := bufio.NewScanner(file)
    for scanner.Scan() {
        num, err := strconv.Atoi(scanner.Text())
        if err != nil {
            return nil, fmt.Errorf("error converting to number: %w", err)
        }
        data.AddNumber(num)
    }

    if err := scanner.Err(); err != nil {
        return nil, fmt.Errorf("error reading input file: %w", err)
    }

    return data, nil
}

// SerializeAndWrite serializes ProcessedData and writes it to output file
func (fp *FileProcessor) SerializeAndWrite(data ProcessedData) error {
    file, err := os.Create(fp.outputFile)
    if err != nil {
        return fmt.Errorf("error creating output file: %w", err)
    }
    defer file.Close()

    writer := bufio.NewWriter(file)
    defer writer.Flush()

    for _, num := range data.numbers {
        _, err := writer.WriteString(fmt.Sprintf("%d\n", num))
        if err != nil {
            return fmt.Errorf("error writing to output file: %w", err)
        }
    }

    return nil
}

// Process orchestrates the entire data processing workflow
func (fp *FileProcessor) Process() error {
    // Read and deserialize input data
    inputData, err := fp.ReadAndDeserialize()
    if err != nil {
        return err
    }

    // Process data
    processedData := inputData.Process()

    // Serialize and write output data
    err = fp.SerializeAndWrite(processedData)
    if err != nil {
        return err
    }

    return nil
}

func main() {
    processor := NewFileProcessor("input.txt", "output.txt")
    if err := processor.Process(); err != nil {
        fmt.Fprintf(os.Stderr, "Error: %v\n", err)
        os.Exit(1)
    }
    fmt.Println("Processing completed successfully.")
}
</code></pre>
<p>这段代码十分容易理解，在这段代码中，我们建立了三个类型：NumberData、ProcessedData和FileProcessor。前两个分别代表解码后的输入数据和编码前的输出数据，FileProcessor则是封装了文件操作和数据处理的逻辑的自定义类型。这段代码将文件I/O、数据处理和主要流程控制分离到不同的方法中。在读取和写入过程中，数据经历了字符串 -> NumberData -> ProcessedData -> 字符串的转换过程，同时数据也是<strong>在不同类型的方法间传递和变换状态</strong>。</p>
<p>接下来我们再来看看函数式范式版本，Go虽然提供了一些函数式编程的基础支持，比如一等公民的函数、支持高阶函数、闭包等，但一些像monad、monoid等高级概念还需要手工实现。<a href="https://github.com/IBM/fp-go">IBM开源了一个Go的函数式编程基础库fp-go</a>，这里就借用fp-go的便利实现上面的同等功能，我们看看风格上有何不同：</p>
<pre><code>// fp-in-go/double/fp-go/main.go

package main

import (
    "bufio"
    "fmt"
    "os"
    "strconv"
    "strings"

    "github.com/IBM/fp-go/either"
    "github.com/IBM/fp-go/ioeither"
)

// 读取文件内容
func readFile(filename string) ioeither.IOEither[error, string] {
    return ioeither.TryCatchError(func() (string, error) {
        content, err := os.ReadFile(filename)
        return string(content), err
    })
}

// 将字符串转换为数字列表
func parseNumbers(content string) either.Either[error, []int] {
    numbers := []int{}
    scanner := bufio.NewScanner(strings.NewReader(content))
    for scanner.Scan() {
        num, err := strconv.Atoi(scanner.Text())
        if err != nil {
            return either.Left[[]int](err)
        }
        numbers = append(numbers, num)
    }
    return either.Right[error](numbers)
}

// 将数字乘以2
func multiplyBy2(numbers []int) []int {
    result := make([]int, len(numbers))
    for i, num := range numbers {
        result[i] = num * 2
    }
    return result
}

// 将结果写入文件
func writeFile(filename string, content string) ioeither.IOEither[error, string] {
    return ioeither.TryCatchError(func() (string, error) {
        return "", os.WriteFile(filename, []byte(content), 0644)
    })
}

func main() {
    program := ioeither.Chain(func(content string) ioeither.IOEither[error, string] {
        return ioeither.FromEither(
            either.Chain(func(numbers []int) either.Either[error, string] {
                multiplied := multiplyBy2(numbers)
                result := []string{}
                for _, num := range multiplied {
                    result = append(result, strconv.Itoa(num))
                }
                return either.Of[error](strings.Join(result, "\n"))
            })(parseNumbers(content)),
        )
    })(readFile("input.txt"))

    program = ioeither.Chain(func(content string) ioeither.IOEither[error, string] {
        return writeFile("output.txt", content)
    })(program)

    result := program()
    err := either.ToError(result)

    if err != nil {
        fmt.Println("Program failed:", err)
    } else {
        fmt.Println("Program completed successfully")
    }
}
</code></pre>
<p>相对于前面使用命令式范式风格的代码，这段函数式范式的代码理解起来就要难上不少。</p>
<p>不过，这段代码很好地诠释了函数式编程中的函数组合理念，我们看到函数被当作值来传递和使用。例如，在ioeither.Chain中，我们传递了匿名函数作为参数，这体现了函数式编程中函数作为一等公民的概念。multiplyBy2函数是一个纯函数的例子，它没有副作用，对于相同的输入总是产生相同的输出。这种纯函数更容易测试和推理。</p>
<p>代码中最明显的函数组合例子是在main函数中，我们使用ioeither.Chain来组合多个函数操作。并且在这里，我们将文件读取、内容处理和文件写入操作串联在一起，形成一个更大的操作。而ioeither.Chain和either.Chain又都是高阶函数的例子，它们接受其他函数作为参数并返回新的函数。Either和IOEither类型也是函数式编程中用于错误处理的主流方式，允许我们以更函数式的方式处理错误，将错误处理集成到函数组合中。</p>
<p>很多人好奇如果用纯函数式编程语言实现这个示例会是什么样子的，下面我就贴一段Haskell语言的代码，大家简单了解一下，这里就不对代码进行解释了：</p>
<pre><code>// fp-in-go/double/fp-haskell/Main.hs

import System.IO
import Control.Monad (when)
import Text.Read (readMaybe)
import Data.Maybe (catMaybes)

-- Define a custom type for the result
data DoubledNumbers = DoubledNumbers { doubledNumbers :: [Int] } deriving (Show)

-- Function to read numbers from a file
readNumbers :: FilePath -&gt; IO (Either String [Int])
readNumbers filePath = do
    content &lt;- readFile filePath
    let numbers = catMaybes (map readMaybe (lines content))
    return $ if null numbers
             then Left "No valid numbers found."
             else Right numbers

-- Function to write result to a file
writeResult :: FilePath -&gt; DoubledNumbers -&gt; IO (Either String ())
writeResult filePath result = do
    let resultString = unlines (map show (doubledNumbers result))
    writeFile filePath resultString
    return $ Right ()

-- Function to double the numbers
doubleNumbers :: [Int] -&gt; DoubledNumbers
doubleNumbers numbers = DoubledNumbers { doubledNumbers = map (* 2) numbers }

main :: IO ()
main = do
    -- Read numbers from input.txt
    readResult &lt;- readNumbers "input.txt"
    case readResult of
        Left err -&gt; putStrLn $ "Error: " ++ err
        Right numbers -&gt; do
            let result = doubleNumbers numbers
            -- Write result to output.txt
            writeResultResult &lt;- writeResult "output.txt" result
            case writeResultResult of
                Left err -&gt; putStrLn $ "Error: " ++ err
                Right () -&gt; putStrLn "Successfully written the result to output.txt."
</code></pre>
<blockquote>
<p>注：安装ghc后，执行ghc &#8211;make Main就可以将上面Main.hs编译为一个可执行程序。更多关于haskell编译器的信息可以到<a href="https://www.haskell.org">haskell官网</a>查看。</p>
</blockquote>
<p>从上面的示例我们大致也能感受到两种范式在思维层面的差异，正如Robert Martin在《函数式设计》一书中说道的那样：<strong>函数式程序更倾向于铺设调节数据流转换的管道结构，而可变的命令式程序更倾向于迭代地处理一个个类型对象</strong>。</p>
<p>我们很难在一个例子中体现出函数式编程的所有概念和思维特点，接下来，我们就来逐个说说函数式编程范式中的要素，你也可以对应前面的图中的内容，反复感受函数式编程的思维特点。</p>
<h2>2. 函数式编程的要素</h2>
<blockquote>
<p>面向对象的编程通过封装不确定因素来使代码能被人理解，而函数式编程通过尽量减少不确定因素来使代码能被人理解。—— Michael Feathers 《<a href="https://book.douban.com/subject/2248759/">修改代码的艺术</a>》一书作者</p>
</blockquote>
<p>函数式编程建立在几个核心要素之上，这些要素共同构成了函数式编程的基础。让我们逐一探讨这些要素。</p>
<h3>2.1 纯函数 (Pure Functions)</h3>
<p>纯函数是函数式编程的基石。一个纯函数具有以下特性:</p>
<ul>
<li>对于相同的输入，总是产生相同的输出；</li>
<li>不会产生副作用(不会修改外部状态)；</li>
<li>不依赖外部状态。</li>
</ul>
<p>例如，前面fp-go示例中的multiplyBy2就是一个纯函数:</p>
<pre><code>func multiplyBy2(numbers []int) []int {
    result := make([]int, len(numbers))
    for i, num := range numbers {
        result[i] = num * 2
    }
    return result
}
</code></pre>
<p>这个函数总是为相同的输入返回相同的结果，并且不会修改任何外部状态。</p>
<h3>2.2 不可变性 (Immutability)</h3>
<p>Robert Martin在《函数式设计》一书为函数式编程下一个理想的定义：没有赋值语句的编程。实质是其强调了不可变性在函数式编程范式中的重要意义。在没有赋值语句的情况下，代码通常基于对原状态的计算而得到新的状态，而对原状态没有任何修改。</p>
<p>在Go语言中，由于不支持不可变变量(很多语言用val关键字来声明不可变变量，但Go并不支持)，我们通常通过复制对象来实现不可变性，这可以帮助我们避免状态变化带来的复杂性，但也因为复制而增加了内存开销和性能成本。</p>
<pre><code>// 定义一个不可变的结构体
type Point struct {
    x, y int
}

// 创建一个新的 Point，模拟不可变性
func NewPoint(x, y int) Point {
    return Point{x, y}
}

// 移动Point的方法，返回一个新的Point
func (p Point) Move(dx, dy int) Point {
    return NewPoint(p.x+dx, p.y+dy)
}
</code></pre>
<h3>2.3 高阶函数 (Higher-Order Functions)与函数组合(Function Composition)</h3>
<p>Go语言的一个内置特性让它具备了使用函数式编程范式的前提，那就是<strong>在Go中，函数是一等公民</strong>。这意味着函数可以像其他类型变量一样，被赋值、传参和返回。</p>
<p>而接受其他函数作为参数或返回函数的函数，被称为<strong>高阶函数</strong>，这也是函数式编程的基石，如下面的applyOperation函数就是一个高阶函数：</p>
<pre><code>func applyOperation(x int, operation func(int) int) int {
    return operation(x)
}

func double(x int) int {
    return x * 2
}

result := applyOperation(5, double) // 结果为10
</code></pre>
<p>而有了对高阶函数的支持，我们才能运用函数式思维中的核心思维：函数组合，来铺设调节数据流转换的管道结构：</p>
<pre><code>// fp-in-go/high-order-func/main.go

package main

import (
    "fmt"
)

// 定义一个类型为函数的别名
type IntTransformer func(int) int

// 将多个转换函数组合成一个管道
func pipe(value int, transformers ...IntTransformer) int {
    for _, transformer := range transformers {
        value = transformer(value)
    }
    return value
}

// 定义一些转换函数
func addOne(x int) int {
    return x + 1
}

func square(x int) int {
    return x * x
}

func main() {
    // 使用管道处理数据
    result := pipe(3, addOne, square)
    fmt.Println("Result:", result) // 输出 Result: 16
}
</code></pre>
<p>这个示例中的pipe函数接受一个初始值和多个转换函数，并将其串联执行。main函数调用pipe函数，将addOne和square两个转换函数连接起来并执行输出结果。</p>
<p>前面那个使用fp-go编写的示例中，使用ioeither.Chain构建的program也是一个函数调用组合。</p>
<p>此外，链式调用也是一种在日常开发中常见的函数组合的使用形式，它<strong>融合了命令式的类型和函数式编程的函数组合</strong>，特别适用于集合类型数据的处理，通过链式调用，可以以更简洁和直观的方式进行数据转换和处理。下面是一个基于泛型实现的通用的链式调用(filter -> map -> reduce)的示例：</p>
<pre><code>// fp-in-go/func-composition/main.go

package main

import "fmt"

// Collection 接口定义了通用的集合操作
type Collection[T any] interface {
    Filter(predicate func(T) bool) Collection[T]
    Map(transform func(T) T) Collection[T]
    Reduce(initialValue T, reducer func(T, T) T) T
}

// SliceCollection 是基于切片的集合实现
type SliceCollection[T any] struct {
    data []T
}

// NewSliceCollection 创建一个新的 SliceCollection
func NewSliceCollection[T any](data []T) *SliceCollection[T] {
    return &amp;SliceCollection[T]{data: data}
}

// Filter 实现了 Collection 接口的 Filter 方法
func (sc *SliceCollection[T]) Filter(predicate func(T) bool) Collection[T] {
    result := make([]T, 0)
    for _, item := range sc.data {
        if predicate(item) {
            result = append(result, item)
        }
    }
    return &amp;SliceCollection[T]{data: result}
}

// Map 实现了 Collection 接口的 Map 方法
func (sc *SliceCollection[T]) Map(transform func(T) T) Collection[T] {
    result := make([]T, len(sc.data))
    for i, item := range sc.data {
        result[i] = transform(item)
    }
    return &amp;SliceCollection[T]{data: result}
}

// Reduce 实现了 Collection 接口的 Reduce 方法
func (sc *SliceCollection[T]) Reduce(initialValue T, reducer func(T, T) T) T {
    result := initialValue
    for _, item := range sc.data {
        result = reducer(result, item)
    }
    return result
}

// SetCollection 是基于 map 的集合实现
type SetCollection[T comparable] struct {
    data map[T]struct{}
}

// NewSetCollection 创建一个新的 SetCollection
func NewSetCollection[T comparable]() *SetCollection[T] {
    return &amp;SetCollection[T]{data: make(map[T]struct{})}
}

// Add 向 SetCollection 添加元素
func (sc *SetCollection[T]) Add(item T) {
    sc.data[item] = struct{}{}
}

// Filter 实现了 Collection 接口的 Filter 方法
func (sc *SetCollection[T]) Filter(predicate func(T) bool) Collection[T] {
    result := NewSetCollection[T]()
    for item := range sc.data {
        if predicate(item) {
            result.Add(item)
        }
    }
    return result
}

// Map 实现了 Collection 接口的 Map 方法
func (sc *SetCollection[T]) Map(transform func(T) T) Collection[T] {
    result := NewSetCollection[T]()
    for item := range sc.data {
        result.Add(transform(item))
    }
    return result
}

// Reduce 实现了 Collection 接口的 Reduce 方法
func (sc *SetCollection[T]) Reduce(initialValue T, reducer func(T, T) T) T {
    result := initialValue
    for item := range sc.data {
        result = reducer(result, item)
    }
    return result
}

// ToSlice 实现了 Collection 接口的 ToSlice 方法
func (sc *SetCollection[T]) ToSlice() []T {
    result := make([]T, 0, len(sc.data))
    for item := range sc.data {
        result = append(result, item)
    }
    return result
}

func main() {
    // 使用 SliceCollection
    numbers := NewSliceCollection([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
    result := numbers.
        Filter(func(n int) bool { return n%2 == 0 }).
        Map(func(n int) int { return n * 2 }).
        Reduce(0, func(acc, n int) int { return acc + n })
    fmt.Println(result) // 输出: 60

    // 使用 SetCollection
    set := NewSetCollection[int]()
    for _, n := range []int{1, 2, 2, 3, 3, 3, 4, 5} {
        set.Add(n)
    }
    uniqueSum := set.
        Filter(func(n int) bool { return n &gt; 2 }).
        Map(func(n int) int { return n * n }).
        Reduce(0, func(acc, n int) int { return acc + n })
    fmt.Println(uniqueSum) // 输出: 50 (3^2 + 4^2 + 5^2)
}
</code></pre>
<p>这段代码定义的泛型接口类型Collection包含三个方法：</p>
<ul>
<li>Filter：根据条件过滤集合中的元素。</li>
<li>Map：对集合中的每个元素应用转换函数。</li>
<li>Reduce：对集合中的元素进行归约操作，比如求和。</li>
</ul>
<p>其中Filtre、Map都是返回集合自身，这样便允许实现Collection接口的集合类型(如上面的SetCollection和SliceCollection)使用链式调用，代码看起来也十分易于理解。</p>
<h3>2.4 递归(Recursion)</h3>
<p>递归是函数式编程中常用的控制结构，常用来替代循环。例如下面是计算阶乘的函数实现：</p>
<pre><code>func factorial(n int) int {
    if n &lt;= 1 {
        return 1
    }
    return n * factorial(n-1)
}
</code></pre>
<p>递归的优点十分明显，代码简洁，易于理解(相对于循环)，特别适合处理分解问题（如树结构、图遍历等）。但不足也很突出，比如可能导致栈溢出(尤其是对那些不支持尾递归优化的语言，比如Go)，特别是对于较大的输入。此外，由于每次递归调用都需要创建新栈帧，维护栈状态，递归会有额外的性能开销。调试递归函数也可能比循环更复杂，因为需要跟踪多个函数调用。</p>
<h3>2.5 惰性求值 (Lazy Evaluation)</h3>
<p>惰性求值是指延迟计算表达式的值，直到真正需要它的时候。这样可以避免不必要的计算并有效管理内存，特别是在处理大集合或无限集合时。下面是用惰性求值实现迭代集合元素的示例：</p>
<blockquote>
<p>注：Go原生并不支持惰性求值的语法，但我们可以使用闭包来模拟。</p>
</blockquote>
<pre><code>// fp-in-go/lazy-evaluation/lazy-range/main.go

package main

import "fmt"

func lazyRange(start, end int) func() (int, bool) {
    current := start
    return func() (int, bool) {
        if current &gt;= end {
            return 0, false
        }
        result := current
        current++
        return result, true
    }
}
func main() {
    next := lazyRange(1, 5)
    for {
        value, hasNext := next()
        if !hasNext {
            break
        }
        fmt.Println(value)
    }
}
</code></pre>
<p>我们看到这段代码通过惰性求值方式生成从1到4的数字，避免了预先生成整个范围的集合元素，节省了内存，并避免了不必要的计算。</p>
<p>我们再来看一个用惰性求值生成前N个斐波那契数列的示例：</p>
<pre><code>// fp-in-go/lazy-evaluation/fibonacci/main.go

package main

import (
    "fmt"
)

// Fibonacci 返回一个生成无限斐波那契数列的函数
func Fibonacci() func() int {
    a, b := 0, 1
    return func() int {
        a, b = b, a+b
        return a
    }
}

func main() {
    fib := Fibonacci()
    for i := 0; i &lt; 10; i++ { // 打印前10个斐波那契数
        fmt.Println(fib())
    }
}
</code></pre>
<p>我们看到Fibonacci函数返回一个闭包，每次调用时生成下一个斐波那契数，这样我们在需要时生成下一个斐波那契数，而无需生成所有。</p>
<p>虽然函数式编程强调纯函数和不可变性，但在实际应用中，我们不可避免地需要处理副作用，如I/O操作、数据库交互等。接下来，我们就来看看在函数式编程范式中是如何处理带有副作用的操作的。</p>
<h2>3. 函数式编程对副作用操作的处理</h2>
<h3>3.1 理解副作用</h3>
<p>在函数式编程中，副作用是指函数或表达式在执行过程中对其周围环境产生的任何可观察到的变化。这些变化包括但不限于：</p>
<ul>
<li>修改全局变量或静态局部变量</li>
<li>修改函数参数</li>
<li>执行I/O操作（读写文件、网络通信等）</li>
<li>抛出异常或错误</li>
<li>调用其他具有副作用的函数</li>
</ul>
<p>副作用使得程序的行为变得难以预测和测试，因为函数的输出不仅依赖于其输入，还依赖于程序的状态和外部环境。函数式编程通过最小化副作用来提高程序的可预测性和可测试性。</p>
<h3>3.2 Monad: 函数式编程中处理副作用的核心抽象</h3>
<p>在函数式编程中，Monad是一种用于处理副作用的核心抽象。它提供了一种结构化的方式来处理计算中的状态、异常、输入输出等副作用，使得程序更加模块化和可组合。</p>
<p>在范畴论中，Monad被定义为一个自函子(endofunctor)加上两个自然变换(有点抽象了)：</p>
<ul>
<li>return (也称为unit)：将一个值封装到Monad中。</li>
<li>bind (也称为flatMap或>>=)：将一个Monad中的值应用到一个函数中，并返回一个新的Monad。</li>
</ul>
<blockquote>
<p>注：要入门范畴论，可以参考《<a href="https://book.douban.com/subject/30357114/">Category Theory for Programmers</a>》这本书。</p>
</blockquote>
<p>Monad可以通过以下策略来处理副作用：</p>
<ul>
<li>延迟执行：将副作用操作封装在Monad中，但不立即执行，这样可以将副作用推迟到程序的边缘。</li>
<li>显式表示：使副作用成为类型系统的一部分，迫使开发者显式地处理这些效果。</li>
<li>组合性：提供了一种方式来组合包含副作用的操作，而不破坏函数的纯粹性。</li>
<li>错误处理：提供了一种统一的方式来处理可能失败的操作。</li>
<li>状态管理：允许在一系列操作中传递和修改状态，而不需要使用可变变量。</li>
</ul>
<p>在实际应用中，我们可以根据具体需求选择使用不同的Monad实现。每种Monad都有其适用场景，比如：</p>
<ul>
<li>使用Option(Maybe) Monad处理可能缺失的值，避免空指针异常。</li>
<li>使用Result(Either) Monad 处理可能失败的操作，提供更丰富的错误信息。</li>
<li>使用IO Monad封装所有的I/O操作，将副作用推迟到程序的边缘。</li>
</ul>
<p>接下来，我们就结合Go示例来逐一探讨这三种Monad实现。</p>
<h3>3.3 Option (Maybe)</h3>
<p>Option 用于表示一个值可能存在或不存在，避免了使用null或undefined带来的问题。</p>
<pre><code>// fp-in-go/side-effect/option/main.go

package main

import "fmt"

type Option[T any] struct {
    value   T
    present bool
}

func Some[T any](x T) Option[T] {
    return Option[T]{value: x, present: true}
}

func None[T any]() Option[T] {
    return Option[T]{present: false}
}

func (o Option[T]) Bind(f func(T) Option[T]) Option[T] {
    if !o.present {
        return None[T]()
    }
    return f(o.value)
}

// 使用示例
func safeDivide(a, b int) Option[int] {
    if b == 0 {
        return None[int]()
    }
    return Some(a / b)
}

func main() {
    result := Some(10).Bind(func(x int) Option[int] {
        return safeDivide(x, 2)
    })
    fmt.Println(result) // {5 true}

    result = Some(10).Bind(func(x int) Option[int] {
        return safeDivide(x, 0)
    })
    fmt.Println(result) // {0 false}
}
</code></pre>
<p>这段示例程序定义了一个Option结构体：包含一个值和一个表示值是否存在的布尔变量。Some和None函数是Option的创建函数，Some函数：返回一个包含值的Option。None函数返回一个不包含值的Option。Bind方法对Option中的值应用一个函数，如果值不存在则返回None。</p>
<h3>3.4 Result (Either)</h3>
<p>Result可用于处理可能产生错误的操作，它比Option提供了更多的信息，它可以可以携带错误信息。</p>
<pre><code>// fp-in-go/side-effect/result/main.go

package main

import (
    "fmt"
    "os"
    "strings"
)

type Result[T any] struct {
    value T
    err   error
    isOk  bool
}

func Ok[T any](value T) Result[T] {
    return Result[T]{value: value, isOk: true}
}

func Err[T any](err error) Result[T] {
    return Result[T]{err: err, isOk: false}
}

func (r Result[T]) Bind(f func(T) Result[T]) Result[T] {
    if !r.isOk {
        return Err[T](r.err)
    }
    return f(r.value)
}

// 使用示例
func readFile(filename string) Result[string] {
    content, err := os.ReadFile(filename)
    if err != nil {
        return Err[string](err)
    }
    return Ok(string(content))
}

func processContent(content string) Result[string] {
    // 处理内容...
    return Ok(strings.ToUpper(content))
}

func main() {
    result := readFile("input.txt").Bind(processContent)
    fmt.Println(result) // {HELLO, GOLANG &lt;nil&gt; true}
    result = readFile("input1.txt").Bind(processContent)
    fmt.Println(result) // { 0xc0000a0420 false}
}
</code></pre>
<p>这段示例程序定义了一个Result结构体：包含一个值、一个错误信息和一个表示操作是否成功的布尔变量。Ok和Err函数是Result的创建函数，Ok函数返回一个成功的Result。Err函数返回一个失败的Result。Bind方法对成功的Result中的值应用一个函数，如果操作失败则返回错误。</p>
<p>在示例中，我们分别用读取input.txt和不存在的input1.txt来演示成功和错误的两个情况，具体输出结果见上面代码中的注释。</p>
<h3>3.5 IO Monad</h3>
<p>IO Monad用于封装所有的带有副作用的输入/输出操作，使得这些操作在类型系统中可见，并且可以被推迟执行。</p>
<pre><code>// fp-in-go/side-effect/io-monad/main.go

package main

import (
    "fmt"
    "os"
    "strings"
)

// IO represents an IO operation that, when run, produces a value of type any or an error
type IO struct {
    run func() (any, error)
}

// NewIO creates a new IO monad
func NewIO(f func() (any, error)) IO {
    return IO{run: f}
}

// Bind chains IO operations, allowing for type changes
func (io IO) Bind(f func(any) IO) IO {
    return NewIO(func() (any, error) {
        v, err := io.run()
        if err != nil {
            return nil, err
        }
        return f(v).run()
    })
}

// Map transforms the value inside IO
func (io IO) Map(f func(any) any) IO {
    return io.Bind(func(v any) IO {
        return NewIO(func() (any, error) {
            return f(v), nil
        })
    })
}

// Pure lifts a value into the IO context
func Pure(x any) IO {
    return NewIO(func() (any, error) { return x, nil })
}

// ReadFile is an IO operation that reads a file
func ReadFile(filename string) IO {
    return NewIO(func() (any, error) {
        content, err := os.ReadFile(filename)
        if err != nil {
            return nil, fmt.Errorf("failed to read file: %w", err)
        }
        return string(content), nil
    })
}

// WriteFile is an IO operation that writes to a file
func WriteFile(filename string, content string) IO {
    return NewIO(func() (any, error) {
        err := os.WriteFile(filename, []byte(content), 0644)
        if err != nil {
            return nil, fmt.Errorf("failed to write file: %w", err)
        }
        return true, nil
    })
}

// Print is an IO operation that prints to stdout
func Print(x any) IO {
    return NewIO(func() (any, error) {
        fmt.Println(x)
        return x, nil
    })
}

func main() {
    // Example: Read a file, transform its content, and write it back
    program := ReadFile("input.txt").
        Map(func(v any) any {
            return strings.ToUpper(v.(string))
        }).
        Bind(func(v any) IO {
            return WriteFile("output.txt", v.(string))
        }).
        Bind(func(v any) IO {
            success := v.(bool)
            if success {
                return Pure("File processed successfully")
            }
            return Pure("Failed to process file")
        }).
        Bind(func(v any) IO {
            return Print(v)
        })

    // Run the IO operation
    result, err := program.run()
    if err != nil {
        fmt.Printf("An error occurred: %v\n", err)
    } else {
        fmt.Printf("Program completed: %s\n", result)
    }
}
</code></pre>
<p>这个示例提供了一个非泛型版本的IO Monad的Go实现，它允许我们链式组合带有副作用的IO操作，同时保持了一定程度的类型安全（尽管需要类型断言）。在实际使用中，你完全不用自己实现IO Monad，可以直接使用IBM/fp-go中的ioeither，就像本文初那个示例那样。</p>
<h2>4. 小结</h2>
<p>到这里，关于函数式编程思维的入门介绍就告一段落了！</p>
<p>通过上面的介绍，我们看到函数式编程提供了一种不同于传统命令式编程的思维方式。它强调不可变性、纯函数和函数的组合，为数据流的处理搭建管道，这些特性使得代码更易于理解、测试和并行化。然而，函数式编程也带来了一些挑战，如处理副作用和状态管理的复杂性和难于理解。</p>
<p>学习函数式编程不仅可以扩展我们的编程技能，还能帮助我们以新的方式思考问题和设计解决方案。正如《函数式编程思维》一书中译者所说，接受一种新的编程范式可能需要时间和耐心，但最终会带来新的见解和能力。</p>
<p>在实际应用中，纯粹的函数式编程并不常见，更常见的是将函数式编程的概念和技术与其他编程范式(主要就是命令式范式)相结合。</p>
<p>Go语言虽然不是一个纯函数式语言，但它提供了足够的特性来支持函数式编程风格，如一等公民的函数、闭包和高阶函数等。</p>
<p>最后要记住，编程范式是工具，而不是教条。好的程序员应该能够根据具体问题和场景，灵活地选择和组合不同的编程范式，以创造出最优雅、高效的解决方案。</p>
<p>本文涉及的源码可以在<a href="https://github.com/bigwhite/experiments/blob/master/fp-in-go">这里</a>下载 &#8211; https://github.com/bigwhite/experiments/blob/master/fp-in-go</p>
<p>本文部分源代码由Claude 3.5 sonnet和GPT-4o生成。</p>
<h2>5. 参考资料</h2>
<ul>
<li>《<a href="https://book.douban.com/subject/36974785/">函数式设计：原则、模式与实践</a>》- https://book.douban.com/subject/36974785/</li>
<li>《<a href="https://book.douban.com/subject/26587213/">函数式编程思维</a>》- https://book.douban.com/subject/26587213/</li>
<li>《<a href="https://book.douban.com/subject/36787585/">计算机程序的构造和解释</a>》- https://book.douban.com/subject/36787585/</li>
<li>《<a href="https://book.douban.com/subject/30165168/">Learning Functional Programming in Go</a>》 &#8211; https://book.douban.com/subject/30165168/</li>
<li><a href="https://www.youtube.com/watch?v=Jif3jL6DRdw">Introduction to fp-go, functional programming for golang</a> &#8211; https://www.youtube.com/watch?v=Jif3jL6DRdw</li>
<li><a href="https://betterprogramming.pub/investigate-functional-programming-concepts-in-go-1dada09bc913">Investigate Functional Programming Concepts in Go</a> &#8211; https://betterprogramming.pub/investigate-functional-programming-concepts-in-go-1dada09bc913</li>
<li><a href="https://medium.com/better-programming/investigating-the-i-o-monad-in-go-3c0fabbb4b3d">Investigating the I/O Monad in Go</a> &#8211; https://medium.com/better-programming/investigating-the-i-o-monad-in-go-3c0fabbb4b3d</li>
</ul>
<hr />
<p><a href="https://public.zsxq.com/groups/51284458844544">Gopher部落知识星球</a>在2024年将继续致力于打造一个高品质的Go语言学习和交流平台。我们将继续提供优质的Go技术文章首发和阅读体验。同时，我们也会加强代码质量和最佳实践的分享，包括如何编写简洁、可读、可测试的Go代码。此外，我们还会加强星友之间的交流和互动。欢迎大家踊跃提问，分享心得，讨论技术。我会在第一时间进行解答和交流。我衷心希望Gopher部落可以成为大家学习、进步、交流的港湾。让我相聚在Gopher部落，享受coding的快乐! 欢迎大家踊跃加入！</p>
<p><img src="http://image.tonybai.com/img/tonybai/gopher-tribe-zsxq-small-card.png" alt="img{512x368}" /><br />
<img src="http://image.tonybai.com/img/tonybai/go-programming-from-beginner-to-master-qr.png" alt="img{512x368}" /></p>
<p><img src="http://image.tonybai.com/img/tonybai/go-first-course-banner.png" alt="img{512x368}" /><br />
<img src="http://image.tonybai.com/img/tonybai/imooc-go-column-pgo-with-qr.jpg" alt="img{512x368}" /></p>
<p>著名云主机服务厂商DigitalOcean发布最新的主机计划，入门级Droplet配置升级为：1 core CPU、1G内存、25G高速SSD，价格5$/月。有使用DigitalOcean需求的朋友，可以打开这个<a href="https://m.do.co/c/bff6eed92687">链接地址</a>：https://m.do.co/c/bff6eed92687 开启你的DO主机之路。</p>
<p>Gopher Daily(Gopher每日新闻) &#8211; https://gopherdaily.tonybai.com</p>
<p>我的联系方式：</p>
<ul>
<li>微博(暂不可用)：https://weibo.com/bigwhite20xx</li>
<li>微博2：https://weibo.com/u/6484441286</li>
<li>博客：tonybai.com</li>
<li>github: https://github.com/bigwhite</li>
<li>Gopher Daily归档 &#8211; https://github.com/bigwhite/gopherdaily</li>
</ul>
<p><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。</p>
<p style='text-align:left'>&copy; 2024, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2024/08/11/understand-functional-programming-in-go/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>GoCN社区Go读书会第二期：《Go语言精进之路》</title>
		<link>https://tonybai.com/2022/07/07/gocn-community-go-book-club-issue2-go-programming-from-beginner-to-master/</link>
		<comments>https://tonybai.com/2022/07/07/gocn-community-go-book-club-issue2-go-programming-from-beginner-to-master/#comments</comments>
		<pubDate>Thu, 07 Jul 2022 11:40:39 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Amazon]]></category>
		<category><![CDATA[atomic]]></category>
		<category><![CDATA[benchmark]]></category>
		<category><![CDATA[B站]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[Cgo]]></category>
		<category><![CDATA[Channel]]></category>
		<category><![CDATA[crypto]]></category>
		<category><![CDATA[CSP]]></category>
		<category><![CDATA[defer]]></category>
		<category><![CDATA[delve]]></category>
		<category><![CDATA[error]]></category>
		<category><![CDATA[for]]></category>
		<category><![CDATA[Function]]></category>
		<category><![CDATA[functrace]]></category>
		<category><![CDATA[fuzzing]]></category>
		<category><![CDATA[GC]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[go-coding-in-go-way]]></category>
		<category><![CDATA[go-module]]></category>
		<category><![CDATA[go-test]]></category>
		<category><![CDATA[go.mod]]></category>
		<category><![CDATA[Go1]]></category>
		<category><![CDATA[go1.18]]></category>
		<category><![CDATA[GoCN]]></category>
		<category><![CDATA[GODEBUG]]></category>
		<category><![CDATA[GODEUG]]></category>
		<category><![CDATA[gofmt]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[Gopher]]></category>
		<category><![CDATA[gopherchina]]></category>
		<category><![CDATA[goroutine]]></category>
		<category><![CDATA[Go语言学习笔记]]></category>
		<category><![CDATA[Go语言精进之路]]></category>
		<category><![CDATA[Haskell]]></category>
		<category><![CDATA[http]]></category>
		<category><![CDATA[https]]></category>
		<category><![CDATA[if]]></category>
		<category><![CDATA[init]]></category>
		<category><![CDATA[Interface]]></category>
		<category><![CDATA[iota]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[leanpub]]></category>
		<category><![CDATA[Method]]></category>
		<category><![CDATA[metrics]]></category>
		<category><![CDATA[Module]]></category>
		<category><![CDATA[Package]]></category>
		<category><![CDATA[panic]]></category>
		<category><![CDATA[pitfalls]]></category>
		<category><![CDATA[pprof]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[receiver]]></category>
		<category><![CDATA[Reflect]]></category>
		<category><![CDATA[runtime]]></category>
		<category><![CDATA[RussCox]]></category>
		<category><![CDATA[select]]></category>
		<category><![CDATA[strings]]></category>
		<category><![CDATA[sync]]></category>
		<category><![CDATA[time]]></category>
		<category><![CDATA[TLS]]></category>
		<category><![CDATA[trap]]></category>
		<category><![CDATA[unsafe]]></category>
		<category><![CDATA[亚马逊]]></category>
		<category><![CDATA[代码风格]]></category>
		<category><![CDATA[作用域]]></category>
		<category><![CDATA[共享内存]]></category>
		<category><![CDATA[内存分配]]></category>
		<category><![CDATA[写书]]></category>
		<category><![CDATA[出书]]></category>
		<category><![CDATA[出版]]></category>
		<category><![CDATA[函数]]></category>
		<category><![CDATA[切片]]></category>
		<category><![CDATA[包导入]]></category>
		<category><![CDATA[华章书院]]></category>
		<category><![CDATA[原子操作]]></category>
		<category><![CDATA[反射]]></category>
		<category><![CDATA[变量声明]]></category>
		<category><![CDATA[坑]]></category>
		<category><![CDATA[垃圾回收]]></category>
		<category><![CDATA[复合字面值]]></category>
		<category><![CDATA[字符串]]></category>
		<category><![CDATA[导读]]></category>
		<category><![CDATA[常量]]></category>
		<category><![CDATA[并发]]></category>
		<category><![CDATA[并发编程]]></category>
		<category><![CDATA[接口]]></category>
		<category><![CDATA[文字稿]]></category>
		<category><![CDATA[方法]]></category>
		<category><![CDATA[机械工业出版社]]></category>
		<category><![CDATA[标准库]]></category>
		<category><![CDATA[汇编]]></category>
		<category><![CDATA[泛型]]></category>
		<category><![CDATA[泛读]]></category>
		<category><![CDATA[直播]]></category>
		<category><![CDATA[简单]]></category>
		<category><![CDATA[精读]]></category>
		<category><![CDATA[组合]]></category>
		<category><![CDATA[编程思维]]></category>
		<category><![CDATA[自助出版]]></category>
		<category><![CDATA[自媒体]]></category>
		<category><![CDATA[自带电池]]></category>
		<category><![CDATA[行动清单]]></category>
		<category><![CDATA[设计哲学]]></category>
		<category><![CDATA[语言]]></category>
		<category><![CDATA[运行时]]></category>
		<category><![CDATA[错误处理]]></category>
		<category><![CDATA[面向工程]]></category>
		<category><![CDATA[项目布局]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=3610</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2022/07/07/gocn-community-go-book-club-issue2-go-programming-from-beginner-to-master 本文是2022年6月26日我在GoCN社区的Go读书会第二期《Go语言精进之路》直播的文字稿。本文对直播的内容做了重新整理与修订，供喜欢阅读文字的朋友们在收看直播后的揣摩和参考。视频控的童鞋可以关注GoCN公众号和视频号看剪辑后的视频，也可以在B站GopherChina专区下收看视频回放(https://www.bilibili.com/video/BV1p94y1R7jg)。 大家晚上好，我叫白明，是《Go语言精进之路》一书的作者，也是tonybai.com的博主，很荣幸今天参加GoCN社区Go读书会第二期，分享一下我个人在写书和读书方面的经验和体会。 今天的分享包括三方面内容： 写书的历程。一些Gopher可能比较好奇，这么厚的一套书是怎么写出来的，今天就和大家聊一聊。 《Go语言精进之路》导读。主要是把这本书的整体构思与大家聊聊，希望通过这个导读帮助读者更好地阅读和理解这套书。 我个人的读书方法与经验的简要分享。 首先和大家分享一下写书的历程。 一. 写书的历程 1. 程序员的“小目标”与写书三要素 今天收看直播的童鞋都是有追求的技术人员，可能心底都有写一本属于自己的书的小目标。这样可以把自己学习到的知识、技能和经验以比较系统的方式输出给其他人，可以帮助其他人快速学习和掌握本领域的知识、技能和经验。 当然写书还有其他好处，比如：提升名气、更容易混技术圈子、可能给你带来更好的职业发展机会，当然也会给你带来一些额外的副业收入，至于多少，还要看书籍的口碑与销量。 那怎么才能写书呢？作为“过来人”，我总结了三个要素，也是三个条件。 第一个要素是能力。 这个很容易理解。以Go为例，如果你没有在Go语言方面的知识、技能的沉淀，没有对Go语言方方面面的较为深入的理解，你很难写出一本口碑很好的书籍。尤其是那种有原创性、独到见解的著书。而不是对前人资料做系统整理摘抄的编书。编书更常见于教材、字典等。显然著书对作者水平的要求更高。 第二个要素是意愿。 写过书的同学都有体会，写书是一件辛苦活。需要你在正式工作之余付出大量业余时间伏案创作。并且对于小众技术类书籍来说，写书能带来的金钱上的收益和你付出的时长和精力不成正比。就这个问题，我曾与机械工业出版社的营销编辑老师聊过，得到的信息是：Go技术书籍的市场与Java、Python还没法比，即便是像Go语言圣经《Go程序设计语言》的销量也没法与Java、Python的头部书籍销量相比。 第三个要素是机会。 记得小时候十分羡慕那些能出书的人，觉得都是大神级的人物。不过那个时候出书的确很难，机会应该很少，你要不是在学术圈里混很难出书。如今就容易地多了，渠道也多了。每年出版社都有自己的出版计划，各个出版社的编辑老师也在根据计划在各种自媒体上、技术圈子中寻觅匹配的技术作者。 如果你有自己的思路，也可以整理出大纲，并通过某种方式联系到出版社老师，如果匹配就可以出。 另外国外流行电子自助出版，这也给很多技术作者很好的出版机会。比如国内作者老貘写的Go 101系列就是在亚马逊和leanpub上做的自助出版，效果还不错。 以上就是我总结的出书的三个要素，一旦集齐这三个要素呢，出书实际就是自然而然的一件事了。以我为例。 从能力方面来说呢，我大约从2011年开始接触和学习Go语言，算是国内较早的一批Go语言接纳者。Go语言2012年才发布1.0版本，因此那时我接触的Go时还是r60版本，还不是正式的1.0版本。从那时起就一直在跟踪Go演化，日常写一些Go项目的小程序。 Go 1.5实现自举并大幅降低GC延迟，我于是开始在一些生产环境使用Go，并逐渐将知识和经验做了沉淀，在自己的博客上不断做着Go相关内容的输出，反响也不错。 随着输出Go内容的增多，我发现以博客的形式输出，内容组织零散，于是我第一次有了将自己的Go知识系统整理并输出的意愿和想法。 我在实践Go的过程中收到很多Go初学者的提问：Go入门容易，但精进难，怎么才能像Go开发团队那样写出符合Go思维和语言惯例的高质量代码呢？这个问题引发了我的思考。在2017年GopherChina大会我以《go coding in go way》为主题，以演讲的形式尝试回答这个问题，但鉴于演讲的时长有限，很多内容没能展开，效果不甚理想。这进一步增强了我通过书籍的形式系统解答这个问题的意愿。 而当时我家大宝已经长大了，我也希望通过写书这个行动身体力行地给孩子树立一个正面的榜样。中国古语有云：言传身教，我也想践行一下。 机会就这样自然而然的来了！2018年初，机械工业出版社副总编杨福川老师在微信联系到我，和我探讨一下是否可以写一本类似于“Effective Go”的书，当时机械工业出版社华章出版社策划了Effective XXX(编写高质量XXX)系列图书，当时已经出版了C、Python等语言版本的书籍，还差Go语言的。我的出书意愿与出版社的需求甚是匹配，于是我答应的杨老师的要求，成为了这套丛书的Go版本的作者。 2. 写书的过程 我是2018下旬开始真正动笔的。 真正开始码字的时候，我才意识到，写书真不容易，要写出高质量书稿，的确需付出大量时间和汗水。每天晚上、早上都在构思、码字、写代码示例、画插图，睡眠时间很少。记得当时每周末都在奋笔疾书，陪伴家人尤其是孩子的时间很少。 另外我这个人还习惯于把一个知识点讲细讲透，这样每一节的篇幅都不小。因此，写作进展是很缓慢的，就这样，进度一再延期。好在编辑老师比较nice，考虑到书稿质量，没有狠狠催进度。 2020年11月末，我正式向出版社交了初稿，记得初稿有66条，近40w字。 又经过一年的排期、编辑、修订、排版，2021年12月下旬正式出版。 2022年1月《Go语言精进之路》正式上架到各个渠道货架。 到今天为止，出版了近六个月，这本书收获了还不错的口碑，在各个平台上的口碑都在8分以上(注：口碑分数还在动态变化，下图仅为当时的快照，不代表如今的分数)。 能获得大家的认可，让我很是欣慰，觉得写书过程付出的辛苦没有白费。 以上就是我的写书历程。总的来说一句话：写书不易，写高质量的书更难。 接下来我来进行一下《Go语言精进之路》一书的导读。 二. 《Go语言精进之路》导读 也许是“用力过猛”，《Go语言精进之路》一书写的太厚了，无法装订为一册。编辑老师建议装订为两册，即1、2册。很多同学好奇为什么不是上下册而是1、2册，这里是编辑老师的“高瞻远瞩”，目的是为后续可能的“续写”(比如第3册)留足空间，毕竟Go语言还在快速演进，目前的版本还不包含像泛型这样的新语法。不过，目前第3册还尚未列入计划。 [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/gocn-community-go-book-club-issue2-go-programming-from-beginner-to-master-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2022/07/07/gocn-community-go-book-club-issue2-go-programming-from-beginner-to-master">本文永久链接</a> &#8211; https://tonybai.com/2022/07/07/gocn-community-go-book-club-issue2-go-programming-from-beginner-to-master</p>
<p>本文是2022年6月26日我在<strong>GoCN社区的Go读书会第二期《Go语言精进之路》直播的文字稿</strong>。本文对直播的内容做了重新整理与修订，供喜欢阅读文字的朋友们在收看直播后的揣摩和参考。视频控的童鞋可以关注<strong>GoCN公众号和视频号</strong>看剪辑后的视频，也可以<a href="https://www.bilibili.com/video/BV1p94y1R7jg">在B站GopherChina专区下收看视频回放</a>(https://www.bilibili.com/video/BV1p94y1R7jg)。</p>
<hr />
<p>大家晚上好，我叫白明，是<a href="https://item.jd.com/13694000.html">《Go语言精进之路》</a>一书的作者，也是<a href="https://tonybai.com">tonybai.com</a>的博主，很荣幸今天参加GoCN社区Go读书会第二期，分享一下我个人在写书和读书方面的经验和体会。</p>
<p>今天的分享包括三方面内容：</p>
<ul>
<li>写书的历程。一些Gopher可能比较好奇，这么厚的一套书是怎么写出来的，今天就和大家聊一聊。</li>
<li>《Go语言精进之路》导读。主要是把这本书的整体构思与大家聊聊，希望通过这个导读帮助读者更好地阅读和理解这套书。</li>
<li>我个人的读书方法与经验的简要分享。</li>
</ul>
<p>首先和大家分享一下写书的历程。</p>
<h2>一. 写书的历程</h2>
<p><img src="https://tonybai.com/wp-content/uploads/gocn-community-go-book-club-issue2-go-programming-from-beginner-to-master-2.png" alt="" /></p>
<h3>1. 程序员的“小目标”与写书三要素</h3>
<p>今天收看直播的童鞋都是有追求的技术人员，可能心底都有写一本属于自己的书的小目标。这样可以把自己学习到的知识、技能和经验以比较系统的方式输出给其他人，可以帮助其他人快速学习和掌握本领域的知识、技能和经验。</p>
<p>当然写书还有其他好处，比如：提升名气、更容易混技术圈子、可能给你带来更好的职业发展机会，当然也会给你带来一些额外的副业收入，至于多少，还要看书籍的口碑与销量。</p>
<p>那怎么才能写书呢？作为“过来人”，我总结了三个要素，也是三个条件。</p>
<p>第一个要素是<strong>能力</strong>。</p>
<p>这个很容易理解。以Go为例，如果你没有在Go语言方面的知识、技能的沉淀，没有对Go语言方方面面的较为深入的理解，你很难写出一本口碑很好的书籍。尤其是那种有原创性、独到见解的著书。而不是对前人资料做系统整理摘抄的编书。编书更常见于教材、字典等。显然著书对作者水平的要求更高。</p>
<p>第二个要素是<strong>意愿</strong>。</p>
<p>写过书的同学都有体会，写书是一件辛苦活。需要你在正式工作之余付出大量业余时间伏案创作。并且对于小众技术类书籍来说，写书能带来的金钱上的收益和你付出的时长和精力不成正比。就这个问题，我曾与机械工业出版社的营销编辑老师聊过，得到的信息是：Go技术书籍的市场与Java、Python还没法比，即便是像Go语言圣经《Go程序设计语言》的销量也没法与Java、Python的头部书籍销量相比。</p>
<p>第三个要素是<strong>机会</strong>。</p>
<p>记得小时候十分羡慕那些能出书的人，觉得都是大神级的人物。不过那个时候出书的确很难，机会应该很少，你要不是在学术圈里混很难出书。如今就容易地多了，渠道也多了。每年出版社都有自己的出版计划，各个出版社的编辑老师也在根据计划在各种自媒体上、技术圈子中寻觅匹配的技术作者。</p>
<p>如果你有自己的思路，也可以整理出大纲，并通过某种方式联系到出版社老师，如果匹配就可以出。</p>
<p>另外国外流行电子自助出版，这也给很多技术作者很好的出版机会。比如国内作者老貘写的<a href="https://go101.org/">Go 101系列</a>就是在<a href="https://kdp.amazon.com/en_US/">亚马逊</a>和<a href="https://leanpub.com">leanpub</a>上做的自助出版，效果还不错。</p>
<p>以上就是我总结的出书的三个要素，一旦集齐这三个要素呢，出书实际就是自然而然的一件事了。以我为例。</p>
<p>从能力方面来说呢，我大约从2011年开始接触和学习Go语言，算是国内较早的一批Go语言接纳者。Go语言2012年才发布1.0版本，因此那时我接触的Go时还是r60版本，还不是正式的1.0版本。从那时起就一直在跟踪Go演化，日常写一些Go项目的小程序。</p>
<p>Go 1.5实现自举并大幅降低GC延迟，我于是开始在一些生产环境使用Go，并逐渐将知识和经验做了沉淀，在自己的博客上不断做着Go相关内容的输出，反响也不错。</p>
<p>随着输出Go内容的增多，我发现以博客的形式输出，内容组织零散，于是我第一次有了将自己的Go知识系统整理并输出的意愿和想法。</p>
<p>我在实践Go的过程中收到很多Go初学者的提问：Go入门容易，但精进难，怎么才能像Go开发团队那样写出符合Go思维和语言惯例的高质量代码呢？这个问题引发了我的思考。在2017年GopherChina大会我以<a href="https://tonybai.com/2017/04/20/go-coding-in-go-way/">《go coding in go way》</a>为主题，以演讲的形式尝试回答这个问题，但鉴于演讲的时长有限，很多内容没能展开，效果不甚理想。这进一步增强了我通过书籍的形式系统解答这个问题的意愿。</p>
<p>而当时我家<a href="https://daughter.tonybai.com">大宝</a>已经长大了，我也希望通过写书这个行动身体力行地<strong>给孩子树立一个正面的榜样</strong>。中国古语有云：<strong>言传身教</strong>，我也想践行一下。</p>
<p>机会就这样自然而然的来了！2018年初，机械工业出版社副总编杨福川老师在微信联系到我，和我探讨一下是否可以写一本类似于“Effective Go”的书，当时机械工业出版社华章出版社策划了Effective XXX(编写高质量XXX)系列图书，当时已经出版了C、Python等语言版本的书籍，还差Go语言的。我的出书意愿与出版社的需求甚是匹配，于是我答应的杨老师的要求，成为了这套丛书的Go版本的作者。</p>
<h3>2. 写书的过程</h3>
<p>我是2018下旬开始真正动笔的。</p>
<p>真正开始码字的时候，我才意识到，写书真不容易，要写出高质量书稿，的确需付出大量时间和汗水。每天晚上、早上都在构思、码字、写代码示例、画插图，睡眠时间很少。记得当时每周末都在奋笔疾书，陪伴家人尤其是孩子的时间很少。</p>
<p>另外我这个人还习惯于把一个知识点讲细讲透，这样每一节的篇幅都不小。因此，写作进展是很缓慢的，就这样，进度一再延期。好在编辑老师比较nice，考虑到书稿质量，没有狠狠催进度。</p>
<p>2020年11月末，我正式向出版社交了初稿，记得初稿有66条，近40w字。</p>
<p>又经过一年的排期、编辑、修订、排版，2021年12月下旬正式出版。</p>
<p>2022年1月《Go语言精进之路》正式上架到各个渠道货架。</p>
<p>到今天为止，出版了近六个月，这本书收获了还不错的口碑，在各个平台上的口碑都在8分以上(注：口碑分数还在动态变化，下图仅为当时的快照，不代表如今的分数)。</p>
<p><img src="https://tonybai.com/wp-content/uploads/gocn-community-go-book-club-issue2-go-programming-from-beginner-to-master-5.jpg" alt="" /><br />
<img src="https://tonybai.com/wp-content/uploads/gocn-community-go-book-club-issue2-go-programming-from-beginner-to-master-6.png" alt="" /></p>
<p>能获得大家的认可，让我很是欣慰，觉得写书过程付出的辛苦没有白费。</p>
<p>以上就是我的写书历程。总的来说一句话：<strong>写书不易，写高质量的书更难</strong>。</p>
<p>接下来我来进行一下《Go语言精进之路》一书的导读。</p>
<h2>二. 《Go语言精进之路》导读</h2>
<p><img src="https://tonybai.com/wp-content/uploads/gocn-community-go-book-club-issue2-go-programming-from-beginner-to-master-3.png" alt="" /></p>
<p>也许是“用力过猛”，《Go语言精进之路》一书写的太厚了，无法装订为一册。编辑老师建议装订为两册，即1、2册。很多同学好奇为什么不是上下册而是1、2册，这里是编辑老师的“高瞻远瞩”，目的是为后续可能的“续写”(比如第3册)留足空间，毕竟Go语言还在快速演进，目前的版本还不包含像<a href="https://tonybai.com/2022/04/20/some-changes-in-go-1-18">泛型这样的新语法</a>。不过，目前第3册还尚未列入计划。</p>
<p>本套书共分为10个部分，66个主题。第一册包含了前7个部分，后3部分在第二册中。</p>
<h3>1. 整体写作思路</h3>
<p>整套书围绕着两个前后关联的思路循序展开。</p>
<p>第一个思路我叫它：<strong>精进之路，思维先行</strong>。</p>
<p>第二个思路称为：<strong>践行哲学，遵循惯例，认清本质，理解原理</strong>。</p>
<p>我们先来看看第一个思路。</p>
<h3>2. 精进之路，思维先行</h3>
<p>收看直播的童鞋都不止学过一门编程语言。大家可能都有过这样的经历：你已经精通A语言，然后在学习B语言的时候用A语言的思维去写B代码，你会觉得写出的B代码很别扭，写出的代码总是感觉不是很地道，总觉得不是那种高质量的B语言代码。</p>
<p>其实，不仅学习编程语言是这样，学自然语言也是一样。最典型的一个例子，大家都学过十几年的英语，但毕业后能用地道的英语表达自己观点的人却不多，为什么呢？那就是我们总用中文的思维方式去组织英语的句子，去说英语，这样再怎么努力也很难上一个层次。</p>
<p>其实，很多语言大师早就意识到了这一点。下面是我收集的这些大师的关于语言与思维的论点，这里和大家分享一下：</p>
<blockquote>
<p>“语言决定思维方式” &#8211; 萨丕尔假说</p>
<p>“我的语言之局限，即我的世界之局限” &#8211;  路德维希·维特根斯坦，语言哲学的奠基人</p>
<p>“不能改变你思维方式的语言，不值得学习” &#8211; Alan Perlis（首届ACM图灵奖得主)</p>
</blockquote>
<p>我们看到：无论是自然语言界的大师，还是IT界的大佬，他们的观点异曲同工。总之一句话：<strong>语言要精进，思维要先行</strong>。</p>
<h3>3. Part1：进入Go语言编程思维导引</h3>
<p>正是因为意识到语言与思维的紧密关系，我在书的第一部分就安排了Go语言编程思维导引，希望大家意识到Go编程思维在语言精进之路上的重要性。</p>
<p>一门编程语言的思维也不是与生俱来的，而是在演进中逐步形成的。所以在这一部分，我安排了Go诞生与演进、Go设计哲学：简单、组合、并发、面向工程。这样做的目的是让大家一起了解Go语言设计者在设计Go语言时的所思所想，让读者站在语言设计者的高度理解Go语言与众不同的设计，认同Go语言的设计理念。因为这些是<strong>Go编程语言思维形成的“土壤”</strong>。</p>
<p>这一部分最后一节是Go编程思维举例导引，书中给出了C, Haskell和Go程序员在面对同一个问题时，首先考虑到的思维方式以及不同思维下代码设计方式的差异。</p>
<p>知道Go编程思维的重要性后，我们应该怎么做呢？</p>
<h3>4. 怎么学习Go编程思维？</h3>
<p>学习的本质是一种模仿。<strong>要学习Go思维，就要去模仿Go团队、Go社区的优秀项目和代码，看看他们怎么做的</strong>。这套书后面的部分讲的就是这个。而“践行哲学，遵循惯例，认清本质，理解原理”就是对后面内容的写作思路的概要性总结。</p>
<ul>
<li>践行哲学</li>
</ul>
<p>把Go设计哲学用于自己的项目的设计实践中，而不是仅停留在口头知道上。</p>
<ul>
<li>遵循惯例</li>
</ul>
<p>遵循Go团队的一些语言惯例，比如“comma，ok”、使用复合字面值初始化等，使用这些惯例你可以让你的代码显得很地道，别人一看就懂。</p>
<ul>
<li>认清本质</li>
</ul>
<p>为了更高效地利用语言机制，我们要认清一些语言机制背后的本质，比如切片、字符串在运行时的表示，这样一来既能帮助开发人员正确使用这些语法元素，同时也能避免入坑。</p>
<ul>
<li>理解原理</li>
</ul>
<p>Go带有运行时。运行时全程参与Go应用生命周期，因此，只有对Goroutine调度、GC等原理做适当了解，才能更好的发挥Go的威力。</p>
<p>这套书的part2-part10 就是基于对Go团队、Go社区优秀实践与惯例的梳理，用系统化的思路构建出来并循序渐进呈现给大家的。</p>
<h3>5. Part2 – 项目基础：布局、代码风格与命名</h3>
<p>这部门的内容是每个gopher在开启一个Go项目时都要考虑的事情。</p>
<ul>
<li>项目布局</li>
</ul>
<p>我见过很多Gopher问项目布局的事情，因为Go官方没有给出标准布局。本书讲解了Go项目的结构布局的演进历程以及Go社区的事实标准，希望能给大家提供足够的参考信息。</p>
<ul>
<li>代码风格</li>
</ul>
<p>针对Go代码风格，由于代码风格在Go中已经弱化，所以这里主要还是带大家理解gofmt存在的意义和使用方法。</p>
<ul>
<li>命名惯例</li>
</ul>
<p>关于命名，我不知道大家是否觉得命名难，但对我来说是挺难的，我总是绞尽脑汁在想用啥名(手动允悲)。所以我的原则是“代码未动，命名先行”。 对于Go中变量、标识符等的命名惯例这样的“关键的问题”，我使用了“笨方法”：我统计了Go标准库、Docker库、k8s库的命名情况，并分门别类给出不同语法元素的命名惯例，具体内容大家可以看书了解 。</p>
<h3>6. Part3 – 语法基础：声明、类型、语句与控制结构</h3>
<p>第三部分讲的很基础，但内容还是要高于基础的。</p>
<ul>
<li>一致的变量声明</li>
</ul>
<p>我们知道Go提供多种变量声明方式，但是在不同位置该用哪种声明方式可读性好又不容易造坑呢(尤其要注意短变量声明)？书中给出了系统阐述。</p>
<ul>
<li>无类型常量与iota</li>
</ul>
<p>大家都用过常量，但很多人对于无类型常量与有类型常量区别不了解，书中帮你做了总结。还有，很多人用过iota，但却不理解iota的真正含义以及它能帮你做啥。书中对iota的语义做了说明，对常见用途做了梳理。</p>
<ul>
<li>零值可用</li>
</ul>
<p>Go提倡零值可用，也内置了有很多零值可用类型，用起来很爽，比如：切片(不全是，仅在append时是零值可用，当用下标访问时，不具备零值可用)、sync包中的Mutex、RDMutex等</p>
<p>其实类比于线程（thread），goroutine也是一种零值可用的“类型”，只是Go没有goroutine这个类型罢了。</p>
<p>如果我们是包的设计者，如果提供零值可用的类型，可以提升包的使用者的体验。</p>
<ul>
<li>复合字面值来初始化</li>
</ul>
<p>使用复合字面值对相应的变量进行初始化是一个Go语言的惯例， Go虽然提供了new和make，但日常很少用，尤其是new。</p>
<ul>
<li>切片、字符串、map的原理、惯用法与坑</li>
</ul>
<p>Go是带有runtime的语言，语法层面展示的很多语法元素和runtime层真实的表示并不一致。要想高效利用这些类型，如果不了解runtime层表示还真不行。有时候还有很严重的“坑”。懂了，自然就能绕过坑。</p>
<ul>
<li>包导入</li>
</ul>
<p>Go源文件的import语句后面跟着的是包名还是包路径？Go编译是不是必须要有依赖项的源码才可以，只有.a是否可以？这些问题书中都有系统说明</p>
<ul>
<li>代码块与作用域</li>
</ul>
<p>代码块与作用域是Go语言的基础概念，虽然基础，如果理解不好，也是有“坑”的，比如最常见的变量遮蔽等。一旦理解透了，还可以帮你解决意想不到的语法问题和执行语义错误问题。</p>
<ul>
<li>控制语句</li>
</ul>
<p>Go倡导“一个问题只有一种解决方法”。Go针对每种控制语句仅提供一种语法形式。虽然仅有一种形式，用不好，一样容器掉坑。本套书总结了Go控制语句的惯用法与使用注意事项。</p>
<h3>7. Part4 – 语法基础：函数与方法</h3>
<p>我们日常编写的Go代码逻辑都在函数或方法中，函数/方法是Go程序逻辑的基本承载单元。</p>
<ul>
<li>init函数</li>
</ul>
<p>init函数是包初始化过程中执行的函数，它有很多特殊用途。并且其初始化顺序对程序执行语义也有影响，这方面要搞清楚。书中对init函数的常见用途做了梳理，比如database/sql包的驱动自注册模式等。</p>
<ul>
<li>成为“一等公民” </li>
</ul>
<p>在Go中，函数成为了“一等公民”。函数成为一等公民后可以像变量一样，被作为参数传递到函数中、作为返回值从函数中返回、作为右值赋值给其他变量等，书中系统讲解了这个特性都有哪些性质和特殊应用，比如函数式编程等。</p>
<ul>
<li>defer语句的惯用法与坑</li>
</ul>
<p>defer就是帮你简化代码逻辑的，书中总结了defer语句的应用模式。以及使用defer的注意事项，比如函数求值时机、使用开销等。</p>
<ul>
<li>变长参数函数</li>
</ul>
<p>Go支持变长参数函数。大家可以没有意识到：变长参数函数是我们日常用的最多的一类函数，比如append函数、fmt.Printf系列、log包中提供的按日志严重级别输出日志的函数等。</p>
<p>但变长参数函数可能也是我们自己设计与实现较少的一类函数形式。 变长参数函数能帮我们做什么呢？书中讲解了变长参数函数的常见用途，比如实现功能选项模式等。</p>
<ul>
<li>方法的本质、receiver参数类型选择、方法集合</li>
</ul>
<p>方法的本质其实是函数，弄清楚方法的本质可以帮助我们解决很多难题，书中以实例方式帮助大家理解这一点。</p>
<p>方法receiver参数类型的选择也是Go初学者的常见困惑，这里书中给出三个原则，参照这三个原则，receiver类型选择就不是问题了。</p>
<p>怎么确定一个类型是否实现接口？我们需要看类型的方法集合。那么确定一个类型方法集合就十分重要，尤其是那些包括类型嵌入的类型的方法集合，书中对这块内容做了系统的讲解。</p>
<h3>8. Part5 – 语法核心：接口</h3>
<ul>
<li>接口的内部表示</li>
</ul>
<p>接口是Go语言中的重要语法。Russ Cox曾说过：“如果要从Go语言中挑选出一个特性放入其他语言，我会选择接口”。可见接口的重要性。不过，用好接口类型的前提是理解接口在runtime层的表示，这一节会详细说明空接口与非空接口的内部表示。</p>
<ul>
<li>接口的设计惯例</li>
</ul>
<p>我们应该设计什么样的接口呢？ 大接口有何弊端？小接口有何优势？多小的接口算是合理的呢？这些在本节都有说明。</p>
<ul>
<li>接口与组合</li>
</ul>
<p>组合是Go的设计哲学，Go是关于组合的语言。接口在面向组合编程时将发挥重要作用。这里我将提到Go的两种组合方式：垂直组合和水平组合。其中接口类型在水平组合中起到的关键性的作用。书中还讲解了通过接口进行水平组合的几种模式：包裹模式、适配器函数、中间件等。</p>
<p>很多初学者告诉我，他们做了一段时间Go编码了，但还没有自己设计过接口，我建议这样的同学好好读读这一部分。</p>
<h3>9. Part6 – 语法核心：并发编程</h3>
<ul>
<li>并发设计vs并行设计</li>
</ul>
<p>学习并发编程首先要搞懂并发与并行的概念，书中用了一个很形象的机场安检的例子，来告诉大家并发与并行的区别。并发关乎结构，并行关注执行</p>
<ul>
<li>并发原语的原理与应用模式</li>
</ul>
<p>Go实现了csp模型，提供了goroutine、channel、select并发原语。</p>
<p>理解go并发编程。首先要深入理解基于goroutine的并发模型与调度方式。书中对这方面做了深入浅出的讲解，不涉及太多代码，相信大家都能看懂。</p>
<p>书中还对比了go并发模型，一种是csp，一种是传统的基于共享内存方式，并列举了Go并发的常见模式，比如创建、取消、超时、管道模式等。</p>
<p>另外，channel作为goroutine间通信的标准原语，有很多玩法，这里列举了常见的模式和使用注意事项。</p>
<ul>
<li>低级同步原语(sync和atomic)</li>
</ul>
<p>虽然有了CSP模型的并发原语，极大简化并发编程，但是sync包和原子操作也不能忘记，很多性能敏感的临界区还需要sync包/atomic这样的低级同步原语来同步。</p>
<h3>10. Part7 – 错误处理</h3>
<p>单独将错误处理拎出来，是因为很多人尤其是来自java的童鞋，习惯了try-catch-finally的结构化错误处理，看到go的错误处理就让其头疼。</p>
<p>Go语言十分重视错误处理，但它也的确有着相对保守的设计和显式处理错误的惯例。</p>
<p>本部分涵盖常见Go错误处理的策略、避免if err != nil写太多的方案，更为重要的是panic与错误处理的差别。我见过太多将panic用作正常处理的同学了。尤其是来自java阵营的童鞋。</p>
<h3>11. Part8 – 编程实践：测试、调试与性能剖析</h3>
<p>本部分聚焦编码之外的Go工具链工程实践。</p>
<ul>
<li>Go测试惯例与组织形式 </li>
</ul>
<p>这部分首先和大家聊聊go test包的组织形式，包括是选择包内测试还是包外测试？何时采用符合go惯例的表驱动的测试用例组织形式？如何管理测试依赖的外部数据文件等。</p>
<ul>
<li>模糊测试(fuzzing test)。</li>
</ul>
<p>这里的模糊测试并非基于go 1.18的原生<a href="https://tonybai.com/2021/12/01/first-class-fuzzing-in-go-1-18">fuzzing test</a>进行，写书的时候go 1.18版本尚未发布，而是基于德米特里-维尤科夫的<a href="http://tonybai.com/2015/12/08/go-fuzz-intro/">go-fuzz工具</a>。</p>
<ul>
<li>性能基准测试、度量数据与pprof性能剖析</li>
</ul>
<p>Go原生提供性能基准测试。这一节讲解了如何做性能基准测试、如何编写串行与并行的测试、性能基准测试结果比较工具以及如何排除额外干扰，让结果更准确等方面内容。在讲解pprof性能剖析工具时，我使用一个实例进行剖析讲解，这样理解起来更为直观。</p>
<ul>
<li>Go调试</li>
</ul>
<p>说到Go调试，我们日常使用最多的估计还是print大法。但在print大法之外，其实有一个事实标准的Go调试工具，它就是delve。在这一节中，我讲解了delve的工作原理以及使用delve如何实现并发调试、coredump调试以及在线挂接(attach)进程的调试。</p>
<h3>12. Part9 – 标准库、反射与cgo</h3>
<p>go是自带电池，开箱即用的语言，拥有高质量的标准库。在国外有些Gopher甚至倡导仅依赖标准库实现go应用。</p>
<ul>
<li>高频使用的标准库包（net、http、strings、time、crypto等)</li>
</ul>
<p>在这一节，我对高频使用的标准库包的原理和使用进行拆解分析，net、http、标准库io模型、strings、time、crypto等以帮助大家更高效的运用标准库。</p>
<ul>
<li>reflect包使用的三大法则</li>
</ul>
<p>reflect包为go提供了反射能力，书中对反射的实现原理做了讲解，重点是reflect使用的三大法则。</p>
<ul>
<li>cgo使用</li>
</ul>
<p>cgo不是go，但是cgo机制是使用go与c交互的唯一手段。书中对cgo的用法与约束做了详细讲解，尤其是在cgo开启的情况下如何做静态编译值得大家细读。</p>
<ul>
<li>unsafe包的安全使用法则</li>
</ul>
<p>事实证明unsafe包很有用，但要做到安全使用unsafe包，尤其是unsafe.Pointer，需要遵循一定的安全使用法则。书中对此做了举例详细说明。</p>
<p>反射、cgo、unsafe算是高级话题，要透彻理解，需要多阅读几遍书中内容并结合实践。</p>
<h3>13. Part10 – 工程实践</h3>
<ul>
<li>go module</li>
</ul>
<p>go module在<a href="https://tonybai.com/2018/11/19/some-changes-in-go-1-11/">go 1.11版本</a>中引入go，在<a href="https://tonybai.com/2021/02/25/some-changes-in-go-1-16">go 1.16版本</a>中成为go官方默认构建模式。go程序员入门go，精进go都跨不过go module这道坎儿。书中对go module构建模式做了超级系统的讲解：从go构建模式演进历史、go module的概念、原理、惯例、升降级major版本的操作，到使用注意事项等。不过这里还有有一些瑕疵，那就是go module这一节放置的位置太靠后了，应该往往前面提提。如果后面有修订版，可以考虑这么做。</p>
<ul>
<li>自定义go包导入路径</li>
</ul>
<p>书中还给出了一个自定义go包导入路径的一种实现方案，十分适合组织内部的私有仓库，有兴趣的同学可以重点看看。</p>
<ul>
<li>go命令的使用模式详解</li>
</ul>
<p>这一节将go命令分门别类地进行详细说明。包括：</p>
<pre><code>- 获取与安装的go get/go install
- go包检视的go list
- go包构建的go build
- 运行与诊断的GODEBUG、GOGC等环境变量的功用
- 代码静态检查与重构
- 文档查看
- go代码生成go generate
</code></pre>
<ul>
<li>Go常见的“坑”</li>
</ul>
<p>这一节将Go常见的“坑”进行了一次检阅。我这里将坑分为“语法类”和“标准库类”，并借鉴了央视五套天下足球top10节目，对每个坑的“遇坑指数”与“坑害指数”做了点评。</p>
<h3>14. 具备完整的示例代码与勘误表</h3>
<p>这套书拥有具备完整的示例代码与<a href="https://github.com/bigwhite/GoProgrammingFromBeginnerToMaster">勘误表</a>，它们都被持续维护，让大家没有读书的后顾之忧。</p>
<h2>三. 读书的实践与体会</h2>
<p><img src="https://tonybai.com/wp-content/uploads/gocn-community-go-book-club-issue2-go-programming-from-beginner-to-master-4.png" alt="" /></p>
<p>下面我再分享一下我个人是怎么读书的，包括go技术书籍的读书历程，以及关于读书的一些实践体会。</p>
<p>读书是千人千面的事，没有固定标准的。我的读书方法也不见得适合诸位。大家听听即可，觉得还不错，能借鉴上就最好了。</p>
<p>今天收看直播估计以gopher为主，所以首先说说Go语言书籍的阅读历程</p>
<h3>1. Go语言书籍阅读历程：先外后内</h3>
<p>对于IT技术类图书，初期还是要看原版的。这个没办法，因为it编程技术绝大多数来自国外。</p>
<p>我读的第一本Go技术书就是《the way to go》，至今这本书也没有引入国内。这是一本Go语言百科全书，大多数内容如今仍适用。唯一不足是该书成书于Go 1.0发布之前，使用的好像是r60版本，有少部分内容已经不适用。</p>
<p>后来Go 1.0发布后，我还陆续读过Addison-Wesley出版的《programming in go》和《The Go Programming Language Phrasebook》，两本书都还不错。</p>
<p>2015年末的布莱恩.克尼根和go核心团队的多诺万联合编写的《The Go Programming Language》，国内称之为Go圣经的书出版了，这让外文go技术书籍达到了巅峰，后来虽然也有go书籍书籍陆续出版，但都无法触及go圣经的地位。</p>
<p>说完外文图书，我再来说说中文Go图书的阅读历程。</p>
<p>我读过的第一本中文Go书籍是2012年许式伟老师的《Go语言编程》，很佩服许老师的眼光和魄力，七牛云很早就在生产用go。</p>
<p>第二本中文Go书籍是雨痕老师的《go学习笔记》，这也是国内第一本深入到go底层原理的书籍(后半部分)，遗憾的是书籍停留在go 1.5(还是go 1.6)的实现上，没有随Go版本演进而持续更新。</p>
<p>柴大和曹大合著的《go高级编程》也是一本不错的go技术书籍，如果你要深入学习cgo和go汇编，建议阅读此书。</p>
<p>后面的《Go语言底层原理剖析》和《Go语言设计与实现》也都是以深入了解Go运行机制为目标的书籍，口碑都很好，对这方面内容感兴趣的gopher，可以任意挑一本学习。</p>
<h3>2. 自己的读书方法</h3>
<p>我的读书方法其实不复杂，主要分为精读和泛读。</p>
<ul>
<li>阅读方式：好书精读，闲书泛读</li>
</ul>
<p>好书，集中一大段时间内进行阅读。 闲书(不烧脑)，通常是 碎片化阅读。</p>
<ul>
<li>精读方法：摘录+脑图+行动清单</li>
</ul>
<p>摘录就是将书中的观点和细节摘录出来，放到读书笔记，最好能用自己的语言重新描述出来，这样印象深刻，理解更为透彻。</p>
<p>脑图，概括书的思维脉络，防止读完就忘记。 通过脑图，我至少看着脉络能想起来。</p>
<p>行动清单：如果没有能输出行动清单，那这本书对你来说意义就不大。 什么是好书，好书就是那种看完后很迫切的想基于书中的观点做点什么。行动清单将有助于我在后续的行动中反复理解书中内容，提高知识的消化率和理解深度。</p>
<ul>
<li>泛读方法：碎片化+听书</li>
</ul>
<p>泛读主要是碎片化快读或听书，主要是坐地铁，坐公交，散步时。开车时在保证安全的前提下，可以用听书的方式。</p>
<h2>四. 小结</h2>
<p>本次分享了三块内容，这里小结一下：</p>
<ul>
<li>写书历程和写书三要素：能力 + 意愿 + 机会；</li>
<li>Go精进之路导读：思维先行，践行哲学，遵循惯例，认清本质，理解原理；</li>
<li>读书方法：选高质量图书精读(脑图+细节摘录+行动清单）。</li>
</ul>
<h2>五. Q&amp;A</h2>
<ul>
<li>在实际开发中有没有什么优雅的处理error的方法？</li>
</ul>
<p>建议看《Go语言精进之路》第一册第七部分中关于error处理的内容。</p>
<ul>
<li>是否在工作中使用过六边形架构以及依赖注入的处理经验?</li>
</ul>
<p>暂没有使用过六边形架构，生产中没有使用过Go第三方依赖注入的方案。</p>
<ul>
<li>后面会有泛型和模糊测试的补充么？</li>
</ul>
<p>从书籍内容覆盖全面性的角度而言，我个人有补充上述内容的想法，但还要看现在这套书的销售情况以及出版社的计划。目前还没列入个人工作计划。</p>
<ul>
<li>作者总结一系列go方法论、惯例等很实用，这种有逻辑的思考和见解是怎么形成的？</li>
</ul>
<p>没有特意考虑过是怎么形成的。个人平时喜欢多问自己几个为什么，形成让自己信服的工作和学习逻辑。(文字稿补充：同理心、多总结、多复盘、多输出)。</p>
<p>学习Go惯例、方法论，可以多多看Go语言开源项目自身的代码评审，看看Go contributor写代码的思路和如何评审其他贡献者的代码的。(文字稿补充：在这一过程中，潜移默化的感受Go编程思维)。</p>
<ul>
<li>如何阅读大型go项目的源码？</li>
</ul>
<p>我个人的方法就是自上而下。先拆分结构，然后找入口。如果是一个可执行的go程序，还是从入口层层的向后看。然后通过一些工具，比如我个人之前开发的<a href="https://tonybai.com/2020/12/10/a-kind-of-thinking-about-how-to-trace-function-call-chain">函数调用跟踪工具</a>，查看程序执行过程中的函数调用次序。</p>
<p>更细节的内容，还是要深入到代码中去查看。</p>
<ul>
<li>对Go项目中的一些设计模式的看法？如何使用设计模式，使用时注意哪些事项？</li>
</ul>
<p>设计模式在go语言中并不是一个经常拿出来提的东西。我之前的一个观点：在其他语言中，需要大家通过一些额外细心的设计构建出来的设计模式，在Go语言中是自然而然就有的东西。</p>
<p>我在自己的日常编码过程中，不会太多从如何应用设计模式的角度思考，而是按照go设计哲学，去考虑并发设计、组合的设计，而不是非要套用那23个经典设计模式。</p>
<hr />
<p><a href="https://wx.zsxq.com/dweb2/index/group/51284458844544">“Gopher部落”知识星球</a>旨在打造一个精品Go学习和进阶社群！高品质首发Go技术文章，“三天”首发阅读权，每年两期Go语言发展现状分析，每天提前1小时阅读到新鲜的Gopher日报，网课、技术专栏、图书内容前瞻，六小时内必答保证等满足你关于Go语言生态的所有需求！2022年，Gopher部落全面改版，将持续分享Go语言与Go应用领域的知识、技巧与实践，并增加诸多互动形式。欢迎大家加入！</p>
<p><img src="http://image.tonybai.com/img/tonybai/gopher-tribe-zsxq-small-card.png" alt="img{512x368}" /><br />
<img src="http://image.tonybai.com/img/tonybai/go-programming-from-beginner-to-master-qr.png" alt="img{512x368}" /></p>
<p><img src="http://image.tonybai.com/img/tonybai/go-first-course-banner.png" alt="img{512x368}" /><br />
<img src="http://image.tonybai.com/img/tonybai/imooc-go-column-pgo-with-qr.jpg" alt="img{512x368}" /></p>
<p><a href="https://tonybai.com/">我爱发短信</a>：企业级短信平台定制开发专家 https://tonybai.com/。smspush : 可部署在企业内部的定制化短信平台，三网覆盖，不惧大并发接入，可定制扩展； 短信内容你来定，不再受约束, 接口丰富，支持长短信，签名可选。2020年4月8日，中国三大电信运营商联合发布《5G消息白皮书》，51短信平台也会全新升级到“51商用消息平台”，全面支持5G RCS消息。</p>
<p>著名云主机服务厂商DigitalOcean发布最新的主机计划，入门级Droplet配置升级为：1 core CPU、1G内存、25G高速SSD，价格5$/月。有使用DigitalOcean需求的朋友，可以打开这个<a href="https://m.do.co/c/bff6eed92687">链接地址</a>：https://m.do.co/c/bff6eed92687 开启你的DO主机之路。</p>
<p>Gopher Daily(Gopher每日新闻)归档仓库 &#8211; https://github.com/bigwhite/gopherdaily</p>
<p>我的联系方式：</p>
<ul>
<li>微博：https://weibo.com/bigwhite20xx</li>
<li>博客：tonybai.com</li>
<li>github: https://github.com/bigwhite</li>
</ul>
<p><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。</p>
<p style='text-align:left'>&copy; 2022, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2022/07/07/gocn-community-go-book-club-issue2-go-programming-from-beginner-to-master/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>世界读书日：带你走近Go语言编程思维</title>
		<link>https://tonybai.com/2022/04/23/taking-a-closer-look-at-programming-thinking-in-go/</link>
		<comments>https://tonybai.com/2022/04/23/taking-a-closer-look-at-programming-thinking-in-go/#comments</comments>
		<pubDate>Fri, 22 Apr 2022 22:53:42 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[Cpp]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[Gopher]]></category>
		<category><![CDATA[goroutine]]></category>
		<category><![CDATA[Go语言精进之路]]></category>
		<category><![CDATA[Haskell]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[OO]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[世界读书日]]></category>
		<category><![CDATA[你一生的故事]]></category>
		<category><![CDATA[假说]]></category>
		<category><![CDATA[内存]]></category>
		<category><![CDATA[并发]]></category>
		<category><![CDATA[指针]]></category>
		<category><![CDATA[泛型]]></category>
		<category><![CDATA[素数筛]]></category>
		<category><![CDATA[编程思维]]></category>
		<category><![CDATA[萨丕尔—沃夫假说]]></category>
		<category><![CDATA[降临]]></category>
		<category><![CDATA[面向对象]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=3514</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2022/04/23/taking-a-closer-look-at-programming-thinking-in-go 经过十几年的演化和发展，Go语言在全世界范围内已经拥有了百万级别的拥趸，在这些开发者当中，除了一部分新入行的编程语言初学者之外，更多的是从其他编程语言阵营转过来的开发者。由于Go语言上手容易，在转Go的初期大家很快就掌握了Go的语法。 但在编写更多Go代码之后，很多人发现自己写的Go代码总是感觉很别扭，并且总是尝试在Go语言中寻找自己上一门语言中熟悉的语法元素。自己的Go代码风格似乎和Go标准库、主流Go开源项目的代码在思考角度和使用方式上存在不小差异，并且每每看到Go核心开发团队的代码时总有一种醍醐灌顶的感觉。出现这种情况的主要原因就是大脑中上一门编程语言的思维方式在“作祟”。 本文将通过《Go语言精进之路：从新手到高手的编程思想、方法与技巧》这本书的内容来详细看一看编程语言与编程思维的关系以及Go语言的编程思维究竟是什么，以帮助大家更加深入地理解Go编程。 了解Go编程思维之前，我们先看看思维与语言之间究竟有什么联系呢？ 1.语言与思维——来自大师的观点 在人类自然语言学界有一个很著名的假说——“萨丕尔—沃夫假说”，这个假说的内容是这样的：“语言影响或决定人类的思维方式。” 说到这个假说，我们不能不提及在2017年年初国内上映了一部口碑不错的美国科幻大片《降临》，这部片子改编自雨果奖获得者华裔科幻小说家Ted姜的《你一生的故事》。片中主线剧情的理论基础就是“萨丕尔—沃夫假说”。更夸张的是片中直接将该假说应用到外星人语言上，将其扩展到宇宙范畴。片中的女主作为人类代表与外星人沟通，并学会了外星语言，从此思维大变，拥有了预知未来的“超能力”，这也算是语言影响思维的极致表现了。 奇妙的是，在编程语言界，有位大师级人物也有着与“萨丕尔-沃夫假说”异曲同工的观点和认知，他就是首届图灵奖得主、著名计算机科学家艾伦·佩利（Alan J. Perlis），他从另外一个角度提出：“不能影响到你的编程思维方式的编程语言不值得去学习和使用。” 2.现实中的“投影” 从上述大师们的理论和观点，我们看到了语言与思维之间存在着某种联系。那么两者间的这种联系在真实编程世界中的投影又是什么样子的呢？我们来看一个简单的编程问题——素数筛： 问题描述：素数是一个自然数，它具有两个截然不同的自然数除数：1和它本身。这里的问题是如何找到小于或等于给定整数n的素数。针对这个问题，我们可以采用埃拉托斯特尼素数筛算法。 算法描述：先用最小的素数2去筛，把2的倍数剔除掉；下一个未筛除的数就是素数（这里是3）。再用这个素数3去筛，筛除掉3的倍数&#8230; 这样不断重复下去，直到筛完为止（算法图示见图1）。 图1 素数筛算法图示 下面是该素数筛算法的不同编程语言的实现版本。 （1）C语言版本 // sieve.c #include &#60;stdio.h&#62; #define LIMIT 50 #define PRIMES 10 void sieve() { int c, i,j,numbers[LIMIT], primes[PRIMES]; for (i=0;i&#60;LIMIT;i++){ numbers[i]=i+2; /*fill the array with natural numbers*/ } for (i=0;i&#60;LIMIT;i++){ if (numbers[i]!=-1){ for (j=2*numbers[i]-2;j&#60;LIMIT;j+=numbers[i]) [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/taking-a-closer-look-at-programming-thinking-in-go-1.jpeg" alt="" /></p>
<p><a href="https://tonybai.com/2022/04/23/taking-a-closer-look-at-programming-thinking-in-go">本文永久链接</a> &#8211; https://tonybai.com/2022/04/23/taking-a-closer-look-at-programming-thinking-in-go</p>
<p>经过十几年的演化和发展，Go语言在全世界范围内已经拥有了百万级别的拥趸，在这些开发者当中，除了一部分新入行的编程语言初学者之外，更多的是从其他编程语言阵营转过来的开发者。由于Go语言上手容易，在转Go的初期大家很快就掌握了Go的语法。</p>
<p>但在编写更多Go代码之后，很多人发现自己写的Go代码总是感觉很别扭，并且总是尝试在Go语言中寻找自己上一门语言中熟悉的语法元素。自己的Go代码风格似乎和Go标准库、主流Go开源项目的代码在思考角度和使用方式上存在不小差异，并且每每看到Go核心开发团队的代码时总有一种醍醐灌顶的感觉。出现这种情况的主要原因就是大脑中上一门编程语言的思维方式在“作祟”。</p>
<p>本文将通过<a href="https://item.jd.com/13694000.html">《Go语言精进之路：从新手到高手的编程思想、方法与技巧》</a>这本书的内容来详细看一看编程语言与编程思维的关系以及Go语言的编程思维究竟是什么，以帮助大家更加深入地理解Go编程。</p>
<p><img src="https://tonybai.com/wp-content/uploads/taking-a-closer-look-at-programming-thinking-in-go-2.png" alt="" /></p>
<p>了解Go编程思维之前，我们先看看思维与语言之间究竟有什么联系呢？</p>
<h3>1.语言与思维——来自大师的观点</h3>
<p>在人类自然语言学界有一个很著名的假说——“萨丕尔—沃夫假说”，这个假说的内容是这样的：“<strong>语言影响或决定人类的思维方式</strong>。”</p>
<p>说到这个假说，我们不能不提及在2017年年初国内上映了一部口碑不错的美国科幻大片《降临》，这部片子改编自雨果奖获得者华裔科幻小说家Ted姜的《你一生的故事》。片中主线剧情的理论基础就是“萨丕尔—沃夫假说”。更夸张的是片中直接将该假说应用到外星人语言上，将其扩展到宇宙范畴。片中的女主作为人类代表与外星人沟通，并学会了外星语言，从此思维大变，拥有了预知未来的“超能力”，这也算是语言影响思维的极致表现了。</p>
<p>奇妙的是，在编程语言界，有位大师级人物也有着与“萨丕尔-沃夫假说”异曲同工的观点和认知，他就是首届图灵奖得主、著名计算机科学家艾伦·佩利（Alan J. Perlis），他从另外一个角度提出：“不能影响到你的编程思维方式的编程语言不值得去学习和使用。”</p>
<h3>2.现实中的“投影”</h3>
<p>从上述大师们的理论和观点，我们看到了语言与思维之间存在着某种联系。那么两者间的这种联系在真实编程世界中的投影又是什么样子的呢？我们来看一个简单的编程问题——素数筛：</p>
<ul>
<li>
<p>问题描述：素数是一个自然数，它具有两个截然不同的自然数除数：1和它本身。这里的问题是如何找到小于或等于给定整数n的素数。针对这个问题，我们可以采用埃拉托斯特尼素数筛算法。</p>
</li>
<li>
<p>算法描述：先用最小的素数2去筛，把2的倍数剔除掉；下一个未筛除的数就是素数（这里是3）。再用这个素数3去筛，筛除掉3的倍数&#8230; 这样不断重复下去，直到筛完为止（算法图示见图1）。</p>
</li>
</ul>
<p><img src="https://tonybai.com/wp-content/uploads/taking-a-closer-look-at-programming-thinking-in-go-3.gif" alt="" /><br />
<center>图1 素数筛算法图示</center></p>
<p>下面是该素数筛算法的不同编程语言的实现版本。</p>
<h4>（1）C语言版本</h4>
<pre><code>// sieve.c
#include &lt;stdio.h&gt;

#define LIMIT  50
#define PRIMES 10

void sieve() {
    int c, i,j,numbers[LIMIT], primes[PRIMES];

    for (i=0;i&lt;LIMIT;i++){
        numbers[i]=i+2; /*fill the array with natural numbers*/
    }

    for (i=0;i&lt;LIMIT;i++){
        if (numbers[i]!=-1){
            for (j=2*numbers[i]-2;j&lt;LIMIT;j+=numbers[i])
                numbers[j]=-1; /* 筛除非素数 */
        }
    }

    c = j = 0;
    for (i=0;i&lt;LIMIT&amp;&amp;j&lt;PRIMES;i++) {
        if (numbers[i]!=-1) {
            primes[j++] = numbers[i]; /*transfer the primes to their own array*/
            c++;
        }
    }

    for (i=0;i&lt;c;i++) printf("%d\n",primes[i]);
}
</code></pre>
<h4>（2）Haskell版本</h4>
<pre><code>// sieve.hs

sieve [] = []
sieve (x:xs) = x : sieve (filter (\a -&gt; not $ a `mod` x == 0) xs)
n = 100
main = print $ sieve [2..n]
</code></pre>
<h4>（3）Go语言版本</h4>
<pre><code>// sieve.go

func Generate(ch chan&lt;- int) {
    for i := 2; ; i++ {
        ch &lt;- i
    }
}

func Filter(in &lt;-chan int, out chan&lt;- int, prime int) {
    for {
        i := &lt;-in
        if i%prime != 0 {
            out &lt;- i
        }
    }
}

func main() {
    ch := make(chan int)
    go Generate(ch)
    for i := 0; i &lt; 10; i++ {
        prime := &lt;-ch
        print(prime, "\n")
        ch1 := make(chan int)
        go Filter(ch, ch1, prime)
        ch = ch1
    }
}
</code></pre>
<p>对比上述的三个语言版本的素数筛算法的实现，我们看到：</p>
<ul>
<li>
<p>C版本的素数筛程序是一个常规实现。它定义了两个数组：numbers和primes，“筛”的过程在numbers这个数组中进行（即基于纯内存修改），非素数的数组元素被设置为-1，便于后续提取；</p>
</li>
<li>
<p>Haskell版本采用了函数递归的思路，通过“filter操作集合”，用下面谓词（过滤条件）筛除素数的倍数，将未筛除的数的集合作为参数传递归递给下去；</p>
</li>
</ul>
<pre><code>\a -&gt; not $ a `mod` x == 0；
</code></pre>
<ul>
<li>Go版本程序实现了一个并发素数筛，它采用的是goroutine的并发组合。程序从素数2开始，依次为每个素数建立一个goroutine，用于作为筛除该素数的倍数。ch指向当前最新输出素数所位于的筛子goroutine的源channel，这段代码来自于Rob Pike的一次关于并发的分享。Go版本程序的执行过程可以用图2立体的展现出来。</li>
</ul>
<p><img src="https://tonybai.com/wp-content/uploads/taking-a-closer-look-at-programming-thinking-in-go-4.gif" alt="" /><br />
<center>图2 Go版本素数筛执行图示</center></p>
<h3>3.Go语言原生编程思维</h3>
<p>通过上述这个现实中的问题我们可以看到：面对同一个问题，来自不同编程语言的程序员给出了思维方式截然不同的解决方法：C的命令式思维、Haskell的函数式思维和Go的并发思维。结合“萨丕尔—沃夫假说”，我们可以得到一个未经理论证实但又确实对现实有影响的推论：<strong>编程语言影响编程思维，或者说每种编程语言都有属于自己的原生编程思维</strong>。</p>
<p>Go语言诞生较晚，大多数Gopher（包括笔者在内）第一语言都不是Go，都是“半路出家”从其他语言转过来的，如C、C++、Java、Python等。每种语言都有自己的原生编程思维。比如：C语言相信程序员，提供了指针和指针运算，让C程序员天马行空的发挥，接近底层的直接内存操作让C程序拥有很高的性能；C++支持多范式（命令式、OO和泛型），虽不强迫程序员使用某个特定的范式，但推荐使用最新代表现代语言发展特色的泛型等高级范式；Python语言更是形成了Pythonic规则来指导Python程序员写出符合Python思维或惯用法的代码。</p>
<p>经验告诉我们但凡属于某个编程语言的高质量范畴的代码，其必定是在这种编程语言原生思维下编写的代码。如果用A语言的思维去编写B语言的代码（比如用OO思维写C代码，用命令式的思维写Haskell代码等），那么你写出的代码多半无法被B语言社区所认可，更难以成为高质量代码的典范。并且，如果沿着这样的方向去学习和实践B语言，那么结果只能是“南辕北辙”，离编写出高质量代码的目标渐行渐远。</p>
<p>那Go原生编程思维究竟是什么呢？一门编程语言的编程思维是由语言设计者、语言实现团队、语言社区、语言使用者在长期的演化和实践中形成的一种统一的思维习惯、行为方式、代码惯用法和风格。Go语言从诞生到现在也近十年多了。经过Go设计哲学熏陶、Go开发团队的引导和教育、Go社区的实践，Go语言也渐渐形成了属于自己的原生编程思维，或者说形成了符合Go语言哲学的Go语言惯用法（idiomatic go）。它们是Go语言的精华，也是构建本书内容的骨架，并值得我们用一本书的规模去详细呈现。因此可以说阅读本书的过程也是学习和建立Go语言原生编程思维的过程。</p>
<h3>4. 小结</h3>
<p>本文详细介绍了编程语言与编程思维之间的联系。我们学习和使用一门编程语言，目标就是要用这门语言的原生思维方式去编写高质量代码。学习Go，就要用Go的原生编程思维去写Go代码，而不是用其他语言的思维方式。掌握Go原生编程思维就是我们通往高质量Go编程的学习方向和必经之路。如果您想要了解更多有关Go编程思维的内容，推荐您详细阅读我的新作《Go语言精进之路：从新手到高手的编程思想、方法与技巧》。</p>
<hr />
<p><img src="https://tonybai.com/wp-content/uploads/taking-a-closer-look-at-programming-thinking-in-go-5.jpeg" alt="" /></p>
<p>Gopher Daily(Gopher每日新闻)归档仓库 &#8211; https://github.com/bigwhite/gopherdaily</p>
<p>我的联系方式：</p>
<ul>
<li>微博：https://weibo.com/bigwhite20xx</li>
<li>微信公众号：iamtonybai</li>
<li>博客：tonybai.com</li>
<li>github: https://github.com/bigwhite</li>
<li>“Gopher部落”知识星球：https://public.zsxq.com/groups/51284458844544</li>
</ul>
<p><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。</p>
<p style='text-align:left'>&copy; 2022, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2022/04/23/taking-a-closer-look-at-programming-thinking-in-go/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>“能力越大，责任越大” &#8211; Go语言之父详解将于Go 1.18发布的Go泛型</title>
		<link>https://tonybai.com/2021/02/18/typing-generic-go-by-griesemer-at-gophercon-2020/</link>
		<comments>https://tonybai.com/2021/02/18/typing-generic-go-by-griesemer-at-gophercon-2020/#comments</comments>
		<pubDate>Wed, 17 Feb 2021 22:45:29 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[constraint]]></category>
		<category><![CDATA[Contract]]></category>
		<category><![CDATA[Cpp]]></category>
		<category><![CDATA[FeatherweightGo]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Go2]]></category>
		<category><![CDATA[gogenerics]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[GopherCon]]></category>
		<category><![CDATA[GopherCon2020]]></category>
		<category><![CDATA[Haskell]]></category>
		<category><![CDATA[IanLanceTaylor]]></category>
		<category><![CDATA[Interface]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[RobertGriesemer]]></category>
		<category><![CDATA[RobPike]]></category>
		<category><![CDATA[RussCox]]></category>
		<category><![CDATA[sort]]></category>
		<category><![CDATA[type]]></category>
		<category><![CDATA[typeinference]]></category>
		<category><![CDATA[typeparameter]]></category>
		<category><![CDATA[大流行]]></category>
		<category><![CDATA[新冠病毒]]></category>
		<category><![CDATA[正交性]]></category>
		<category><![CDATA[泛型]]></category>
		<category><![CDATA[类型推导]]></category>
		<category><![CDATA[类型约束]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=3104</guid>
		<description><![CDATA[注：本文是首发于笔者微信公众号“iamtonybai”上的付费文章，这里免费分享给大家！ 在2020.11.9~11.13举行的全球最具影响力的Go语言技术大会GopherCon 2020上，Go语言之父之一的Robert Griesemer为全世界Gopher们带来了本次大会最重量级的演讲“Typing [Generic] Go”。 图：Robert Griesemer带来的有关Go泛型演讲 在这个演讲中，Robert Griesemer向Gopher们介绍了自从今年中旬在Go官网发表文章“The Next Step for Generics”以来Go泛型(Go Generics)技术草案的最新变化，并详细介绍了类型参数(type parameter)是如何满足Go现有的类型系统的，以及Go编译器是如何对Go泛型代码进行类型检查的。 本文整理了此次演讲的重点内容，供广大Gopher参考，希望能为大家理解Go泛型带来帮助。 一. 预备知识 为了更好地理解Robert Griesemer的讲解，这里先带着大家回顾一下Go generics技术草案演化史。 图：Go泛型技术草案演化时间线 2017年7月，Go核心团队领军人物Russ Cox在Gophercon 2017大会上发表演讲“Toward Go 2”，正式吹响Go向下一个阶段演化的号角； 2018年8月，在Gophercon 2018大会结束后不久，Go核心团队发布了Go2 draft proposal，这里面涵盖了由Ian Lance Taylor和Robert Griesemer操刀主写的Go泛型的第一版draft proposal。这版草案引入了contract关键字来定义泛型类型参数(type parameter)的约束、类型参数放在普通函数参数列表前面的小括号中，并用type关键字声明： // 第一版泛型技术草案中的典型泛型语法 contract stringer(x T) { var s string = x.String() } func Stringify(type T stringer)(s []T) (ret [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/go-generics-at-gophercon-2020-1.png" alt="img{512x368}" /></p>
<blockquote>
<p>注：本文是首发于笔者微信公众号“iamtonybai”上的<a href="https://mp.weixin.qq.com/s/SMT40557JgQ9FjUkswznlA">付费文章</a>，这里免费分享给大家！</p>
</blockquote>
<p>在2020.11.9~11.13举行的全球最具影响力的Go语言技术大会<a href="https://www.gophercon.com/">GopherCon 2020</a>上，Go语言之父之一的<a href="https://github.com/griesemer">Robert Griesemer</a>为全世界Gopher们带来了本次大会最重量级的演讲<strong>“Typing [Generic] Go”</strong>。</p>
<p><img src="https://tonybai.com/wp-content/uploads/go-generics-at-gophercon-2020-2.png" alt="img{512x368}" /><br />
<center>图：Robert Griesemer带来的有关Go泛型演讲</center></p>
<p>在这个演讲中，Robert Griesemer向Gopher们介绍了自从今年中旬在Go官网发表文章<a href="https://blog.golang.org/generics-next-step">“The Next Step for Generics”</a>以来<a href="https://tonybai.com/2020/06/18/the-go-generics-is-coming-and-supported-in-go-1-17-at-the-earliest/">Go泛型(Go Generics)技术草案</a>的最新变化，并详细介绍了类型参数(type parameter)是如何满足Go现有的类型系统的，以及Go编译器是如何对Go泛型代码进行类型检查的。</p>
<p>本文整理了此次演讲的重点内容，供广大Gopher参考，希望能为大家理解Go泛型带来帮助。</p>
<h3>一. 预备知识</h3>
<p>为了更好地理解Robert Griesemer的讲解，这里先带着大家回顾一下Go generics技术草案演化史。</p>
<p><img src="https://tonybai.com/wp-content/uploads/go-generics-evolution-timeline.png" alt="img{512x368}" /><br />
<center>图：Go泛型技术草案演化时间线</center></p>
<ul>
<li>2017年7月，Go核心团队领军人物<a href="https://github.com/rsc/">Russ Cox</a>在Gophercon 2017大会上发表演讲“<a href="https://blog.golang.org/toward-go2">Toward Go 2</a>”，正式吹响Go向下一个阶段演化的号角；</li>
<li>2018年8月，在Gophercon 2018大会结束后不久，Go核心团队发布了Go2 draft proposal，这里面涵盖了由Ian Lance Taylor和Robert Griesemer操刀主写的Go泛型的<a href="https://github.com/golang/proposal/blob/00fd2f65291738699cd265243559718f1fb7d8c5/design/go2draft-contracts.md">第一版draft proposal</a>。这版草案引入了<strong>contract关键字</strong>来定义泛型类型参数(type parameter)的约束、类型参数放在普通函数参数列表前面的<strong>小括号</strong>中，并用type关键字声明：</li>
</ul>
<pre><code>// 第一版泛型技术草案中的典型泛型语法

contract stringer(x T) {
    var s string = x.String()
}

func Stringify(type T stringer)(s []T) (ret []string) {

}
</code></pre>
<ul>
<li>2019年7月，Ian Lance Taylor在GopherCon 2019大会上发表演讲<a href="https://blog.golang.org/why-generics">“Why Generics?”</a>，并更新了<a href="https://github.com/golang/proposal/blob/4a54a00950b56dd0096482d0edae46969d7432a6/design/go2draft-contracts.md">泛型的技术草案</a>，简化了contract的语法设计：</li>
</ul>
<pre><code>// 简化后的contract语法如下：

contract stringer(T) {
    T String() string
}
</code></pre>
<ul>
<li>2020年6月，《Featherweight Go》论文发表在arxiv.org上，该论文缘于Rob Pike向著名计算机科学家、函数语言专家、<a href="https://tonybai.com/tag/haskell">Haskell语言</a>的设计者之一、Java泛型的设计者PHILIP WADLER发出的一次邀请，希望PHILIP WADLER帮助Go核心团队解决Go语言的泛型扩展问题：</li>
</ul>
<p><img src="https://tonybai.com/wp-content/uploads/go-generics-at-gophercon-2020-4.png" alt="img{512x368}" /><br />
<center>图：Rob Pike向PHILIP WADLER发出的邀请</center></p>
<p>而这篇论文则是对这次邀请的回应。这篇论文为Go语言的一个最小语法子集设计了泛型语法Featherweight Generic Go(FGG)，并成功地给出了FGG到Feighterweight Go(FG)的可行性实现的形式化证明。</p>
<p>该篇论文采用monomorphisation(单态)的实现，而非Java使用的擦触法(Erasure)，这样的好处之一是如果代码中没有使用任何泛型抽象，程序的运行时不会因支持泛型而承担额外的消耗。</p>
<p>该论文的形式化证明给Go团队带来了信心，也是的Go团队在一些语法问题上达成更广泛的一致。</p>
<p><img src="https://tonybai.com/wp-content/uploads/go-generics-at-gophercon-2020-5.png" alt="img{512x368}" /><br />
<center>图：Robert Griesemer表达了对该论文团队的感谢</center></p>
<ul>
<li>2020.6月末，Ian Lance Taylor和Robert Griesemer在Go官方博客发表了文章<a href="http://blog.golang.org/generics-next-step">《The Next Step for Generics》</a>，介绍了Go泛型工作的最新进展。Go团队放弃了之前的技术草案，并重新编写了<a href="https://github.com/golang/proposal/blob/d44c4ded9c1a13dcf715ac641ce760170fbbcf64/design/go2draft-type-parameters.md">一个新草案</a>。在这份新技术方案中，Go团队放弃了引入contract关键字作为泛型类型参数的约束，而采用扩展后的interface来替代contract。这样上面的Stringify函数就可以写成如下形式：</li>
</ul>
<pre><code>type Stringer interface {
    String() string
}

func Stringify(type T Stringer)(s []T) (ret []string) {
    ... ...
}
</code></pre>
<p>同时，Go团队还推出了可以<a href="https://go2goplay.golang.org">在线试验Go泛型语法的playground</a>：https://go2goplay.golang.org，这样gopher们可以直观体验新语法，并给出自己的意见反馈。</p>
<ul>
<li>2020年11月的GopherCon 2020大会，Griesemer与全世界Gopher同步了Go泛型的最新进展和roadmap，在最新的技术草案版本中，小括号被方括号取代，类型参数前面的type关键字也不再需要了：</li>
</ul>
<pre><code>func Stringify[T Stringer](s []T) (ret []string) {
    ... ...
}
</code></pre>
<p>go2goplay.golang.org也支持了方括号语法，gopher可以在线体验。</p>
<p><strong>下面我们就来看看Griesemer对最新Go泛型技术草案的详细讲解</strong>。</p>
<h3>二. 类型参数(Type parameters)技术草案详解</h3>
<p>这版草案与2019年中旬发布的草案的最大变动就是<strong>使用interface而不是contract来表达对类型参数的约束</strong>。</p>
<p>该版设计的主要特性：</p>
<ul>
<li>类型参数(Type parameters) &#8211; 一种将类型或函数进行参数化的机制</li>
<li>约束(Constraints) &#8211; 一种表达对类型参数的约束的机制</li>
<li>类型推导(Type inference，可选)</li>
</ul>
<h4>普通函数参数列表 vs. 泛型函数的类型参数列表</h4>
<p>我们知道，普通函数的参数列表是这样的：</p>
<pre><code>(x, y aType, z anotherType)
</code></pre>
<ul>
<li>x, y, z是形参(parameter)的名字，即变量；</li>
<li>aType，anotherType是形参的类型，即类型。</li>
</ul>
<p>我们再来看一下类型参数(type parameter)列表：</p>
<pre><code>[P, Q aConstraint, R anotherConstraint]
</code></pre>
<ul>
<li>P，Q，R是类型形参的名字，即类型；</li>
<li>aConstraint，anotherConstraint代表类型参数的约束(constraint)，可以理解为一种元类型(meta-type，即修饰类型的类型)。</li>
</ul>
<blockquote>
<p>注：按惯例，类型参数(type parameter)的名字都是头母大写的。</p>
</blockquote>
<h4>为什么需要类型参数(type parameter)</h4>
<p>我们先来看一下当前Go语言标准库中提供的排序方案：</p>
<pre><code>// $GOROOT/src/sort/sort.go
type Interface interface {
        Len() int
        Less(i, j int) bool
        Swap(i, j int)
}

func Sort(data Interface) {
    ... ...
}
</code></pre>
<p>为了应用这个排序函数Sort，我们需要让被排序的类型实现sort.Interface接口，就像下面例子中这样：</p>
<pre><code>type IntSlice []int

func (p IntSlice) Len() int           { return len(p) }
func (p IntSlice) Less(i, j int) bool { return p[i] &lt; p[j] }
func (p IntSlice) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }

func main() {
        sl := IntSlice([]int{89, 14, 8, 9, 17, 56, 95, 3})
        fmt.Println(sl)
        sort.Sort(sl)
        fmt.Println(sl)
}
</code></pre>
<p>这真是我们想要的实现方式吗？我们真正需要的是这样的：</p>
<pre><code>func Sort(list []Elem)

// 使用
var myList = []Elem{...}
Sort(myList)
</code></pre>
<p>解决办法：<strong>使用type parameter</strong>(类型参数或叫做参数化的类型，将类型作为参数传递)：</p>
<p><img src="https://tonybai.com/wp-content/uploads/go-generics-at-gophercon-2020-6.png" alt="img{512x368}" /><br />
<center>图：使用类型参数的Sort</center></p>
<h4>约束(constraints)</h4>
<p>约束(constraint)规定了一个类型实参(type argument)必须满足的条件要求。而在泛型Go中，<strong>我们使用interface来定义约束</strong>。</p>
<p>如果某个类型实现了某个约束(规定的所有条件要求)，那么它就是一个合法的类型实参。</p>
<p>下面是一个泛型版本的Sort函数：</p>
<pre><code>func Sort[Elem interface{ Less(y Elem) bool }](list []Elem)
</code></pre>
<p>我们看到上面函数Sort的类型形参(type parameter)Elem的约束是一个interface，这样传入的类型实参(type argument)只要实现了该接口即可。</p>
<p>约束的定义中也可以引用类型形参，比如下面这个泛型函数：</p>
<p><img src="https://tonybai.com/wp-content/uploads/go-generics-at-gophercon-2020-7.png" alt="img{512x368}" /><br />
<center>图：约束的定义中引用类型形参</center></p>
<h4>类型形参的声明与作用域</h4>
<p><img src="https://tonybai.com/wp-content/uploads/go-generics-at-gophercon-2020-8.png" alt="img{512x368}" /><br />
<center>图：类型参数的声明与作用域</center></p>
<p>类型参数的作用域始于<strong>[</strong>，终于泛型函数的函数体结尾或泛型类型的声明结尾。</p>
<h4>泛型的类型具化与类型检查</h4>
<p>下面是一个使用泛型版本Sort函数的例子：</p>
<pre><code>func Sort[Elem interface{ Less(y Elem) bool }](list []Elem)

type book struct{&#8230;}
func (x book) Less(y book) bool {&#8230;}
var bookshelf []book
&#8230;
Sort[book](bookshelf) // 泛型函数调用
</code></pre>
<p>上面的泛型函数调用<code>Sort[book](bookshelf)</code>将分成两个阶段：</p>
<ol>
<li>具化(instantiation)</li>
</ol>
<p>形象点说，具化(instantiation)就<strong>好比一家生产“排序机器”的工厂根据要排序的对象的类型将这样的机器生产出来的过程</strong>。以上面的例子来说，整个具化过程如下：</p>
<ul>
<li>工厂接单：<strong>Sort[book]</strong>，发现要排序的对象类型为book； </li>
<li>模具检查与匹配：检查book类型是否满足模具的约束要求(即是否实现了Less方法)，如满足，则将其作为类型实参替换Sort函数中的类型形参，结果为<strong>Sort[book interface{ Less(y book) bool }]</strong>；</li>
<li>生产机器：将泛型函数Sort具化为一个<strong>新函数</strong>，这里将其起名为<strong>booksort</strong>，其函数原型为<strong>func([]book)</strong>。本质上<strong>booksort := Sort[book]</strong>。</li>
</ul>
<ol>
<li>调用(invocation)</li>
</ol>
<p>一旦“排序机器”被生产出来，那么它就可以对目标对象进行排序了，这和普通的函数调用没有区别。这里就相当于调用booksort(bookshelf)，整个过程只需检查传入的函数实参(bookshelf)的类型与booksort函数原型中的形参类型([]book)是否匹配即可。</p>
<p>用伪代码来表述上面两个过程如下：</p>
<pre><code>Sort[book](bookshelf)

&lt;=&gt;

具化：booksort := Sort[book]
调用：booksort(bookshelf)
</code></pre>
<h4>泛型类型</h4>
<p>除了函数可以携带类型参数变身为“泛型函数”外，类型也可以拥有类型参数而化身为“泛型类型”：</p>
<pre><code>type Lesser[T any] interface{
   Less(y T) bool
}
</code></pre>
<p>上面代码中的<strong>any</strong>代表没有任何约束，等价于interface{}。</p>
<h4>泛型类型的类型参数的声明与作用域范围</h4>
<p>泛型类型的类型参数的声明方式如下，类型参数的作用域范围也同见下图：</p>
<p><img src="https://tonybai.com/wp-content/uploads/go-generics-at-gophercon-2020-9.png" alt="img{512x368}" /><br />
<center>图：泛型类型的类型参数的声明与作用域</center></p>
<h4>用泛型类型改造Sort</h4>
<p>用泛型类型定义一个具名的约束条件- Lesser接口类型：</p>
<pre><code>type Lesser[T any] interface{
   Less(y T) bool
}
</code></pre>
<p>使用Lesser[T]作为约束的Sort函数可以这样写：</p>
<pre><code>func Sort[Elem Lesser[Elem]](list []Elem)
</code></pre>
<blockquote>
<p>注意：任何泛型函数或泛型类型在使用前都必须先“具化(instantiation)”。</p>
</blockquote>
<p>我们再来看看Sort函数的内部实现：</p>
<pre><code>func Sort[Elem Lesser[Elem]](list []Elem) {
    ...
    var i, j int
    ...
    if list[i].Less(List[j]) {
        ...
    }
    ...
}
</code></pre>
<ul>
<li>这里的list[i]和list[j]的类型是Elem；</li>
<li>Elem不是一个接口类型，它是泛型函数(Sort)的类型参数，Lesser[Elem]是作为类型参数的约束而存在的，不要与函数常规参数列表混淆。</li>
</ul>
<p>再次强调：<strong>类型参数是一个真实的类型，不是一个接口类型(变量)，当然我们可以使用一个接口类型作为类型实参来具化一个泛型函数或泛型类型</strong>。</p>
<h4>实参类型自动推导(Argument type inference)</h4>
<p>我们是想要：</p>
<pre><code>Sort[book](bookshelf)
</code></pre>
<p>还是：</p>
<pre><code>Sort(bookshelf)
</code></pre>
<p>显然是后者。我们希望Go编译器能够根据传入的变量自动推导出类型参数的实参类型。</p>
<p><img src="https://tonybai.com/wp-content/uploads/go-generics-at-gophercon-2020-10.png" alt="img{512x368}" /><br />
<center>图：实参类型的自动推导</center></p>
<p>这样，在具化之前，如果泛型函数调用没有显式提供实参类型，那么Go编译器将进行自动实参类型推导。<strong>有了是实参类型的自动推导，大多数泛型调用的方式与常规函数调用一致</strong>。</p>
<h4>类型列表(type lists)</h4>
<p>到这里，约束仅限于描述方法要求。下面的函数调用仍然无法工作：</p>
<pre><code>Sort([]int{1, 2, 3})
</code></pre>
<p>因为原生的int类型不满足Elem的约束，没有实现Less方法。虽然我们可以用下面替代方法实现整型切片的排序：</p>
<pre><code>type myInt int
func (x myInt) Less(y myInt) bool { return x &lt; y }
</code></pre>
<p>但这还是太麻烦了。</p>
<p>Go泛型扩展了interface语法，除了让interface拥有自己的方法列表外，还支持在interface中定义类型列表(type list)：</p>
<pre><code>type Float interface {
   type float32, float64
}

// float32和float64都可以作为类型实参传递给Sin
func Sin[T Float](x T) T
</code></pre>
<p>现在，一个类型实参要想满足约束，要么它实现了约束中的所有方法，要么它或它的底层类型(underlying type)在约束的类型列表中。</p>
<p>下面是一个泛型函数min的声明与约束定义：</p>
<pre><code>func min[T Ordered](x, y T) T ...

type Ordered interface {
    type int, int8, int16, ..., uint, uint8, uint16, ..., float32, float64, string
}
</code></pre>
<p>函数min的实现如下：</p>
<pre><code>func min[T Ordered](x, y T) T {
    if x &lt; y {
        return x
    }
    return y
}
</code></pre>
<ul>
<li>x和y的类型都是T，T类型要满足约束Ordered；</li>
<li>x &lt; y是合法的，因为在Ordered的类型列表中的每个类型都支持"&lt;"比较。</li>
</ul>
<p>但不同类型参数代表的却是不同类型：</p>
<pre><code>func invalid[Tx, Ty Ordered](x Tx, y Ty) Tx {
    ...
    if x &lt; y { // 不合法
        ...
    }
}
</code></pre>
<ul>
<li>x的类型是Tx，y的类型是Ty；</li>
<li>Tx和Ty是不同类型；</li>
<li>"&lt;"需要两个操作数拥有相同的类型。</li>
</ul>
<h4>类型列表应用的典型示例</h4>
<ul>
<li>将[]byte和string的操作整合在一起</li>
</ul>
<p>我们知道目前标准库中有一个bytes包和一个strings包，这两个包一个用于处理[]byte，一个则用于处理string。但使用过这两个包的gopher会发现，这两个包中大部分函数和方法是一样的，甚至处理逻辑都是一样的。有了泛型后，我们可以将对两种类型的大部分操作整合在一起，以Index函数为例：</p>
<pre><code>type Bytes interface {
   type []byte, string
}

// Index returns the index of the first instance of sep
// in s, or -1 if sep is not present in s.
func Index[bytes Bytes](s, sep bytes) int
</code></pre>
<ul>
<li>类型参数(type parameter)之间的关系</li>
</ul>
<pre><code>type Pointer[T any] interface {
    type *T
}

func f[T any, PT Pointer[T]](x T)

或

func foo[T any, PT interface{type *T}](x T)
</code></pre>
<p>上面是基于类型列表表述“一个类型的指针类型”约束的方案。PT的实参的类型必须是T的实参类型的指针类型。</p>
<p>下面这几个函数和接口很大可能会加入到标准库：</p>
<pre><code>func BasicSort[Elem Ordered](list []Elem)

func Sort[Elem Lesser[Elem]](list []Elem)

type Lesser[Elem any] interface {
    Less(Elem) Elem
}
</code></pre>
<h4>小结</h4>
<p>关于泛型声明：</p>
<ul>
<li>类型参数列表和普通参数列表相似，只是使用"[ ]"括起；</li>
<li>函数和类型都可以拥有类型参数列表；</li>
<li>使用interface表达对类型参数的约束。</li>
</ul>
<p>关于泛型使用：</p>
<ul>
<li>泛型函数和类型在使用之前必须先“具化(instantiated)”；</li>
<li>类型自动推导可实现函数隐式具化；</li>
<li>如果类型实参满足约束，那么具化才会合法。</li>
</ul>
<p>截至2020.10月份的泛型设计草案版本，我们对以下特性设计的满意度为：</p>
<p><img src="https://tonybai.com/wp-content/uploads/go-generics-at-gophercon-2020-11.png" alt="img{512x368}" /></p>
<h3>三. 结束语</h3>
<h4>“能力越大，责任越大”</h4>
<ul>
<li>类型参数(泛型)是Go工具集中的新成员；</li>
<li>它与语言的其他部分正交；</li>
<li>其正交性也打开了编码风格的一个新维度。</li>
</ul>
<p><strong>泛型引入了抽象，无用的抽象带来复杂性。请三思而后行！</strong></p>
<h4>示例1</h4>
<pre><code>func ReadAll(r io.Reader) ([]byte, error)

对比：

func ReadAll[reader io.Reader](r reader) ([]byte, error)
</code></pre>
<p>=> 引入泛型的版本并未解决任何实际问题(还带来了复杂难以理解的抽象)</p>
<h4>示例2</h4>
<pre><code>// Drain drains any elements remaining on the channel.
func Drain[T any](c &lt;-chan T)

// Merge merges two channels of some element type into
// a single channel.
func Merge[T any](c1, c2 &lt;-chan T) &lt;-chan T
</code></pre>
<p>=> 类型参数让以往无法实现的逻辑成为现实。</p>
<h4>何时使用泛型</h4>
<ul>
<li>增强静态类型安全性</li>
<li>更高效的内存使用</li>
<li>(显著的)更好的性能</li>
</ul>
<p><strong>泛型是带有类型检查的宏(macro)。使用宏之前请三思！</strong></p>
<h4>接下来的工作</h4>
<p>Go核心团队正在着手做出一个完整的泛型实现，以便我们解决所有未解决的问题。我们继续欢迎大家的反馈！</p>
<p>如何抢先体验泛型：</p>
<ul>
<li>playground: https://go2goplay.golang.org/ </li>
<li>go2go命令工具：git checkout dev.go2go</li>
</ul>
<blockquote>
<p>注：2020.11.21日，Go开发团队技术负责人Russ Cox在golang-dev上的mail确认了Go泛型(type parameter)<a href="https://groups.google.com/g/golang-dev/c/U7eW9i0cqmo/m/ffs0tyIYBAAJ?pli=1">将在Go 1.18版本落地，即2022.2月份</a>。</p>
</blockquote>
<p><img src="https://tonybai.com/wp-content/uploads/go-generics-at-gophercon-2020-12.png" alt="img{512x368}" /></p>
<p>关注公众号“iamtonybai”，<strong>fgg</strong>获取论文“Featherweight Go”下载链接；发送<strong>gophercon2020</strong>获取GopherCon 2020大会技术ppt资料。</p>
<p><img src="https://tonybai.com/wp-content/uploads/qrcode_for_iamtonybai.jpg" alt="img{512x368}" /></p>
<hr />
<p>“Gopher部落”知识星球开球了！高品质首发Go技术文章，“三天”首发阅读权，每年两期Go语言发展现状分析，每天提前1小时阅读到新鲜的Gopher日报，网课、技术专栏、图书内容前瞻，六小时内必答保证等满足你关于Go语言生态的所有需求！星球首开，福利自然是少不了的！2020年年底之前，8.8折(很吉利吧^_^)加入星球，下方图片扫起来吧！</p>
<p><img src="http://image.tonybai.com/img/202011/gopher-tribe-zsxq.png" alt="" /></p>
<p>Go技术专栏“<a href="https://www.imooc.com/read/87">改善Go语⾔编程质量的50个有效实践</a>”正在慕课网火热热销中！本专栏主要满足广大gopher关于Go语言进阶的需求，围绕如何写出地道且高质量Go代码给出50条有效实践建议，上线后收到一致好评！欢迎大家订阅！</p>
<p><img src="http://image.tonybai.com/img/202011/go-column-pgo-with-qr-and-text.png" alt="" /></p>
<p>我的网课“<a href="https://coding.imooc.com/class/284.html">Kubernetes实战：高可用集群搭建、配置、运维与应用</a>”在慕课网热卖中，欢迎小伙伴们订阅学习！</p>
<p><img src="https://tonybai.com/wp-content/uploads/k8s-practice-with-qr-and-text.png" alt="img{512x368}" /></p>
<p><a href="https://tonybai.com/">我爱发短信</a>：企业级短信平台定制开发专家 https://tonybai.com/<br />
smspush : 可部署在企业内部的定制化短信平台，三网覆盖，不惧大并发接入，可定制扩展； 短信内容你来定，不再受约束, 接口丰富，支持长短信，签名可选。2020年4月8日，中国三大电信运营商联合发布《5G消息白皮书》，51短信平台也会全新升级到“51商用消息平台”，全面支持5G RCS消息。</p>
<p>著名云主机服务厂商DigitalOcean发布最新的主机计划，入门级Droplet配置升级为：1 core CPU、1G内存、25G高速SSD，价格5$/月。有使用DigitalOcean需求的朋友，可以打开这个<a href="https://m.do.co/c/bff6eed92687">链接地址</a>：https://m.do.co/c/bff6eed92687 开启你的DO主机之路。</p>
<p>Gopher Daily(Gopher每日新闻)归档仓库 - https://github.com/bigwhite/gopherdaily</p>
<p>我的联系方式：</p>
<ul>
<li>微博：https://weibo.com/bigwhite20xx</li>
<li>微信公众号：iamtonybai</li>
<li>博客：tonybai.com</li>
<li>github: https://github.com/bigwhite</li>
</ul>
<p>微信赞赏：<br />
<img src="https://tonybai.com/wp-content/uploads/wechat-zanshang-code-512x512.jpg" alt="img{512x368}" /></p>
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。</p>
<p style='text-align:left'>&copy; 2021, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2021/02/18/typing-generic-go-by-griesemer-at-gophercon-2020/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>源创会开源访谈：十年成长，Go语言的演化之路</title>
		<link>https://tonybai.com/2017/10/24/go-evolution-for-ten-years-an-interview-by-osc/</link>
		<comments>https://tonybai.com/2017/10/24/go-evolution-for-ten-years-an-interview-by-osc/#comments</comments>
		<pubDate>Tue, 24 Oct 2017 00:50:39 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[CNCF]]></category>
		<category><![CDATA[Common-Lisp]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[GC]]></category>
		<category><![CDATA[generics]]></category>
		<category><![CDATA[github]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Go1]]></category>
		<category><![CDATA[Go2]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[gopherchina]]></category>
		<category><![CDATA[GopherCon]]></category>
		<category><![CDATA[goroutine]]></category>
		<category><![CDATA[Haskell]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[microservice]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[RobPike]]></category>
		<category><![CDATA[Ruby]]></category>
		<category><![CDATA[RussCox]]></category>
		<category><![CDATA[Rust]]></category>
		<category><![CDATA[TheWayToGo]]></category>
		<category><![CDATA[七周七语言]]></category>
		<category><![CDATA[七牛]]></category>
		<category><![CDATA[云计算]]></category>
		<category><![CDATA[区块链]]></category>
		<category><![CDATA[开源中国]]></category>
		<category><![CDATA[微服务]]></category>
		<category><![CDATA[泛型]]></category>
		<category><![CDATA[源创会]]></category>
		<category><![CDATA[讲师]]></category>
		<category><![CDATA[调度器]]></category>

		<guid isPermaLink="false">http://tonybai.com/?p=2435</guid>
		<description><![CDATA[在参加源创会沈阳站分享之前，接受了开源中国社区编辑王练的文字专访，以下是我针对专访稿的内容。 同时该专访稿首发于开源中国开源访谈栏目，大家可以点击这里看到首发原稿。 1、首先请介绍一下自己 大家好！我叫白明（Tony Bai），目前是东软云科技的一名架构师，专职于服务端开发，日常工作主要使用Go语言。我算是国内较早接触Go语言的程序员兼Advocater了，平时在我的博客、微博和微信公众号”iamtonybai”上经常发表一些关于Go语言的文章和Go生态圈内的信息。 在接触Go之前，我主要使用C语言开发电信领域的一些后端服务系统，拥有多年的电信领域产品研发和技术管理经验。我个人比较喜换钻研和分享技术，是《七周七语言》一书的译者之一，并且坚持写技术博客十余年。同时我也算是一个开源爱好者，也在github上分享过自己开发的几个小工具。 目前的主要研究和关注的领域包括：Go、Kubernetes、Docker、区块链和儿童编程教育等。 2、最初是因为什么接触和使用 Go 语言的？它哪方面的特性吸引了您？ 个人赶脚：选编程语言和谈恋爱有些像（虽然我只谈过一次^_^），我个人倾向一见钟情。我个人用的最多的编程语言是Go、C，这两门语言算是我在不同时期的“一见钟情”的对象吧，也是最终“领（使）证（用）”的，前提：编程世界是“一夫多妻制”^0^。 当然早期也深入过C++，后来Java、Ruby、Common Lisp、Haskell、Python均有涉猎，这些语言算是恋爱对象，但最终都分手了。 最初接触到Go应该是2011年，那是因为看了Rob Pike的3 Day Go Course，那时Go 1.0版本还没有发布，如果没记错，Rob Pike slide中用的还是Go r60版本的语法。现在大脑中留存的当时的第一感觉就是“一见钟情”！ 现在回想起来，大致有这么几点原因： Go与C一脉相承，对于出身C程序员的我来说，这一语言传承非常自然，多体现在语法上； Go语言非常简单，尤其是GC、并发goroutine、interface，让我眼前一亮； Rob Pike的Go Course Slide组织的非常好，看完三篇Slide，基本就入门了。 于是在那之后，又系统阅读了Ivo Balbaert的《The Way To Go》、《Programming in Go &#8211; Creating Applications for the 21st Century》等基本新鲜出炉的书，于是就走入了Go语言世界。 不过当时Go1尚未发布，Go自身也有较大变化，工作中也无法引入这门语言，2013年对Go的关注有些中断，2014年又恢复，直至今天。现在感觉到：如果工作语言与兴趣语言能保持一致是多么幸福的一件事啊。 3、有人说 Go 是互联网时代的 C 语言，对于这两门语言，您是怎么看的？ 如果没记错，至少在国内，第一个提出这种观点的是现七牛的ceo许式伟了，老许是国内第一的Go 鼓吹者，名副其实；而且许式伟的鼓吹不仅停留在嘴上，更是付诸于实践：据说其七牛云的基础设施基本都是Go开发的。因此，对他的“远见卓识”还是钦佩之至的。 C语言缔造的软件行业的成就是举世瞩目，也是公认的。其作者Dennis Ritchie被授予图灵奖就是对C语言最大的肯定和褒奖。C语言缔造了单机操作系统和基础软件的时代：Unix、Linux、nginx/apache以及无数以*inx世界为中心的工具，是云时代之前最伟大的系统编程语言和基础设施语言。 至于 “Go是互联网时代的 [...]]]></description>
			<content:encoded><![CDATA[<p>在参加<a href="http://tonybai.com/2017/10/23/the-speech-script-practice-on-deploying-a-ha-harbor-cluster-for-osc-shenyang-2017/">源创会沈阳站</a>分享之前，接受了<a href="https://www.oschina.net/">开源中国社区</a>编辑<a href="https://my.oschina.net/mrtudou">王练</a>的文字专访，以下是我针对专访稿的内容。</p>
<p>同时该专访稿首发于开源中国开源访谈栏目，大家可以点击<a href="https://www.oschina.net/question/2896879_2268389">这里</a>看到首发原稿。</p>
<h3>1、首先请介绍一下自己</h3>
<p>大家好！我叫白明（Tony Bai），目前是<a href="http://www.neusoft.com/cn/">东软云科技</a>的一名架构师，专职于服务端开发，日常工作主要使用<a href="https://golang.org">Go语言</a>。我算是国内较早接触<a href="http://tonybai.com/tag/c">Go语言</a>的程序员兼Advocater了，平时在我的<a href="http://tonybai.com">博客</a>、<a href="http://weibo.com/bigwhite20xx">微博</a>和微信公众号”iamtonybai”上经常发表一些关于Go语言的文章和Go生态圈内的信息。</p>
<p>在接触Go之前，我主要使用<a href="http://tonybai.com/tag/c">C语言</a>开发电信领域的一些后端服务系统，拥有多年的电信领域产品研发和技术管理经验。我个人比较喜换钻研和分享技术，是《<a href="https://book.douban.com/subject/10555435/">七周七语言</a>》一书的译者之一，并且坚持写技术博客十余年。同时我也算是一个开源爱好者，也在<a href="https://github.com/bigwhite">github</a>上分享过自己开发的几个小工具。</p>
<p>目前的主要研究和关注的领域包括：Go、<a href="http://tonybai.com/tag/kubernetes">Kubernetes</a>、<a href="http://tonybai.com/tag/docker">Docker</a>、<a href="https://en.wikipedia.org/wiki/Blockchain">区块链</a>和儿童编程教育等。</p>
<p><img src="http://tonybai.com/wp-content/uploads/gopherchina2017-with-mascot.jpg" alt="img{512x368}" /></p>
<h3>2、最初是因为什么接触和使用 Go 语言的？它哪方面的特性吸引了您？</h3>
<p>个人赶脚：选编程语言和谈恋爱有些像（虽然我只谈过一次^_^），我个人倾向一见钟情。我个人用的最多的编程语言是<a href="http://tonybai.com/tag/go">Go</a>、<a href="http://tonybai.com/tag/c">C</a>，这两门语言算是我在不同时期的“一见钟情”的对象吧，也是最终“领（使）证（用）”的，前提：编程世界是“一夫多妻制”^0^。</p>
<p>当然早期也深入过<a href="http://tonybai.com/tag/cpp">C++</a>，后来<a href="http://tonybai.com/tag/java">Java</a>、<a href="http://tonybai.com/tag/ruby">Ruby</a>、<a href="http://tonybai.com/tag/clisp">Common Lisp</a>、<a href="http://tonybai.com/tag/haskell">Haskell</a>、<a href="http://tonybai.com/tag/python">Python</a>均有涉猎，这些语言算是恋爱对象，但最终都分手了。</p>
<p>最初接触到Go应该是2011年，那是因为看了Rob Pike的<a href="https://pan.baidu.com/s/1kV9VxLP">3 Day Go Course</a>，那时<a href="https://blog.golang.org/go-version-1-is-released">Go 1.0版本</a>还没有发布，如果没记错，Rob Pike slide中用的还是Go r60版本的语法。现在大脑中留存的当时的第一感觉就是“一见钟情”！</p>
<p>现在回想起来，大致有这么几点原因：</p>
<ul>
<li>Go与C一脉相承，对于出身C程序员的我来说，这一语言传承非常自然，多体现在语法上；</li>
<li>Go语言非常简单，尤其是GC、并发<a href="http://tonybai.com/2017/06/23/an-intro-about-goroutine-scheduler/">goroutine</a>、interface，让我眼前一亮；</li>
<li>Rob Pike的Go Course Slide组织的非常好，看完三篇Slide，基本就入门了。</li>
</ul>
<p>于是在那之后，又系统阅读了Ivo Balbaert的《<a href="https://book.douban.com/subject/10558892/">The Way To Go</a>》、《<a href="https://book.douban.com/subject/7070565/">Programming in Go &#8211; Creating Applications for the 21st Century</a>》等基本新鲜出炉的书，于是就走入了Go语言世界。</p>
<p>不过当时Go1尚未发布，Go自身也有较大变化，工作中也无法引入这门语言，2013年对Go的关注有些中断，2014年又恢复，直至今天。现在感觉到：如果工作语言与兴趣语言能保持一致是多么幸福的一件事啊。</p>
<h3>3、有人说 Go 是互联网时代的 C 语言，对于这两门语言，您是怎么看的？</h3>
<p>如果没记错，至少在国内，第一个提出这种观点的是现<a href="https://www.qiniu.com/">七牛</a>的ceo<a href="http://weibo.com/xushiweizh">许式伟</a>了，老许是国内第一的Go 鼓吹者，名副其实；而且许式伟的鼓吹不仅停留在嘴上，更是付诸于实践：据说其七牛云的基础设施基本都是Go开发的。因此，对他的“远见卓识”还是钦佩之至的。</p>
<p><a href="https://en.wikipedia.org/wiki/C_(programming_language)">C语言</a>缔造的软件行业的成就是举世瞩目，也是公认的。其作者<a href="https://en.wikipedia.org/wiki/Dennis_Ritchie">Dennis Ritchie</a>被<a href="https://en.wikipedia.org/wiki/Dennis_Ritchie#cite_note-16">授予图灵奖</a>就是对C语言最大的肯定和褒奖。C语言缔造了单机操作系统和基础软件的时代：<a href="https://en.wikipedia.org/wiki/Unix">Unix</a>、<a href="http://tonybai.com/tag/linux">Linux</a>、nginx/apache以及无数以*inx世界为中心的工具，是云时代之前最伟大的系统编程语言和基础设施语言。</p>
<p>至于 “Go是互联网时代的 C 语言”这一观点，如果在几年前很多人还会疑惑甚至不懈，但现在来看：事实胜于雄辩。我们来看看当前<a href="https://www.cncf.io/">CNCF</a>基金会(Cloud Native Computing Foundation)管理的项目中，有一大半都是Go语言开发的，包括<a href="http://tonybai.com/tag/kubernetes">Kubernetes</a>、<a href="https://github.com/prometheus/prometheus">Prometheus</a>等炙手可热的项目；这还不包括近两年最火的<a href="http://tonybai.com/tag/docker">docker</a>项目。事实证明：Go已成为互联网时代、云时代基础设施领域、云服务领域的最具竞争力的编程语言之一。</p>
<p>不过和C不同的是，Go语言还在发展，还在演进，还有巨大的提升空间，Gopher群体还在变大，去年再次成为<a href="https://www.tiobe.com/tiobe-index/">Tiboe</a>的年度语言就是例证。</p>
<p>当然我们还得辩证的看，Go语言虽然在云时代基础设施领域逐渐继承C语言的衣钵，但是由于语言设计理念和设计哲学上的原因，在操作系统以及嵌入式领域，Go还在努力提升。</p>
<h3>4、Go 也经常被拿来和 Java、Rust 等语言比较，您认为它最适合的使用场景有哪些？</h3>
<p>早期对<a href="http://tonybai.com/tag/java">Java</a>有所涉猎，但止步于Java体量过重和框架过多；Rust和Go一样是近几年才兴起的一门很有理想、很有抱负的编程语言，其目标就是安全的系统级编程语言，运行性能极佳，用以替代C/C++的，但就像前面所提到的那样，第一眼看到Rust的语法，就没有那种“一见钟情”的赶脚，希望Rust不要像C++那样，演变的那么复杂。</p>
<p>Go从其第一封设计email出炉到如今<a href="http://tonybai.com/2017/09/24/go-ten-years-and-climbing/">已有十年</a>了，我觉得也不应该由我来告诉大家Go更适合应用在什么领域了，事实摆在那里：“大家都用的地方，总是对的”。这里我只是大致归纳一下：</p>
<ul>
<li>
<p>云计算基础设施领域</p>
<p>代表项目：docker、kubernetes、etcd、<a href="http://tonybai.com/2015/07/06/implement-distributed-services-registery-and-discovery-by-consul/">consul</a>、cloudflare CDN、七牛云存储等。</p>
</li>
<li>
<p>基础软件</p>
<p>代表项目：<a href="https://github.com/pingcap/tidb">tidb</a>、<a href="https://github.com/influxdata/influxdb">influxdb</a>、<a href="https://github.com/cockroachdb/cockroach">cockroachdb</a>等。</p>
</li>
<li>
<p>微服务</p>
<p>代表项目：<a href="https://github.com/go-kit/kit">go-kit</a>、<a href="https://github.com/micro/micro">micro</a>、monzo bank的<a href="https://github.com/monzo">typhon</a>、<a href="https://www.bilibili.com/">bilibili</a>等。</p>
</li>
<li>
<p>互联网基础设施</p>
<p>代表项目：<a href="https://github.com/ethereum/go-ethereum">以太坊</a>、<a href="https://github.com/hyperledger">hyperledger</a>等。</p>
</li>
</ul>
<p>Go在数据科学、人工智能领域也有较大进展，希望在将来能看到Go在这些领域有杀手级项目出现。</p>
<h3>5、Go发展已有10 年，其特性随着版本的迭代不断在更新，您觉得它最好的和最需要改进的特性分别有哪些？</h3>
<p>每种语言都有自己的设计哲学和设计者的考量。我在<a href="http://gopherchina.org/">GopherChina 2017</a>的topic中就提到过<a href="http://tonybai.com/2017/04/20/go-coding-in-go-way/">Go语言的价值观</a>，其中之一就是Simplicity，即简单。相信简单也是让很多开发者走进Gopher世界的重要原因。从今年GopherCon 2017大会上<a href="https://github.com/rsc">Russ Cox</a>的“<a href="https://blog.golang.org/toward-go2">Toward Go 2</a>”的主题演讲中，我们也可以看出：Go team并不会单纯地为了迎合community的意愿去堆砌feature，那go势必走上c++的老路，变得日益复杂，Go受欢迎的基础之一就不存在了。</p>
<p>但演进就一定会要付出代价的，尤其是Go1的约束在前。从我个人对Go的应用来看，最想看到的是包管理和error处理方面的体验提升。但我觉得这两点都是可以通过渐进改进实现的，甚至不会影响到Go1兼容性，不会像引入generics机制，实现难度也不会太高。</p>
<p>对于目前的error handling机制，我个人并没有太多的排斥，这可能是因为我出身C程序员的缘故吧。在error handling这块，只是希望能让gopher拥有更好的体验即可，比如说围绕现有的error机制，增加一些设施以帮助gopher更好的获取error cause信息，就像github.com/pkg/errors包那样。</p>
<p>对于社区呼声很高的<a href="https://en.wikipedia.org/wiki/Generic_programming">generics</a>（泛型），我个人倒是没有什么急切需求。generics虽然可以让大幅提升语言的表现力(expressiveness)，但也给语言自身带来了较大的复杂性。就个人感受而言，C++就是在加入generics后才变得无比庞大和复杂的，同时generics还让很多C++ programmer沉溺于很多magic trick中无法自拔，这对于以“合作分工”为主流的软件开发过程来说，并不是好事情。</p>
<h3>6、Go 官方团队已发布 2.0 计划，更侧重于兼容性和规模化方面。对此，您怎么理解？Go 否已达到最佳性能？</h3>
<p>这个问题和上面的问题有些类似，我的想法差不多。Go team在特性演进方面会十分谨慎，这也是go Team一贯的风格。从Go1到Go2，从现在看来，这个时间跨度不会很短，也许是2-3年也不一定，心急吃不了热豆腐^0^，社区分裂可不是go team想看到的事情，python可是前车之鉴。</p>
<p>另外，Go性能显然还是有改善空间的，尤其是编译性能、GC吞吐和延迟的tradeoff方面；另外goroutine调度器算法方面可能还有改进空间。当前Goroutine调度算法的实现者<a href="https://github.com/dvyukov">Dmitry Vyukov</a>之前就编写了一个scheduler优化的proposal: <a href="https://docs.google.com/document/d/1d3iI2QWURgDIsSR6G2275vMeQ_X7w-qxM2Vp7iGwwuM/pub">NUMA-aware scheduler for Go</a>(针对numa体系的优化)，但也许因为重要性、优先级等考量，一直没有实现，也许后续会实现。</p>
<h3>7、Go 在国内似乎比国外还要火，您认为造成这种现象的原因是什么？</h3>
<p>从一些搜索引擎的trend数据来看，Go在中国地区的确十分火热，甚至在热度值上是领先于欧美世界的。个人觉得造成这种现象的原因可能有如下几点：</p>
<ul>
<li>语言本身的接受度高</li>
</ul>
<p>首先，从Go语言本身考虑。事实证明了：Go语言的设计匹配了国内程序员的行业业务需求和对语言特性的需求(口味)：<br />
 a) 语言：<a href="http://tonybai.com/2017/04/20/go-coding-in-go-way/">简单、正交组合和并发</a>；开发效率和运行效率双高；<br />
 b) 自带battery：丰富的标准库和高质量第三方库；<br />
 c) 迎合架构趋势：天生适合微服务&#8230;.</p>
<ul>
<li>引入早且与Go advocator的努力分不开</li>
</ul>
<p>当前再也不是那个“酒香不怕巷子深”的年代了，再好的编程语言也需要推广和宣称。Go team在<a href="https://github.com/golang/go/wiki">社区建设</a>、全世界推广方面也是不遗余力。至于国内更是有像许式伟、<a href="https://github.com/astaxie">Astaxie</a>这样的占据高端IT圈子的advocator在站台宣传。</p>
<ul>
<li>互联网飞速发展推动Go在国内落地</li>
</ul>
<p>中国已经是事实的移动互联网时代的领军者，大量创业公司如雨后春笋般诞生。而Go对于startup企业来说是极其适合的。开发效率高，满足了Startup企业对产品或服务快速发布的需求；运行效率高可以让startup公司节省初期在硬件方面的投入：一台主机顶住100w并发。</p>
<p>对于那些巨头、大公司而言，Go又是云计算时代基础设施的代表性语言，自然也会投入到Go怀抱，比如：阿里CDN、百度门户入口、滴滴、360等。</p>
<h3>8、对于刚开始学习 Go ，并期待将其应用在项目中的新人们，您有哪些建议？</h3>
<p>学语言，无非实践结合理论。</p>
<ul>
<li>理论：书籍和资料</li>
</ul>
<p>这里转一下我<a href="https://www.zhihu.com/question/30461290/answer/142764934">在知乎上一个回答</a>：</p>
<p>强烈推荐：Rob Pike 3-day Go Course，虽然语法过时了，但看大师的slide，收获还是蛮多的。</p>
<p>Go基础:  Go圣经《<a href="https://book.douban.com/subject/26337545/">The Go Programming Language</a>》和《<a href="https://book.douban.com/subject/25858023/">Go in Action</a>》。<br />
原理学习: 雨痕的《<a href="https://book.douban.com/subject/26832468/">Go学习笔记</a>》。<br />
Go Web编程: 直接看astaxie在github上的《<a href="https://github.com/astaxie/build-web-application-with-golang">Go web编程</a>》。</p>
<p>还有一本内容有些旧的，但个人觉得值得一看的书就是《<a href="https://book.douban.com/subject/10558892/">The Way To Go</a>》，大而全。Github上有部分章节的<a href="https://github.com/Unknwon/the-way-to-go_ZH_CN">中译版</a>。</p>
<p>另外，建议看一遍官方的<a href="https://golang.org/ref/spec">Language specification</a>、<a href="https://golang.org/doc/effective_go.html">effective go</a>和<a href="https://golang.org/doc/faq">go faq</a>，对学go、理解go设计的来龙去脉大有裨益。</p>
<ul>
<li>实践：多读多写Code</li>
</ul>
<p>多读代码：首选标准库，因为Go的惯用法和最佳实践在标准库中都有体现。</p>
<p>写代码：这个如果有项目直接实践那是非常的幸福；否则可以从改写一个自己熟悉领域的工具开始。比如：以前我刚接触Go的时候，没啥可写的。就改写一套<a href="https://github.com/bigwhite/gocmpp">cmpp协议实现</a>。后来做wechat接口，实现了一个简单的<a href="https://github.com/bigwhite/gowechat">wechat基本协议</a>，当然这两个代码也过于陈旧了，代码设计以及其中的go语言用法不值得大家学习了^0^。</p>
<hr />
<p>微博：<a href="http://weibo.com/bigwhite20xx">@tonybai_cn</a><br />
微信公众号：iamtonybai<br />
github.com: https://github.com/bigwhite</p>
<p style='text-align:left'>&copy; 2017, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2017/10/24/go-evolution-for-ten-years-an-interview-by-osc/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Go coding in go way</title>
		<link>https://tonybai.com/2017/04/20/go-coding-in-go-way/</link>
		<comments>https://tonybai.com/2017/04/20/go-coding-in-go-way/#comments</comments>
		<pubDate>Thu, 20 Apr 2017 08:47:42 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Adapter]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[composition]]></category>
		<category><![CDATA[Concurrency]]></category>
		<category><![CDATA[Context]]></category>
		<category><![CDATA[corevalues]]></category>
		<category><![CDATA[Cpp]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[error]]></category>
		<category><![CDATA[exception]]></category>
		<category><![CDATA[GC]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[go-coding-in-go-way]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[Gopher]]></category>
		<category><![CDATA[gopherchina]]></category>
		<category><![CDATA[GopherCon]]></category>
		<category><![CDATA[goroutine]]></category>
		<category><![CDATA[Haskell]]></category>
		<category><![CDATA[idiomatic]]></category>
		<category><![CDATA[Interface]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[Lisp]]></category>
		<category><![CDATA[middleware]]></category>
		<category><![CDATA[Package]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[RobPike]]></category>
		<category><![CDATA[Ruby]]></category>
		<category><![CDATA[runtime]]></category>
		<category><![CDATA[RussCox]]></category>
		<category><![CDATA[Unix]]></category>
		<category><![CDATA[wrapper]]></category>
		<category><![CDATA[价值观]]></category>
		<category><![CDATA[图灵奖]]></category>
		<category><![CDATA[并发]]></category>
		<category><![CDATA[并行]]></category>
		<category><![CDATA[接口]]></category>
		<category><![CDATA[标准库]]></category>
		<category><![CDATA[正交组合]]></category>
		<category><![CDATA[类型体系]]></category>
		<category><![CDATA[组合]]></category>
		<category><![CDATA[继承]]></category>
		<category><![CDATA[编程思维]]></category>
		<category><![CDATA[萨丕尔-沃夫假说]]></category>
		<category><![CDATA[错误处理]]></category>

		<guid isPermaLink="false">http://tonybai.com/?p=2298</guid>
		<description><![CDATA[本篇文章是我在2017年第三届GopherChina大会上所作talk：”Go coding in go way“的改编和展开版，全文如下。 一、序 今天我要分享的题目是“Go coding in go way”，中文含义就是用“Go语言编程思维去写Go代码”。看到这个题目大家不禁要问：究竟什么是Go语言编程思维呢？关于什么是Go语言变成思维其实并没有官方说法。这里要和大家交流的内容都是基于Go诞生七年多以来我个人对Go的设计者、Go team以及Go主流社区的观点和代码行为的整理、分析和总结。希望通过我的这次“抛砖引玉”，让在座的Gopher们对“Go语言编程思维”有一个初步的认知，并在日常开发工作中遵循Go语言的编程思维，写出idiomatic的Go代码。 二、编程语言与编程思维 1、大师的观点 在人类自然语言学界有一个很著名的假说：”萨丕尔-沃夫假说“，这个假说的内容是这样的： 语言影响或决定人类的思维方式("Language inuences/determines thought" - Sapir-Whorf hypothesis ) 说到这个假说，我们不能不提及在2017年初国内上映了一部口碑不错的美国科幻大片《降临》，这部片子改编自雨果奖获得者华裔科幻小说家Ted姜的《你一生的故事》，片中主线剧情的理论基础就是就是“萨丕尔-沃夫假说”。更夸张的是片中直接将该假说应用到外星人语言上，将其扩展到宇宙范畴^_^。片中的女主作为人类代表与外星人沟通，并学会了外星语言，从此思维大变，拥有了预知未来的“超能力”。由此我们可以看出“选择对一门语言是多么的重要”。 奇妙的是，在编程语言界，有位大师级人物也有着与”萨丕尔-沃夫假说”异曲同工的观点和认知。他就是首届图灵奖得主、著名计算机科学家Alan J. Perlis(艾伦·佩利)。他从另外一个角度提出了： “不能影响到你的编程思维方式的编程语言不值得去学习和使用” A language that doesn't aect the way you think about programming is not worth knowing. 2、现实中的“投影” 从上述大师们的理论和观点，我们似乎看到了语言与思维之间存在着某种联系。那么两者间的这种联系在真实编程世界中的投影又是什么样子的呢？我们来看一个简单的编程问题： 【问题: 素数筛】 问题描述：素数是一个自然数，它具有两个截然不同的自然数除数：1和它本身。 要找到小于或等于给定整数n的素数。针对这个问题，我们可以采用埃拉托斯特尼素数筛算法。 算法描述：先用最小的素数2去筛，把2的倍数剔除掉；下一个未筛除的数就是素数(这里是3)。再用这个素数3去筛，筛除掉3的倍数... 这样不断重复下去，直到筛完为止。 算法动图 下面是该素数筛算法的不同编程语言的实现版本。 C语言版本： 【sieve.c】 void [...]]]></description>
			<content:encoded><![CDATA[<p>本篇文章是我在2017年<a href="http://tonybai.com/2017/04/18/my-experience-of-gopherchina-2017-as-a-speaker/">第三届GopherChina大会</a>上所作talk：”<a href="(https://github.com/bigwhite/talks/blob/master/gopherchina/2017/go-coding-in-go-way-cn.slide)">Go coding in go way</a>“的改编和展开版，全文如下。</p>
<h2>一、序</h2>
<p>今天我要分享的题目是<strong>“Go coding in go way”</strong>，中文含义就是用<strong>“Go语言编程思维去写Go代码”</strong>。看到这个题目大家不禁要问：究竟什么是<a href="https://golang.org/">Go语言</a>编程思维呢？关于什么是Go语言变成思维其实并没有官方说法。这里要和大家交流的内容都是基于<a href="https://blog.golang.org/7years">Go诞生七年</a>多以来我个人对Go的设计者、Go team以及Go主流社区的观点和代码行为的整理、分析和总结。希望通过我的这次“抛砖引玉”，让在座的Gopher们对“Go语言编程思维”有一个初步的认知，并在日常开发工作中遵循Go语言的编程思维，写出idiomatic的Go代码。</p>
<h2>二、编程语言与编程思维</h2>
<h3>1、大师的观点</h3>
<p>在人类自然语言学界有一个很著名的假说：”<a href="https://en.wikipedia.org/wiki/Linguistic_relativity">萨丕尔-沃夫假说</a>“，这个假说的内容是这样的：</p>
<pre><code>语言影响或决定人类的思维方式("Language inuences/determines thought" - Sapir-Whorf hypothesis )
</code></pre>
<p>说到这个假说，我们不能不提及在2017年初国内上映了一部口碑不错的美国科幻大片《<a href="https://movie.douban.com/subject/21324900/">降临</a>》，这部片子改编自雨果奖获得者华裔科幻小说家Ted姜的《<a href="https://book.douban.com/subject/1187842/">你一生的故事</a>》，片中主线剧情的理论基础就是就是“萨丕尔-沃夫假说”。更夸张的是片中直接将该假说应用到外星人语言上，将其扩展到宇宙范畴^_^。片中的女主作为人类代表与外星人沟通，并学会了外星语言，从此思维大变，拥有了预知未来的“超能力”。由此我们可以看出“选择对一门语言是多么的重要”。</p>
<p>奇妙的是，在编程语言界，有位大师级人物也有着与”萨丕尔-沃夫假说”异曲同工的观点和认知。他就是首届图灵奖得主、著名计算机科学家<a href="https://en.wikipedia.org/wiki/Alan_Perlis">Alan J. Perlis(艾伦·佩利)</a>。他从另外一个角度提出了：</p>
<pre><code>“不能影响到你的编程思维方式的编程语言不值得去学习和使用”

A language that doesn't aect the way you think about programming is not worth knowing.
</code></pre>
<h3>2、现实中的“投影”</h3>
<p>从上述大师们的理论和观点，我们似乎看到了语言与思维之间存在着某种联系。那么两者间的这种联系在真实编程世界中的投影又是什么样子的呢？我们来看一个简单的编程问题：</p>
<pre><code>【问题: 素数筛】

  问题描述：素数是一个自然数，它具有两个截然不同的自然数除数：1和它本身。 要找到小于或等于给定整数n的素数。针对这个问题，我们可以采用埃拉托斯特尼素数筛算法。
  算法描述：先用最小的素数2去筛，把2的倍数剔除掉；下一个未筛除的数就是素数(这里是3)。再用这个素数3去筛，筛除掉3的倍数... 这样不断重复下去，直到筛完为止。
</code></pre>
<p><img src="http://tonybai.com/wp-content/uploads/Sieve_of_Eratosthenes_animation.gif" alt="img{512x368}" /><br />
算法动图</p>
<p>下面是该素数筛算法的不同编程语言的实现版本。</p>
<p>C语言版本：</p>
<pre><code>【sieve.c】

void sieve() {
        int c, i,j,numbers[LIMIT], primes[PRIMES];

        for (i=0;i&lt;LIMIT;i++){
                numbers[i]=i+2; /*fill the array with natural numbers*/
        }

        for (i=0;i&lt;LIMIT;i++){
                if (numbers[i]!=-1){
                        for (j=2*numbers[i]-2;j&lt;LIMIT;j+=numbers[i])
                                numbers[j]=-1; /*sieve the non-primes*/
                }
        }

        c = j = 0;
        for (i=0;i&lt;LIMIT&amp;&amp;j&lt;PRIMES;i++) {
                if (numbers[i]!=-1) {
                        primes[j++] = numbers[i]; /*transfer the primes to their own array*/
                        c++;
                }
        }

        for (i=0;i&lt;c;i++) printf("%d\n",primes[i]);
}

</code></pre>
<p><a href="https://www.haskell.org/">Haskell</a>版本：</p>
<pre><code>【sieve.hs】

sieve [] = []
sieve (x:xs) = x : sieve (filter (\a -&gt; not $ a `mod` x == 0) xs)

n = 100
main = print $ sieve [2..n]

</code></pre>
<p>Go语言版本：</p>
<pre><code>【sieve.go】

func generate(ch chan&lt;- int) {
    for i := 2; ; i++ {
        ch &lt;- i // Send 'i' to channel 'ch'.
    }
}

func filter(src &lt;-chan int, dst chan&lt;- int, prime int) {
    for i := range src { // Loop over values received from 'src'.
        if i%prime != 0 {
            dst &lt;- i // Send 'i' to channel 'dst'.
        }
    }
}

func sieve() {
    ch := make(chan int) // Create a new channel.
    go generate(ch)      // Start generate() as a subprocess.
    for {
        prime := &lt;-ch
        fmt.Print(prime, "\n")
        ch1 := make(chan int)
        go filter(ch, ch1, prime)
        ch = ch1
    }
}

</code></pre>
<ul>
<li>C版本的素数筛程序是一个常规实现。它定义了两个数组：numbers和primes，“筛”的过程在numbers这个数组中进行(纯内存修改)，非素数的数组元素被设置为-1，便于后续提取；</li>
<li>Haskell版本采用了函数递归的思路，通过 “filter操作集合”，用谓词(过滤条件）\a -> not $ a <code>mod</code> x == 0；筛除素数的倍数，将未筛除的数的集合作为参数传递归递给下去；</li>
<li>Go版本的素数筛实现采用的是goroutine的并发组合。程序从2开始，依次为每个素数建立一个goroutine，用于作为筛除该素数的倍数。ch指向当前最新输出素数所位于的筛子goroutine的源channel，这段代码来自于Rob Pike的一次关于concurrency的分享slide。</li>
</ul>
<p><img src="http://tonybai.com/wp-content/uploads/primesieve.gif" alt="img{512x368}" /></p>
<h3>3、思考</h3>
<p>通过上述这个现实中的问题我们可以看到：面对同一个问题，来自不同编程语言的程序员给出了思维方式截然不同的解决方法：C的命令式思维、Haskell的函数式思维和Go的并发思维。这一定程度上印证了前面的假说：编程语言影响编程思维。</p>
<p><a href="http://tonybai.com/tag/go">Go语言</a>诞生较晚（2007年设计、2009年发布Go1），绝大多数Gopher(包括我在内)第一语言都不是Go，都是“半路出家”从其他语言转过来的，诸如：<a href="http://tonybai.com/tag/c">C</a>、C++、<a href="http://tonybai.com/tag/java">Java</a>、<a href="http://tonybai.com/tag/python">Python</a>等，甚至是Javascript、<a href="http://tonybai.com/tag/haskell">Haskell</a>、<a href="http://tonybai.com/tag/clisp">Lisp</a>等。由于Go语言上手容易，在转Go的初期大家很快就掌握了Go的语法。但写着写着，就是发现自己写的代码总是感觉很别扭，并且总是尝试在Go语言中寻找自己上一门语言中熟悉的语法元素；自己的代码风格似乎和Go stdlib、主流Go开源项目的代码在思考角度和使用方式上存在较大差异。而每每看到Go core team member(比如：rob pike)的一些代码却总有一种醍醐灌顶的赶脚。这就是我们经常说的go coding in c way、in java way、in python way等。出现这种情况的主要原因就是大脑中的原有语言的思维方式在“作祟”。</p>
<p>我们学习和使用一门编程语言，目标就是要用这门语言思维方式去Coding。学习Go，就要用Go的编程思维去写Go代码，而不是用其他语言的思维方式。</p>
<h3>4、编程语言思维的形成</h3>
<p>人类语言如何影响人类思维这个课题自然要留给人类语言学家去破解。但编程语言如何影响编程思维，每个程序员都有着自己的理解。作为一个有着十几年编程经验的程序员，我认为可以用下面这幅示意图来解释一门编程语言思维的形成机制：</p>
<p><img src="http://tonybai.com/wp-content/uploads/language-influnce-model.png" alt="img{512x368}" /></p>
<p>决定编程语言思维的根本在于这门编程语言的价值观！什么是价值观？个人认为：一门编程语言的价值观就是这门语言的最初设计者对程序世界的认知。不同编程语言的价值观不尽相同，导致不同编程语言采用不同的语法结构，不同语言的使用者拥有着不同的思维方式，表现出针对特定问题的不同的行为（具现为：代码设计上的差异和代码风格上的不同），就像上面素数筛那样。比如：</p>
<pre><code>C的价值观摘录：

- 相信程序员：提供指针和指针运算，让C程序员天马行空的发挥
- 自己动手，丰衣足食：提供一个很小的标准库，其余的让程序员自造
- 保持语言的短小和简单
- 性能优先

C++价值观摘录：

- 支持多范式，不强迫程序员使用某个特定的范式
- 不求完美，但求实用（并且立即可用）
</code></pre>
<p>此外，从上述模型图，我们可以看出思维和结构影响语言应用行为，这是语言应用的核心；同时存在一个反馈机制：即语言应用行为会反过来持续影响/优化语言结构。我们常用冰山表示一个事物的表象与内涵。如果说某种语言的惯用法idiomatic tips或者best practice这些具体行为是露出水面上的冰山头部，那么价值观和思维方式就是深藏在水面以下的冰山的基座。</p>
<h2>三、Go语言价值观的形成与Go语言价值观</h2>
<p>从上述模型来看，编程语言思维形成的主导因素是这门编程语言的价值观，因此我们首先来看一下Go语言价值观的形成以及Go语言价值观的具体内容。</p>
<p>Go语言的价值观的形成我觉得至少有三点因素。</p>
<h3>1、语言设计者&amp;Unix文化</h3>
<p>Go语言价值观形成是与Go的初期设计者不无关系的，可以说Go最初设计者主导了Go语言价值观的形成！这就好比一个企业的最初创始人缔造企业价值观和文化一样。</p>
<p><img src="http://tonybai.com/wp-content/uploads/GPT.png" alt="img{512x368}" /></p>
<p>图中是Go的三位最初设计者，从左到右分别是罗伯特·格瑞史莫、罗伯·派克和肯·汤普逊。Go初期的所有features adoption是需要三巨头达成一致才行。三位设计者有一个共同特征，那就是深受Unix文化熏陶。罗伯特·格瑞史莫参与设计了Java的HotSpot虚拟机和Chrome浏览器的JavaScript V8引擎，罗博·派克在大名鼎鼎的bell lab侵淫多年，参与了Plan9操作系统、C编译器以及多种语言编译器的设计和实现，肯·汤普逊更是图灵奖得主、Unix之父。关于Unix设计哲学阐述最好的一本书莫过于埃瑞克.理曼德(Eric S. Raymond)的《<a href="https://book.douban.com/subject/1467587/">UNIX编程艺术</a>》了，该书中列举了很多unix的哲学条目，比如：简单、模块化、正交、组合、pipe、功能短小且聚焦等。三位设计者将Unix设计哲学应用到了Go语言的设计当中，因此你或多或少都能在Go的设计和应用中找到这些哲学的影子。</p>
<h3>2、遗传基因</h3>
<p>Go并发模型CSP理论的最初提出者Tony Hoare曾提出过这样一个观点：</p>
<pre><code>The task of the programming language designer " is consolidation not innovation ". (Tony Hoare, 1973).
编程语言设计者的任务不是创新，而是巩固。
</code></pre>
<p><img src="http://tonybai.com/wp-content/uploads/go-ancestors.png" alt="img{512x368}" /></p>
<p>和其他语言一样，Go也是站在巨人肩膀上的。这种基因继承，不仅仅是语法结构的继承，还有部分价值观的继承和进一步认知改进。当然不可否认的是Go也有自己的微创新，比如： implicit interface implementation、首字母大小写决定visibility等。虽然不受学院派待见，但把Go的这些微创新组合在一起，你会看到Go迸发出了强大的表现力。</p>
<h3>3、面向新的基础设施环境和大规模软件开发的诸多问题</h3>
<p>有一种开玩笑的说法：Go诞生于C++程序的漫长compile过程中。如果c++编译很快，那么上面的Go语言三巨头也没有闲暇时间一起喝着咖啡并讨论如何设计一门新语言。</p>
<p>面对时代变迁、面对新的基础设施环境、多核多处理器平台的出现，很多传统语言表现出了“不适应”，这直接导致在开发大规模软件过程中出现了各种各样的问题，比如：构建缓慢、依赖失控、代码风格各异、难用且复杂无法自动化的工具、跨语言构建难等。Go的设计初衷就是为了面向新环境、面向解决问题的。虽然这些问题不都是能在语言层面解决的，但这些环境和问题影响了设计者对程序世界的认知，也就影响了Go的价值观。</p>
<h3>4、Go语言的价值观</h3>
<p>在明确了Go价值观的形成因素后，我认为Go语言的价值观至少包含以下几点：</p>
<pre><code> - Overall Simplicity 全面的简单
 - Orthogonal Composition 正交组合
 - Preference in Concurrency 偏好并发
</code></pre>
<p>用一句话概括Go的价值观（也便于记忆）：</p>
<pre><code>Go is about orthogonal composition of simple concepts with preference in concurrency.
Go是在偏好并发的环境下的简单概念/事物的正交组合
</code></pre>
<p>无论是Go语言设计还是Go语言使用，都受到上述价值观的影响。接下来我们逐个来看一下Go语言价值观主导下的Go语言编程思维，并看看每种编程思维在语言设计、标准库实现以及主流Go开源项目中的应用体现。我将按“价值观” -> “(价值观下的)语言设计” -> “编程思维” -> “编程思维体现”的顺序展开。</p>
<h2>四、Overall Simplicity</h2>
<p>Go的第一个价值观就是”全面简单”。</p>
<p>图灵奖获得者迪杰斯特拉说过：”简单和优雅不受欢迎，那是因为人们要想实现它们需要更努力地工作，更多自律以及领会更多的教育。” 而Go的设计者们恰恰在语言设计初期就将复杂性留给了语言自身的设计和实现阶段，留给了Go core team，而将简单留给了gopher们。因此，Simplicity价值观更多地体现在了Go语言设计层面。 这里简单列举一些：</p>
<pre><code>- 简洁、正规的语法：大大降低Go入门门槛，让来自其他语言的初学者可以轻松使用Go语言；
- 仅仅25个keyword：主流编程语言中最简单的，没有之一；
-  “一种”代码写法、尽可能减少程序员付出;
- 垃圾回收GC: 降低程序员在内存管理方面的心智负担；
- goroutine：提供最简洁的并发支持；
- constants：对传统语言中常量定义和使用方法做进一步简化；
- interface：纯方法集合，纯行为定义，隐式实现；
- package：Go程序结构层面的唯一组织形式，它将设计、语法、命名、构建、链接和测试都聚于一包中，导入和使用简单。
</code></pre>
<p>如今，Go语言的简单已经从自身设计扩展到Go应用的方方面面，但也正是由于在语言层面已经足够简单了，因此在应用层面，“简单”体现的反倒不是很明显，更加顺其自然。接下来，我总结整理几个在“全面简单”价值观下形成的Go编程思维，我们一起看一下。</p>
<h3>1、short naming thought（短命名思维）</h3>
<p>在gofmt的帮助下，Go语言一统coding style。在这样的一个情形下，naming成了gopher们为数不多可以“自由发挥”的空间了。但对于naming，Go有着自己的思维：短命名。即在并不影响readablity的前提下，尽可能的用长度短小的标识符，于是我们经常看到用单个字母命名的变量，这与其他一些语言在命名上的建议有不同，比如Java建议遵循“见名知义”的命名原则。</p>
<p>Go一般在上下文环境中用最短的名字携带足够的信息，我们可以对比一下Java和Go：</p>
<pre><code>   java    vs. go

  "index" vs. "i"
  "value" vs. "v"
</code></pre>
<p>除了短小，Go还要求尽量在一定上下文中保持命名含义的一致性，比如：在一个上下文中，我们声明了两个变量b，如果第一个b表意为buf，那么后续的b也最好是这个含义。</p>
<p>在命名短小和一致性方面，stdlib和知名开源项目为我们做出表率。我们统计一下Go标准库中标识符使用频率，可以看到大量单字母命名的变量标识符占据top30：</p>
<pre><code>cat $(find $GOROOT -name '*.go') | indents | sort | uniq -c | sort -nr | sed 30q
          60224 v
          42444 err
          38012 t
          33386 x
          33302 i
          33277 b
          27943 p
          25121 s
          21228 n
          20831 r
          20634 _
          19236 c
          17056 y
          12850 f
          12834 a
          ... ...
</code></pre>
<p>细致分析了一下stdlib中常见短变量名字所代表的含义（见代码后的注释），stdlib在一致性方面做的还是不错的，当然也有例外。</p>
<pre><code>        [v, k, i]

        // loop varible
        for i, v := range s {
        for k, v := range m {
        for v := range r { // channel

        // if、switch/case clause varible
        if v := mimeTypes[ext]; v != "" {
        switch v := ptr.Elem(); v.Kind() {
        case v := &lt;-c:

        v := reflect.ValueOf(x) // result of reflect.Value()

        [t]

        t := time.Now() // time
        t := &amp;Timer{ // timer
        if t := md.typemap[off]; t != nil { // type

        [b]

        b := make([]byte, n) // bytes slice
        b := new(bytes.Buffer) // bytes.Buffer
</code></pre>
<h3>2、minimal thought（最小思维)</h3>
<p>码农们是苦逼的，每天坐在电脑前一坐就是10多个小时，自己的“业余”时间已经很少了。Go语言的设计者在这方面做的很“贴心”，考虑到为了让男Gopher能有时间撩妹，女Gopher能有时间傍帅哥，Go语言倡导minimal thought，即尽可能的降低gopher们的投入。这种思维体现在语言设计、语言使用、相关工具使用等多个方面。比如：</p>
<ul>
<li>一种代码风格：程序员们再也无需为coding style的个人喜好而争论不休了，节省下来的时间专心做自己喜欢的事情:)</li>
<li>“一种”代码写法(或提供最少的写法、更简单的写法)：你会发现，面对一个问题，大多数gopher们给出的go实现方式都类似。这就是Go“一种代码写法”思维的直接体现。Go在语法结构层面没有提供给Gopher很大的灵活性。Go is not a “TMTOWTDI — There’s More Than One Way To Do It”。这点与C++、Ruby有着很大不同。</li>
<li>显式代码（obvious），而不是聪明(clever)代码：Go提倡显而易见、可读性好的代码，而非充满各种技巧或称为“炫技”的所谓“聪明”代码。纵观stdlib、kubernetes等go代码库，都是很obvious(直观)的go code，clever code难觅踪迹。这样一来，别人写的代码，你可以轻松地看懂（为你节省大量时间和精力）。这也是Go代码中clever code比例远远小于其他主流语言的原因，Go不是炫技的舞台。</li>
</ul>
<p>C++程序员看到这里是不是在“抹眼泪”，这里并非黑C++，C++的复杂和多范式是客观的，C++98标准和C++17标准的差异甚至可以用“两门语言”来形容，用泛型的代码和不用泛型的代码看起来也像是两门完全不同的语言，这种心智负担之沉重可想而知。</p>
<p>接下来，我们看看minimal thought在语言设计和应用中的体现。</p>
<h4>1) “一种”循环: for</h4>
<p>Go语言仅仅提供了一种用于“循环逻辑”的关键字：for。在其他语言中的各种用于循环逻辑的关键字，比如while, do-while等，在go中都可以通过for模拟实现。</p>
<pre><code>- 常规
  for i := 0; i &lt; count; i++ {}

- "while"
  for condition { }

- "do-while"
  for { // use "for-break" instead
        doSomething()
        if condition { break }
  }

- iterator loop
  for k, v := range f.Value {}

- dead loop
  for {}
</code></pre>
<h4>2) “一种”constant</h4>
<p>前面说过Go设计者是十分体贴的，这种体贴体现在很多不起眼的细节上，比如对传统语言中constant声明和使用的优化。</p>
<p>Go语言中constants只是数字，无论是整型还是浮点型都可以直接写成数字，无需显式地赋给类型：</p>
<pre><code>  const incomingQueueLength = 25

  const (
      http2minMaxFrameSize = 1 &lt;&lt; 14
      http2maxFrameSize    = 1&lt;&lt;24 - 1
  )

  const PI = 3.1415928
  const e = 1E6
</code></pre>
<p>参与计算的constant无需显式算术转换，而是由编译器自动确定语句中constant的承载类型：</p>
<pre><code>  const a = 10080
  var c int32 = 99
  d := c + a
  fmt.Printf("%T\n", d) //int32
  fmt.Printf("%T\n", a) //int

</code></pre>
<h4>3) “一种”错误处理方法</h4>
<p>C++之父Bjarne Stroustrup说过：“世界上有两类编程语言，一类是总被人抱怨诟病的，而另外一类是无人使用的”。Go语言自其出生那天起，就因错误处理机制看起来似乎有些过时、有些简单而被大家所诟病，直至今天这种声音依旧存在。因为Go仅仅提供了一种基于值比较的简单的错误处理方法。但就是这样的错误处理方法也恰恰是Go设计者simplicity价值观的体现。Go设计者认为如果像其他一些主流语言那样，将exception的try-catch-finally的机制与语言的控制结构耦合在一起，将势必大大增加语言的复杂性，这与simplicity的价值观是背道而驰的。简单的基于值比较的error处理方法可以让使用者更重视每一个error并聚焦于错误本身。显式地去处理每一个error，让gopher对代码更有自信。并且在这种机制下，错误值和其他类型的值地位是一样的，错误处理代码也是普通代码，并无特殊之处，无特殊化处理，无需增加语言复杂性。</p>
<p>这些年来，gopher们也初步探索出了这种错误处理方法的常见处理模式，我们以stdlib中识别出的error handling模式为例：</p>
<p><strong>a) 最常见的</strong></p>
<p>在外部无需区分返回的错误值的情况下，可以在内部通过fmt.Errorf或errors.New构造一个临时错误码并返回。这种错误处理方式可以cover 80%以上情形：</p>
<pre><code>    callee:
    return errors.New("something error")

    or

    return fmt.Errorf("something error: %s", "error reason")

  caller:
    if err != nil {... }

</code></pre>
<p><strong>b) 导出的Error变量</strong></p>
<p>当外部需要区分返回的错误值的，比如这里我要进行一个io调用，后续的操作逻辑需要因io调用的返回错误码的不同而异，我们使用导出的error变量：</p>
<pre><code>  // io/io.go
  var ErrShortWrite = errors.New("short write")
  var ErrShortBuffer = errors.New("short buffer")

  if err := doSomeIO(); err == io.ErrShortWrite { ... }
</code></pre>
<p><strong>c) 定义新的错误类型实现定制化错误上下文</strong></p>
<p>上面的导出Error变量中包含的error context信息和信息形成机制太过简单，当我们要定制error context时， 我们可以定义一个新的Error type。之后通过针对error interface value的type assertion or type switch得到真实错误类型并访问error context：</p>
<pre><code>  // encoding/json/decode.go
  type UnmarshalTypeError struct {
      Value  string       // description of JSON value - "bool", "array", "number -5"
      Type   reflect.Type // type of Go value it could not be assigned to
      Offset int64        // error occurred after reading Offset bytes
      Struct string       // name of the struct type containing the field
      Field  string       // name of the field holding the Go value
  }

  func (e *UnmarshalTypeError) Error() string {
      ... ...
      return "json: cannot unmarshal " + e.Value + " into Go value of type " + e.Type.String()
  }

  if serr, ok := err.(*UnmarshalTypeError); ok {
     //use serr to access context in UnmarshalTypeError
     ... ...
  }
</code></pre>
<p>不过这样的用法并不推荐也并不多见，在stdlib、kubernetes中，虽然都有自定义的exported error type，但是却很少在外部通过type assertion直接访问其内部error context字段，那标准库是怎么判断error差别的呢？</p>
<p>**d) 包含package中error公共行为特征的Error interface type</p>
<p>在标准库中，我们可以发现这样一种错误处理方式：将某个包中的Error Type归类，统一提取出一些公共行为特征，并且将这些公共行为特征(behaviour)放入一个公开的interface type中。以stdlib中的net package为例。net package将包内的所有错误类型的公共特征抽象放入”Error”这个error type中。外部使用时，通过这个公共interface获取具体错误的特征：</p>
<pre><code>//net/net.go
  type Error interface {
      error
      Timeout() bool   // Is the error a timeout?
      Temporary() bool // Is the error temporary?
  }

net/http/server.go中的使用举例：

  rw, e := l.Accept()
  if e != nil {
      if ne, ok := e.(net.Error); ok &amp;&amp; ne.Temporary() {
         ... ..
      }
      ... ...
  }

</code></pre>
<p>OpError是net packge中的一个自定义error type , 它实现了Temporary interface, 可以被外部统一用Error的method判断是否是Temporary或timeout error特征：</p>
<pre><code>  //net/net.go
  type OpError struct {
      ... ...
      // Err is the error that occurred during the operation.
      Err error
  }

  type temporary interface {
      Temporary() bool
  }

  func (e *OpError) Temporary() bool {
    if ne, ok := e.Err.(*os.SyscallError); ok {
        t, ok := ne.Err.(temporary)
        return ok &amp;&amp; t.Temporary()
    }
    t, ok := e.Err.(temporary)
    return ok &amp;&amp; t.Temporary()
  }
</code></pre>
<p>**e) 通过一些公开的error behaviour function对error behaviour进行判断</p>
<p>我们在标准库中还能看到一种判断error behavior的方法，那就是通过一些公开的error behaviour function。比如：os包暴露的IsExist等函数：</p>
<pre><code>  //os/error.go

  func IsExist(err error) bool {
      return isExist(err)
  }
  func IsNotExist(err error) bool { ... }
  func IsPermission(err error) bool { ... }

  例子：

    f, err := ioutil.TempFile("", "_Go_ErrIsExist")
    f2, err := os.OpenFile(f.Name(), os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600)
    if os.IsExist(err) {
        fmt.Println("file exist")
        return
    }

</code></pre>
<p>顺便在这里提一下Go关于error type和variable的命名方式：</p>
<pre><code> 错误类型: xxxError

  //net/net.go
  type OpError struct { ... }
  type ParseError struct { ... }
  type timeoutError struct{}

导出的错误变量: ErrXxx

  //io/io.go
  var ErrShortWrite = errors.New("short write")
  var ErrNoProgress = errors.New("multiple Read calls return no data or error")

</code></pre>
<p>很多人抱怨，当前Go提供的error handling方案让gopher可以很容易地写出如下面所示的不优雅代码：</p>
<pre><code>  var err error
  err = doSomethingA()
  if err != nil {
      return err
  }

  err = doSomethingB()
  if err = nil {
      return err
  }

  err = doSomethingC()
  if err = nil {
      return err
  }
  ... ...
</code></pre>
<p>代码中大量重复着if err!= nil { return err} 这段snippet。但是如果你全面浏览过Go标准库中的代码，你会发现像上面这样的代码并不多见。Rob Pike曾经在《<a href="https://blog.golang.org/errors-are-values">errors are values</a>》一文中针对这个问题做过解释，并给了stdlib中的一些消除重复的方法：那就是将error作为一个内部状态：</p>
<pre><code>  //bufio/bufio.go
  type Writer struct {
      err error
      buf []byte
      n   int
      wr  io.Writer
  }
  func (b *Writer) Write(p []byte) (nn int, err error) {
      if b.err != nil {
          return nn, b.err
      }
      ... ...
  }

  //writer_demo.go
  buf := bufio.NewWriter(fd)
  buf.WriteString("hello, ")
  buf.WriteString("gopherchina ")
  buf.WriteString("2017")
  if err := buf.Flush() ; err != nil {
      return err
  }
</code></pre>
<p>error handling的具体策略要根据实际情况而定。stdlib面向的”业务域”相对”狭窄”，像bufio.Write可以采用上面的方法解决，但是对于做业务应用的gopher来讲，业务复杂多变，错误处理没有绝对的模式，需根据上下文不同而具体设计。但如果一个函数中上述snippet过多，很可能是这个函数或方法的职责过多导致，重构是唯一出路。</p>
<h2>五、Orthogonal Composition</h2>
<p>正交组合是我总结出来的第二个Go语言的价值观。如果说上一个价值观聚焦的是Go程序提供的各种小概念实体或者说”零件”的话，那么Composition就是在考虑如何将这些”零件”联系到一起，搭建程序的静态骨架。</p>
<p>在Go语言设计层面，Go设计者为gopher提供了正交的语法元素，供后续组合使用，包括：</p>
<ul>
<li>Go语言无类型体系(type hierarchy)，类型定义正交独立；</li>
<li>方法和类型的正交性: 每种类型都可以拥有自己的method set；</li>
<li>interface与其实现之间无”显式关联”；</li>
</ul>
<p>正交性为”组合”策略的实施奠定了基础，提供了前提。Rob Pike曾说过： “If C++ and Java are about type hierarchies and the taxonomy(分类）of types, Go is about composition.”。组合是搭建Go程序静态结构的主要方式。“组合”的价值观贯穿整个语言设计和语言应用：</p>
<ul>
<li>类型间的耦合方式直接影响到程序的结构；</li>
<li>Go语言通过“组合”构架程序静态结构；</li>
<li>垂直组合(类型组合)：Go通过 type embedding机制提供；</li>
<li>水平组合：Go通过interface语法进行“连接”。</li>
</ul>
<p>interface是水平组合的关键，好比程序肌体上的“关节”，给予连接“关节”的两个部分各自“自由活动”的能力，而整体上又实现了某种功能。</p>
<h3>1、vertical composition thought(垂直组合思维)</h3>
<p>传统OO语言都是通过继承的方式建构出自己的类型体系的，但Go语言中并没有类型体系的概念。Go语言通过类型的垂直组合而不是继承让单一类型承载更多的功能。由于不是继承，那么也就没有了所谓“父子类型”的概念，也没有向上、向下转型(type casting)；被嵌入的类型也不知道将其嵌入的外部类型的存在。调用Method时，method的匹配取决于方法名字，而不是类型。</p>
<p>Go语言通过type embedding实现垂直组合。组合方式莫过于以下这么几种：</p>
<p><strong>a) construct interface by embedding interface</strong></p>
<pre><code>  type ReadWriter interface {
      Reader
      Writer
  }

</code></pre>
<p>通过在interface中嵌入interface type name，实现接口行为聚合，组成大接口。这种方式在stdlib中尤为常用。</p>
<p><strong>b) construct struct by embedding interface</strong></p>
<pre><code>  type MyReader struct {
      io.Reader // underlying reader
      N int64   // max bytes remaining
  }
</code></pre>
<p><strong>c) construct struct by embedding struct</strong></p>
<pre><code>  // sync/pool.go
  type poolLocal struct {
      private interface{}   // Can be used only by the respective P.
      shared  []interface{} // Can be used by any P.
      Mutex                 // Protects shared.
      pad     [128]byte     // Prevents false sharing.
  }
</code></pre>
<p>在struct中嵌入interface type name和在struct嵌入struct，都是“委派模式(delegate)”的一种应用。在struct中嵌入interface方便快速构建满足某一个interface的dummy struct，方便快速进行unit testing，仅需实现少数需要的接口方法即可，尤其是针对Big interface时。</p>
<p>struct中嵌入struct，被嵌入的struct的method会被提升到外面的类型中，比如上述的poolLocal struct，对于外部来说它拥有了Lock和Unlock方法，但是实际调用时，method调用实际被传给poolLocal中的Mutex实例。</p>
<h3>2、small interface thought(小接口思维)</h3>
<p>interface是Go语言真正的魔法。前面提到过，interface好比程序肌体的骨架关节，上下连接着骨架部件。interface决定了Go语言中类型的水平组合方式。interface与其实现者之间的关系是隐式的，无需显式的”implements”声明(但编译器会做静态检查)；interface仅仅是method集合，而method和普通function一样声明，无需在特定位置。</p>
<p>在Go语言中，你会发现小接口（方法数量在1~3）定义占据主流。</p>
<pre><code>  // builtin/builtin.go
  type error interface {
      Error() string
  }

  // io/io.go
  type Reader interface {
      Read(p []byte) (n int, err error)
  }

  // net/http/server.go
  type Handler interface {
      ServeHTTP(ResponseWriter, *Request)
  }

  type ResponseWriter interface {
      Header() Header
      Write([]byte) (int, error)
      WriteHeader(int)
  }
</code></pre>
<p>我统计了一下stdlib、<a href="http://tonybai.com/tag/kubernetes">k8s</a>和docker里面的interface定义，画出了下面这幅接口个数与接口中method个数关系的折线图：</p>
<p><img src="http://tonybai.com/wp-content/uploads/itfmc.png" alt="img{512x368}" /></p>
<p>小接口方法少，职责单一；易于实现和测试，通用性强(如:io.Reader和Writer)，易于组合(如:io.Reader)。不过要想在业务领域定义出合适的小接口，还是需要对问题域有着透彻的理解的。往往无法定义出小接口，都是由于对领域的理解还不到位，没法抽象到很高的程度所致。</p>
<h3>3、horizontal composition thought(水平组合思维)</h3>
<p>有了小接口，后续主要关注如何通过接口进行“连接”的方式实现水平组合，以解决大问题、复杂的问题。通过interface进行组合的一种常见方法就是：通过接受interface类型参数的普通function进行组合。</p>
<p>以下几种具体形式：</p>
<p><strong>a) 基本形式</strong></p>
<p>接受interface value作为参数是水平组合的基本形式：</p>
<pre><code>形式：someFunc(interface value parameter)
</code></pre>
<p>隐式的interface实现会不经意间满足：依赖抽象、里氏替换原则、接口隔离等原则，这在其他语言中是需要很”刻意”的设计谋划的，但在Go interface来看，一切却是自然而然的。</p>
<pre><code>  func ReadAll(r io.Reader) ([]byte, error)
  func Copy(dst Writer, src Reader) (written int64, err error)
</code></pre>
<p><strong>b) wrapper function</strong></p>
<p>形式：接受interface类型参数，并返回与其参数类型相同的返回值</p>
<pre><code>  // Wrapper function:
  func LimitReader(r Reader, n int64) Reader { return &amp;LimitedReader{r, n} }

  type LimitedReader struct {
      R Reader // underlying reader
      N int64  // max bytes remaining
  }
  func (l *LimitedReader) Read(p []byte) (n int, err error) {}

  // Usage:
  r := strings.NewReader("some io.Reader stream to be read\n")
  lr := io.LimitReader(r, 4)
  if _, err := io.Copy(os.Stdout, lr); err != nil {
      log.Fatal(err)
  }
  // Output: some
</code></pre>
<p>LimitReader是一个wrapper function，它在r的外面再包裹上LimitedReader。通过wrapper function将NewReader和LimitedReader 的两者巧妙的组合在了一起。这样当我们采用包装后的reader去Read时，返回的是受到Limitedreader限制的内容了：即只读取了前面的4个字节：”some”。</p>
<p><strong>c) wrapper function chain</strong></p>
<p>由于wrapper function返回值类型与parameter类型相同，因此wrapper function可以组合一个chain，形式如下：</p>
<pre><code>形式：wrapperFunc(wrapperFunc(wrapperFunc(...)))
</code></pre>
<p>我们定义一个wrapper function：CapReader，用于将从reader读取的数据变为大写：</p>
<pre><code>  func CapReader(r io.Reader) io.Reader {
      return &amp;capitalizedReader{r: r}
  }

  type capitalizedReader struct {
      r io.Reader
  }

  func (r *capitalizedReader) Read(p []byte) (int, error) {
      n, err := r.r.Read(p)
      if err != nil {
          return 0, err
      }

      q := bytes.ToUpper(p)
      for i, v := range q {
          p[i] = v
      }
      return n, err
  }

</code></pre>
<p>将多个wrapper function串在一起：</p>
<pre><code>  s := strings.NewReader("some io.Reader stream to be read\n")
  r := io.TeeReader(CapReader(io.LimitReader(s, 4)), os.Stdout)
  b, _ := ioutil.ReadAll(r) //SOME
  fmt.Println(len(b))       //4
</code></pre>
<p>可以看到例子中，我们将TeeReader、CapReader、LimitedReader、strings Reader等组合到了一起，实现了读取前四个字节，将读取数据转换为大写并输出到标准输出的功能。</p>
<p>**d) adapter function type **</p>
<p>adapter function type是一个辅助水平组合实现的“工具”。adapter function type将一个普通function转换为自己的类型，同时辅助快速实现了某个“one-method” interface。 adapter function type的行为模式有些像电影中的“僵尸” &#8211; 咬别人一口就可以将别人转化为自己的同类。最著名的僵尸类型莫过于http.HandlerFunc了：</p>
<pre><code>  type Handler interface {
      ServeHTTP(ResponseWriter, *Request)
  }

  type HandlerFunc func(ResponseWriter, *Request)

  func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
      f(w, r)
  }

  // Usage: use HandlerFunc adapts index function to an implemenation type of Handler interface.
  func index(w http.ResponseWriter, r *http.Request) {
      fmt.Fprintf(w, "Welcome!")
  }

  http.ListenAndServe(":8080", http.HandlerFunc(index))
</code></pre>
<p>可以看到通过HandlerFunc，我们可以将普通function index快速转化为满足Handler interface的type。一旦转化ok，便可以通过interface进行组合了。</p>
<p>**e) middleware composition **</p>
<p>middleware这个词的含义可大可小，在Go Web编程中，常常指的是一个满足Handler interface的HandlerFunc类型实例。实质上：</p>
<pre><code>middleware =  wrapper function + adapter function type
</code></pre>
<p>我们可以看一个例子：</p>
<pre><code>  func logHandler(h http.Handler) http.Handler {
      return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
          t := time.Now()
          log.Printf("[%s] %q %v\n", r.Method, r.URL.String(), t)
          h.ServeHTTP(w, r)
      })
  }

  func authHandler(h http.Handler) http.Handler {
      return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
          err := validateAuth(r.URL.Query().Get("auth"))
          if err != nil {
              http.Error(w, "bad auth param", http.StatusUnauthorized)
              return
          }
          h.ServeHTTP(w, r)
      })
  }

  func main() {
      http.ListenAndServe(":8080", logHandler(authHandler(http.HandlerFunc(index))))
  }

</code></pre>
<p>wrapper function（如：logHandler、authHandler）内部利用 adapter function type转化了一个普通function，并返回实现了Handler interface的HandlerFunc类型实例。</p>
<h2>六、Preference in Concurrency</h2>
<p>Go语言的第三个价值观是偏好并发。如果说interface和组合决定了程序的静态结构组成的话，那么concurrency则影响着程序在执行阶段的动态运行结构。从某种意义上说，Go语言就是关于concurrency和interface的设计!</p>
<p>并发不是并行(paralell)，并发是关于程序结构的，而不是关于性能的。并发让并行更easy，并且通常性能更好;对于程序结构来说，concurrency是一个比interface组合更大的概念。并发是一种在程序执行层面上的组合：goroutines各自执行特定的工作，通过channels+select将goroutines连接起来。原生对并发的支持，让Go语言更适应现代计算环境。并发的存在鼓励程序员在程序设计时进行独立计算的分解。</p>
<p>在语言层面，Go提供了三种并发原语：</p>
<ul>
<li>
<p>goroutines提供<strong>并发执行</strong>, 它是Go runtime调度的基本单元；goroutine实现了异步编程模型的高效，但却允许你使用同步范式编写代码，降低心智负担；goroutines被动态地多路复用到系统核心线程上以保证所有goroutines的正常运行。</p>
</li>
<li>
<p>channels用于goroutines之间的<strong>通信和同步</strong>；channel是goroutines间建立联系的主要途径或者说goroutine通过channel耦合/组合在一起，这一点channel的功用有点像Unix pipe。</p>
</li>
<li>select可以让goroutine同时<strong>协调处理</strong>多个channel操作。</li>
</ul>
<h3>1、concurrency thought(并发思维)</h3>
<p>我将“偏好并发”价值观下的思维统称为“并发思维”。并发思维的核心依旧是组合！</p>
<p>采用并发思维进行程序动态结构设计，需要识别和分解出独立的计算单元放入Goroutines执行，并使用channel/select建立Goroutines之间的联系。计算单元的拆解是并发程序设计的重点，拆解没有统一规则，因业务域不同而异，例如：素数筛实现为每个素数建立一个goroutine以筛除素数的倍数。</p>
<p>不过我们可以从建立Goroutines间的“联系”的角度来看一些常见的goroutines间的“关系”模式。我们可以从“退出机制”和“通信联系”两大方面出发考虑。</p>
<h4>a) “detached” goroutine</h4>
<p>所谓分离的goroutine，即不需要关心它的退出，相当于与父goroutine间“无关系”。这类goroutines启动后与其创建者彻底分离(detached)，生命周期与程序生命周期相同。通常，这类goroutine在后台执行一些特定任务，如：monitor、watcher等。其实现通常采用for-select代码段形式；以timer或event驱动。</p>
<p>Go应用中内置的GC goroutine就是这种类型的：</p>
<pre><code>// runtime/mgc.go
  go gcBgMarkWorker(p) // each P has a background GC G.

  func gcBgMarkWorker(_p_ *p) {
      gp := getg()

      for {
        ... ...
      }
  }
</code></pre>
<h4>b） “parent-child” goroutine</h4>
<p>这类goroutine与detached goroutine正相反，parent需要通知并等待child退出。如果仅通知并等待一个child，我们可以这样做：</p>
<pre><code>parent:

  quit := make(chan string)
  go child(quit)

child:

  select {
    case c := &lt;-workCh:
    // do something
    case &lt;-quit:
    // do some cleanup
    quit&lt;-"done"
  }

parent:

  quit&lt;-"quit"
  &lt;-quit
</code></pre>
<p>当需要同时通知多个child goroutine quit时，我们可以通过channel close来实现：</p>
<pre><code>parent:

  quit := make(chan struct{})
  for ... {
    go child(quit) // several child goroutines
  }

child:

  select {
    case c := &lt;-workCh: // do something
    case &lt;-quit: // do some cleanup
    return
  }

parent:

  close(quit)
  time.Sleep(time.Second * 30)
</code></pre>
<p>如果parent要获得child的退出状态值，可以自定义quit channel中的元素类型：</p>
<pre><code>  type ExitStatus interface {
      Status() int
  }

  type IntStatus int // an adapter
  func (n IntStatus)Status() int {
    return int(n)
  }

  quit := make(chan ExitStatus) // for each child goroutine

child:

  quit &lt;- IntStatus(2017)

parent:

  s := &lt;-quit
  fmt.Println(s.Status()) //2017
</code></pre>
<h4>c) service handle</h4>
<p>一些goroutine在程序内部提供特定service，这些goroutine使用channel作为service handle，其他goroutine通过service handle与其通信。</p>
<p>比如：我们经常使用的time.After返回一个service handle：</p>
<pre><code>  //time/sleep.go
  func After(d Duration) &lt;-chan Time {
      return NewTimer(d).C
  }
</code></pre>
<p>对应的service goroutine就是runtime中的timer service goroutine：</p>
<pre><code>  // runtime/time.go
  func timerproc() {
    timers.gp = getg()
    for {
      ... ...
    }
  }
</code></pre>
<p>编写此类service goroutine时，需要考虑对于“慢消费者”service goroutine应该如何处置：阻塞还是丢弃。timer service goroutine使用的是buffered channel(size=1)，并在向channel发送消息时通过select做了一个判断。如果channel buffer满了，则丢弃这次timer事件。</p>
<p>如果我们要同时处理来自不同service goroutine的handle，那么可以使用service handles aggregation，见下面例子：</p>
<p>比如：我们从wechat、weibo、短信渠道获取msg：</p>
<pre><code>  type msg struct {
      content string
      source  string
  }

  func wechatReceiver() &lt;-chan *msg {
      c := make(chan *msg)
      go func() {
          c &lt;- &amp;msg{"wechat1", sourceWechat}
          c &lt;- &amp;msg{"wechat2", sourceWechat}
          c &lt;- &amp;msg{"wechat3", sourceWechat}
      }()

      return c
  }

  func weiboReceiver() &lt;-chan *msg {...}
  func textmessiageReceiver() &lt;-chan *msg {...}
</code></pre>
<p>我们需要把这些handle聚合起来统一处理，我们通过一个aggregation function来做。对于不固定数量handles的聚合，用goroutine来聚合(非常类似于unix pipe chain)：</p>
<pre><code>  func serviceAggregation(ins ...&lt;-chan *msg) &lt;-chan *msg {
      out := make(chan *msg)
      for _, c := range ins {
          go func(c &lt;-chan *msg) {
              for v := range c {
                  out &lt;- v
              }
          }(c)
      }
      return out
  }

  c := serviceAggregation(weiboReceiver(), wechatReceiver(), textmessageReceiver())
  m := &lt;-c // 获取message并处理
</code></pre>
<p>对于固定数量handles聚合，用select就可以实现：</p>
<pre><code>  func serviceAggregation(weibo, wechat, textmessage &lt;-chan *msg) &lt;-chan *msg {
      out := make(chan *msg)

      go func(out chan&lt;- *msg) {
          for {
              select {
              case m := &lt;-weibo:
                  out &lt;- m
              case m := &lt;-wechat:
                  out &lt;- m
              case m := &lt;-textmessage:
                  out &lt;- m
              }
          }
      }(out)
      return out
  }
  c := serviceAggregation(weiboReceiver(), wechatReceiver(), textmessageReceiver())
  m := &lt;-c // 获取message并处理
</code></pre>
<h4>d) dispatch-and-mix goroutines</h4>
<p>在“微服务”时代，我们在处理一个请求时经常调用多个外部微服务并综合处理返回结果：</p>
<pre><code>  func handleRequestClassic() {
    r1 := invokeService1()
    //handle result1
    r2 := invokeService2()
    //handle result2
    r3 := invokeService3()
    //handle result3
  }
</code></pre>
<p>上述例子中的问题是显而易见的：顺序调用、慢、一旦某个service出现异常，返回时间不可预知，可能会导致调用阻塞。</p>
<p>一个优化的方法就是讲将处理请求时对外部的服务调用分发到goroutine中，再汇总返回结果。并且通过设置一个总体超时时间，让调用返回的时间可预知。：</p>
<pre><code>  func handleRequestByDAM() {
    c1, c2, c3 := make(chan Result1), make(chan Result2), make(chan Result3)
    go func() { c1 &lt;- invokeService1() } ()
    go func() { c2 &lt;- invokeService2() } ()
    go func() { c3 &lt;- invokeService3() } ()
    timeout := time.After(200 * time.Millisecond)
    for i := 0; i &lt; 3; i++ {
        select {
        case r := &lt;-c1: //handle result1
            c1 = nil
        case r := &lt;-c2: //handle result2
            c2 = nil
        case r := &lt;-c3: //handle result3
            c3 = nil
        case &lt;-timeout:
            fmt.Println("timed out")
            return
        }
    }
    return
  }
</code></pre>
<p>不过这次优化后的程序依旧存在一个问题，那就是一旦timeout，调用返回，但一些在途的请求资源可能没有回收，request无法显式撤回，久而久之，可能导致资源的泄露。于是我们做进一步改进：通过Context显式cancel掉已经向外发起的在途请求，释放占用资源:</p>
<pre><code>  type service func() result
  func invokeService(ctx context.Context, s service) chan result {
    c := make(chan result)
    go func() {
      c1 := make(chan result)
      go func() {
        c1 &lt;-s()
      }
      select {
        case v := &lt;-c1:
             c &lt;-v
        case &lt;-ctx.Done():
        // cancel this in-flight request by closing its connection.
      }
    }()
    return c
  }

  func handleRequestByDAM() {
    ctx, cf := context.WithCancel(context.Background())
    c1, c2, c3 := invokeService(ctx, service1), invokeService(ctx, service2),
                   invokeService(ctx, service3)
    timeout := time.After(200 * time.Millisecond)
    for i := 0; i &lt; 3; i++ {
        select {
        case r := &lt;-c1: //handle result1
        case r := &lt;-c2: //handle result2
        case r := &lt;-c3: //handle result3
        case &lt;-timeout:
            cf() // cancel all service invoke requests
            return
        }
    }
    return
  }
</code></pre>
<p>优化后的程序的优点：并发、快、返回结果可预知。</p>
<h2>七、总结</h2>
<p>通过这篇文章，我总结了主导Go语言编程思维的三个价值观：</p>
<ul>
<li>Overall Simplicity</li>
<li>Orthogonal Composition</li>
<li>Preference in Concurrency</li>
</ul>
<p>阐述了每种价值观主导下的编程思维，并给出了每种编程思维在语言设计、语言应用方面的一些模式和实际例子。</p>
<p>Go最初的设计初衷还有一点，那就是将编程时的fun重新带给Gopher们。但个人觉得只有当你使用Go编程思维去写Go code时，你才能体会到Go设计者的用意，才能让你没有别扭的赶脚，发现自己走在正确的way上，才能真正感到go coding时的fun。</p>
<p>另外，虽然总结出的三个价值观数量不多，但如果能在实际运用中认真践行，却能迸发巨大能量。它会让你应对各种复杂情况、代码设计变得游刃有余、顺利解决各种业务问题。</p>
<p>最后再说说Go 2.0。回到前面的 “编程语言思维的形成”模型，行为总是对结构有反馈的，这将导致结构的持续改变和优化。Go team将于今年8月份发布Go1.9版本，这是一个关键的时间节点。恰好今年的denver的<a href="http://www.gophercon.com/">Gophercon大会</a>上，<a href="https://github.com/rsc">Russ Cox</a>将做”the future of go”的演讲，后续是继续1.10还是Go 2.0，让我们拭目以待！不过个人觉得，无论对语言结构改动的需求有多大，Go的价值观都是不会发生改变的。</p>
<p>本文的slide文件可以在<a href="https://github.com/bigwhite/talks/blob/master/gopherchina/2017/go-coding-in-go-way-cn.slide">这里</a>下载。</p>
<hr />
<p>微博：<a href="http://weibo.com/bigwhite20xx">@tonybai_cn</a><br />
微信公众号：iamtonybai<br />
github.com: https://github.com/bigwhite</p>
<p style='text-align:left'>&copy; 2017, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2017/04/20/go-coding-in-go-way/feed/</wfw:commentRss>
		<slash:comments>24</slash:comments>
		</item>
		<item>
		<title>GopherChina讲师专访</title>
		<link>https://tonybai.com/2017/04/06/an-interview-with-me-as-a-lecturer-of-gopherchina-2017/</link>
		<comments>https://tonybai.com/2017/04/06/an-interview-with-me-as-a-lecturer-of-gopherchina-2017/#comments</comments>
		<pubDate>Thu, 06 Apr 2017 03:34:16 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Concurrency]]></category>
		<category><![CDATA[Cpp]]></category>
		<category><![CDATA[GC]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[Gopher]]></category>
		<category><![CDATA[gopherchina]]></category>
		<category><![CDATA[Haskell]]></category>
		<category><![CDATA[Interface]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[Ruby]]></category>
		<category><![CDATA[speaker]]></category>
		<category><![CDATA[函数式语言]]></category>
		<category><![CDATA[编程语言]]></category>
		<category><![CDATA[讲师]]></category>

		<guid isPermaLink="false">http://tonybai.com/?p=2284</guid>
		<description><![CDATA[今年有幸收到GopherChina大会的组织者、beego开源项目的owner、《Go Web编程》的作者谢孟军童鞋的邀请，以讲师身份参加今年的GopherChina大会。下面是GopherChina对我这个讲师的专访稿^0^。该专访稿将同时被发布在公众号“Go中国(微信号：golangchina)”上面，可点击这里阅读。 1、首先介绍一下自己。 大家好！我叫白明（Tony Bai），目前是东软云科技的一名架构师，专职于服务端开发，日常工作主要使用Go语言。我算是国内较早接触Go语言的程序员兼Advocater了，平时在我的博客、微博和微信公众号“iamtonybai”上经常发表一些关于Go语言的文章和Go生态圈内的信息。 在接触Go之前，我主要使用C语言开发电信领域的一些后端服务系统，拥有多年的电信领域产品研发和技术管理经验。我个人比较喜换钻研和分享技术，是《七周七语言》一书的译者之一，并且坚持写技术博客十余年。同时我也算是一个开源爱好者，也在github上分享过自己开发的几个小工具。 目前的主要研究领域包括：Go、Kubernetes、Docker和儿童编程教育等。 2、回忆一下与Golang的渊源。是什么原因决定尝试Golang？自己用Go语言实现的第一个项目是什么？当时 Golang 有什么令人惊喜的表现，又有什么样的小不足，这个不足在Golang已经更新到1.8版本的时候是否已经得到改善？ 众所周知，Go语言最初由Robert Griesemer, Ken Thompson和Rob Pike在2007年末共同设计和实现，2009年11月份正式发布并开源，并于2012年3月份发布了1.0版本以及Go1规范。我就是在2012年开始接触Go的，那是缘于看到一份由Rob Pike主讲的3-day Go Course资料。从那份资料里，我了解到了Go的设计理念和Go语法。 由于之前浸淫于C语言多年，深知C语言在系统编程以及服务端编程方面的强大，同时也亲身体会到C的语法“陷阱”和C手工内存管理给开发者带来的苦恼。虽然那些年市面上也有其他主流语言可供选择，但在我看来，它们给我带来的心智负担太过沉重，比如：C++“宇宙无敌”的学习和使用复杂性、Java超大的资源消耗和庞大且纷繁芜杂的框架体系、动态语言（ruby、python）无静态类型而导致运行时crash时调试的困难、函数式语言（如Haskell、clisp）的过于小众和非主流。显然它们都不是我的菜。直到Go的出现，C程序员出身的我一下子就被这门新语言迷住了。 现在想起这件事来，我当时迷上Go应该主要由于以下几点原因： * 静态类型语言、接近于C的性能(对于C程序员来说，这算是某种天然继承性) * 简洁的语法 * 内置的并发支持 * GC * 贯穿整个语言的正交设计和组合编程思路（兼容对OO的支持） * 工具和功能全面的标准库 而且这几点也是这几年持续支撑我深入学习和使用Go语言的原因。 不过由于Go1刚出来时也十分小众，并且各方面功能还在完善中，我并没有在真实项目中使用Go，这种状况一直持续到2014年末。直到那时，我才在一个小项目中使用Go实现了一个微信公众号的协议接口。当时发现：使用Go实现一些安全协议真是非常方便，因为标准库里内置了很好的支持，比如：各种aes、sha256、tls算法实现。同时，Go内置的testing framework、gofmt、Go pprof工具的表现也是让我感觉用起来十分舒服。 如果非要说当时有什么不足之处的话，那只能是Go对debugging的支持明显不足。即便是到了目前最新的Go 1.8版本，Go在debugging方面虽然有所改善，但和C这样的传统语言来说依然有很大差距。不过好在我们有“print”这个无敌调试武器，Go的这个不足对我影响微乎甚微^0^。 当然随着Go在更多规模稍大项目的使用，Go的包管理问题逐渐浮出水面，这也是整个Go社区都想改进的事情。好在目前已经有了专门的Commitee来做这件事，最新的roadmap显示dep工具将在Go 1.10 dev cycle并入Go tools中。 3、2009年诞生至今，Go语言基本统治了云计算，作为最专业的Go语言专家，您认为这是由于它的哪些优雅的特性？Golang未来还会有什么样的改进和突破？ “作为最专业的Go语言专家”，这一称号的确不敢当。我觉得我个人只是国内Gopher普通一员，能为Go语言在国内的发展做点事情就很高兴了^0^。 Go自从1.5版本自举后，随着ssa优化、GC延迟优化的深入，Go在国内外的使用趋势确实是一片大好，尤其是Go问鼎2016年TIOBE编程语言排行榜的年度语言，让更多的程序员知道Go语言、了解Go语言和使用Go语言。在云计算成为当今IT行业常态的今天，Go在这方面已然成为一个重量级选手。从个人对Go的情感角度出发，我个人是希望Go语言能成为”21世纪的C语言”和云平台第一语言的。不过这是一个过程，需要时间，还需要依靠全世界Gopher和Go Community的共同努力才能实现的。 时代不同，语言的成长环境也有所不同。和上一代和两代的语言似乎有所不同，新一代编程语言是否能进入程序员们的法眼，是否值得程序员去投资，“背景”很重要，即所谓的编程语言也进入了“拼爹”时代。Go语言背靠Google这棵大树，又有Robert Griesemer, Ken Thompson, Rob Pike三巨头坐镇，是真正的“牛二代”，它自然就会得到不少程序员的青睐。我想这是Go吸引眼球的场外因素。 至于Go本身的语言特性，在上一个问题中，我已经做了初步阐述了，这里再补充几点： * [...]]]></description>
			<content:encoded><![CDATA[<p>今年有幸收到<a href="http://www.gopherchina.org/">GopherChina大会</a>的组织者、<a href="https://beego.me/">beego开源项目</a>的owner、《<a href="https://book.douban.com/subject/24316255/">Go Web编程</a>》的作者<a href="http://weibo.com/533452688">谢孟军</a>童鞋的邀请，以讲师身份参加今年的GopherChina大会。下面是GopherChina对我这个讲师的专访稿^0^。该专访稿将同时被发布在公众号“Go中国(微信号：golangchina)”上面，可点击<a href="https://mp.weixin.qq.com/s?__biz=MjM5OTcxMzE0MQ==&amp;mid=2653369932&amp;idx=1&amp;sn=2b89c8253c759714db5dc4fccb96c6e6&amp;chksm=bce4d6568b935f4068e2456429ca37a257ff24237533b718fc64cd445be98452757693ada141&amp;mpshare=1&amp;scene=1&amp;srcid=0405G8XuCfBVfkPTz2skitFO&amp;key=880a8f11cadd9c38c88dbfeaa37bee078b8460ae535cbcb5940e7e73f779bfb674d9e70c633715041ee796cba46e4386a598cab68296084cf1d1965ad31b7ff5c442b390931511a054b34f7dff127979&amp;ascene=0&amp;uin=MTYwMzM0NjYyMQ%3D%3D&amp;devicetype=iMac+MacBookAir6%2C2+OSX+OSX+10.9.2+build(13C64)&amp;version=11020201&amp;pass_ticket=ubemadJo5Ju2NkXnKepVV1ToSJYfkOGXgXuETKrjwLLow4B4h2Ufk0enGSRNk9cn">这里</a>阅读。</p>
<h4>1、首先介绍一下自己。</h4>
<p>大家好！我叫白明（Tony Bai），目前是东软云科技的一名架构师，专职于服务端开发，日常工作主要使用Go语言。我算是国内较早接触Go语言的程序员兼Advocater了，平时在我的<a href="http://tonybai.com">博客</a>、<a href="http://weibo.com/bigwhite20xx/">微博</a>和微信公众号“iamtonybai”上经常发表一些关于Go语言的文章和Go生态圈内的信息。</p>
<p>在接触Go之前，我主要使用<a href="http://tonybai.com/tag/c">C语言</a>开发电信领域的一些后端服务系统，拥有多年的电信领域产品研发和技术管理经验。我个人比较喜换钻研和分享技术，是《<a href="http://tonybai.com/2012/05/08/translate-seven-languages-in-seven-weeks/">七周七语言</a>》一书的译者之一，并且坚持写<a href="http://tonybai.com/">技术博客</a>十余年。同时我也算是一个开源爱好者，也在<a href="https://github.com/bigwhite">github</a>上分享过自己开发的几个小工具。</p>
<p>目前的主要研究领域包括：<a href="http://tonybai.com/tag/go">Go</a>、<a href="http://tonybai.com/tag/kubernetes">Kubernetes</a>、<a href="http://tonybai.com/tag/docker">Docker</a>和儿童编程教育等。</p>
<h4>2、回忆一下与Golang的渊源。是什么原因决定尝试Golang？自己用Go语言实现的第一个项目是什么？当时 Golang 有什么令人惊喜的表现，又有什么样的小不足，这个不足在Golang已经更新到1.8版本的时候是否已经得到改善？</h4>
<p>众所周知，Go语言最初由<a href="https://github.com/griesemer">Robert Griesemer</a>, <a href="https://en.wikipedia.org/wiki/Ken_Thompson">Ken Thompson</a>和<a href="https://github.com/robpike">Rob Pike</a>在2007年末共同设计和实现，2009年11月份正式发布并开源，并于2012年3月份<a href="https://blog.golang.org/go-version-1-is-released">发布了1.0版本</a>以及<a href="https://golang.org/ref/spec">Go1规范</a>。我就是在2012年开始接触Go的，那是缘于看到一份由Rob Pike主讲的3-day <a href="http://tonybai.com/2012/08/23/the-go-programming-language-tutorial-part1/">Go Course资料</a>。从那份资料里，我了解到了Go的设计理念和Go语法。</p>
<p>由于之前浸淫于C语言多年，深知C语言在系统编程以及服务端编程方面的强大，同时也亲身体会到C的语法“陷阱”和C手工内存管理给开发者带来的苦恼。虽然那些年市面上也有其他主流语言可供选择，但在我看来，它们给我带来的心智负担太过沉重，比如：<a href="http://tonybai.com/2004/11/09/cpp-advanced-training-part1">C++</a>“宇宙无敌”的学习和使用复杂性、<a href="http://tonybai.com/tag/java">Java</a>超大的资源消耗和庞大且纷繁芜杂的框架体系、动态语言（<a href="http://tonybai.com/2005/01/05/learn-ruby">ruby</a>、<a href="http://tonybai.com/tag/python">python</a>）无静态类型而导致运行时crash时调试的困难、函数式语言（如<a href="http://tonybai.com/tag/haskell">Haskell</a>、<a href="tonybai.com/2011/06/21/hello-common-lisp">clisp</a>）的过于小众和非主流。显然它们都不是我的菜。直到Go的出现，C程序员出身的我一下子就被这门新语言迷住了。</p>
<p>现在想起这件事来，我当时迷上Go应该主要由于以下几点原因：</p>
<pre><code>* 静态类型语言、接近于C的性能(对于C程序员来说，这算是某种天然继承性)
* 简洁的语法
* 内置的并发支持
* GC
* 贯穿整个语言的正交设计和组合编程思路（兼容对OO的支持）
* 工具和功能全面的标准库
</code></pre>
<p>而且这几点也是这几年持续支撑我深入学习和使用Go语言的原因。</p>
<p>不过由于Go1刚出来时也十分小众，并且各方面功能还在完善中，我并没有在真实项目中使用Go，这种状况一直持续到2014年末。直到那时，我才在一个小项目中使用Go实现了一个微信公众号的协议接口。当时发现：使用Go实现一些安全协议真是非常方便，因为标准库里内置了很好的支持，比如：各种aes、sha256、tls算法实现。同时，Go内置的testing framework、gofmt、Go pprof工具的表现也是让我感觉用起来十分舒服。</p>
<p>如果非要说当时有什么不足之处的话，那只能是Go对debugging的支持明显不足。即便是到了目前最新的<a href="http://tonybai.com/2017/02/03/some-changes-in-go-1-8/">Go 1.8版本</a>，Go在debugging方面虽然有所改善，但和C这样的传统语言来说依然有很大差距。不过好在我们有“print”这个无敌调试武器，Go的这个不足对我影响微乎甚微^0^。</p>
<p>当然随着Go在更多规模稍大项目的使用，Go的包管理问题逐渐浮出水面，这也是整个Go社区都想改进的事情。好在目前已经有了专门的Commitee来做这件事，最新的<a href="https://github.com/golang/dep/wiki/Roadmap">roadmap</a>显示<a href="https://github.com/golang/dep">dep工具</a>将在Go 1.10 dev cycle并入Go tools中。</p>
<h4>3、2009年诞生至今，Go语言基本统治了云计算，作为最专业的Go语言专家，您认为这是由于它的哪些优雅的特性？Golang未来还会有什么样的改进和突破？</h4>
<p>“作为最专业的Go语言专家”，这一称号的确不敢当。我觉得我个人只是国内Gopher普通一员，能为Go语言在国内的发展做点事情就很高兴了^0^。</p>
<p>Go自从<a href="http://tonybai.com/2015/07/10/some-changes-in-go-1-5/">1.5版本自举</a>后，随着ssa优化、GC延迟优化的深入，Go在国内外的使用趋势确实是一片大好，尤其是Go问鼎2016年TIOBE编程语言排行榜的年度语言，让更多的程序员知道Go语言、了解Go语言和使用Go语言。在云计算成为当今IT行业常态的今天，Go在这方面已然成为一个重量级选手。从个人对Go的情感角度出发，我个人是希望Go语言能成为”21世纪的C语言”和云平台第一语言的。不过这是一个过程，需要时间，还需要依靠全世界Gopher和Go Community的共同努力才能实现的。</p>
<p>时代不同，语言的成长环境也有所不同。和上一代和两代的语言似乎有所不同，新一代编程语言是否能进入程序员们的法眼，是否值得程序员去投资，“背景”很重要，即所谓的<a href="http://tonybai.com/2012/10/08/the-new-age-of-programming-language/">编程语言也进入了“拼爹”时代</a>。Go语言背靠Google这棵大树，又有Robert Griesemer, Ken Thompson, Rob Pike三巨头坐镇，是真正的“牛二代”，它自然就会得到不少程序员的青睐。我想这是Go吸引眼球的场外因素。</p>
<p>至于Go本身的语言特性，在上一个问题中，我已经做了初步阐述了，这里再补充几点：</p>
<pre><code>* Go是一门以解决Google内部生产环境中的问题（大规模并发服务）为目标的、兼顾在语言设计层面解决一些软件工程问题的面向大规模并发服务的编程语言；
* 开发效率较高(对比主流的C、C++和Java)，且执行效率与C相比，没有数量级级别的差异；
* 编译速度超快（相对其他需编译的主流语言），无需喝咖啡等待；
* Go1兼容性的承诺。
</code></pre>
<p>Go语言到目前已经演进到1.8版本，Go 1.9开发周期已经打开。今年夏天，Go 1.9发布后，Go似乎就到了版本演进的关键节点，是继续Go1兼容（Go 1.10、Go 1.11&#8230;），还是诞生Go2规范，目前并没有明确信息。不过未来的改进和突破，我觉得还是应该建立在Go语言设计的初衷和设计原则之上，这些初衷和原则包括：</p>
<pre><code>目标：
 * 高效的静态编译语言
 * 动态语言的易用性
 * 类型安全和内存安全
 * 对并发和通信的良好支持
 * 高效、低/趋于零延迟的GC
 * 高速编译

原则：

 * 保持概念正交
 * 保持语法简单
 * 保持类型系统精炼，无type hierarchy

</code></pre>
<p>从这些年Go的发展来看，基本都是遵循以上目标和原则的。即便Go2出来，不符合上述原则的feature，也是很难加入到Go2里面的。</p>
<h4>4、之前是否有关注到Gopher China大会，对大会的风格和内容有什么样的印象？</h4>
<p>对于中国大陆地区规模最大，最具影响力的Go大会，我是从第一届就开始关注了，虽然第一届因故没能参加^0^。在去年举行的第二届大会，我是作为早鸟观众参与的哦。而本届则有幸成为讲师。</p>
<p>GopherChina从诞生至今，规模日益扩大，据说今年的参会人员可能突破1000人。而且GopherChina大会从第一届就汇聚了国内一线IT厂商的精英技术人员作为讲师，并得到了Go core team的大力支持。在每一届大会都会邀请到Go team中的核心开发人员参会布道，甚至在第一届大会时还邀请到了Go三巨头之一的Robert Griesemer，极大满足了国内Gopher的求知欲。</p>
<p>而且就我观察，每一届GopherChina大会的主题都涵盖：语言、工程、新兴领域应用等多个环节，颇具多样性和全面性。</p>
<h4>5、作为讲师也是参会者，对于今年的Gopher China大会的哪些议题有所期待？</h4>
<p>GopherChina每一届都是高手云集，这届也不例外。今年大会的每个议题都令我很是期待。</p>
<h4>6、现在很多企业项目都在准备转Go，对于这些项目的负责人有没有建议和经验分享？</h4>
<p>Go语言以极易上手著称，同时Go也是一门十分简单的语言（相对于其他主流语言），C、Cpp、Java、Python等程序员转型到Go的曲线并不陡峭，因此团队整体转型为Go的门槛并不高。但还是要有几点是项目负责人需要认真考虑的：</p>
<h5>(1) 确认Go适合项目的应用场景</h5>
<p>Go不是万能的，不能为了用Go而去用Go。但Go从最初定位为一门系统语言(Sytem Programming Language)逐渐演化成为一门通用语言(General Purpose Programming Language)，说明其适应性和应用范围已经十分广泛，目前在云计算、Web开发、大数据、游戏、数据库、IDE、容器等领域均有大规模应用案例。但即便这样，仍然在有些领域的应用需要谨慎，比如嵌入式领域、比如mobile开发，虽然在这两个方面Go都做出了很大的努力，但似乎并没有较大的突破。</p>
<h5>(2) 以终为始，从开始就参考Go的最佳实践</h5>
<p>Go经过若干年的演化发展，逐渐形成了一些最佳实践，包括：项目代码组织、命名、惯用法、测试方法、错误处理、接口使用等。建议多看官方的talks、blog和世界范围内Go大会的presentation video。</p>
<h5>(3) 单元测试全程保障</h5>
<p>Go内置了单元测试框架，而单元测试是检验代码设计好坏的基础，也是代码重构的先决条件。建议项目从始至终都要优先考虑对代码编写测试代码。</p>
<h5>(4) 充分利用标准库</h5>
<p>在Go的应用实践中，你会发现Go标准库已经为你提供了大部分你要使用的功能。甚至有一些极端的Go纯粹主义者只愿意标准库中的函数和方法。Go标准库凝聚了Go team以及相关Contributor的Go代码精华，其稳定性绝对值得信赖。充分和广泛利用标准库也便于项目代码组织、构建和迁移。</p>
<h5>(5) 基于go tool建立代码metric视图</h5>
<p>对于那些性能敏感的系统，建议在内部环境基于go tool建立起代码的metric视图，监控代码变化给系统性能等带来的影响，利于问题诊断。</p>
<p><strong><em>最后，请及时反馈Go语言自身问题，你的反馈是Go语言演化的动力</em></strong>。</p>
<h4>7、有没有你觉得很酷的Gopher？可以回答自己哟～</h4>
<p>在github.com/golang/go上，我经常关注<a href="https://github.com/rsc">Russ Cox</a>的代码。众所周知，<a href="https://swtch.com/~rsc/">Russ Cox</a>是Go核心代码提交次数最多的member，他也除三巨头之外，对Go演化影响着最大的人之一。从近两年的Go team开发活动来看，Russ Cox开发效率很高，并且提出的<a href="https://github.com/golang/proposal/blob/master/design/12914-monotonic.md">proposal</a>思维之缜密和全面令人叹服。</p>
<p><a href="https://dave.cheney.net/">Dave Cheney</a>是另一个我经常关注的Gopher，他也是<a href="http://tonybai.com/2016/04/18/my-experience-of-gopherchina2016/">第二届GopherChina大会</a>的受邀讲师。他不遗余力的“鼓吹”Go，并从Go 1.6版本开始，发起了<a href="https://github.com/golang/go/wiki/Go-1.8-Release-Party">Go Global release party</a> ，成为Go Community又一个节日。他不仅是Go community中的意见领袖，同时也为Go社区贡献不少有用的工具和思想，包括：<a href="https://getgb.io">gb</a>、<a href="https://github.com/pkg/errors">errors</a>等。</p>
<p>Dmitry Vyukov，前Intel Black Belt级工程师，现Google员工，虽然他不是专职Go team的人，但他却是Go scheduler当前版本的核心实现者。虽然近两年似乎在golang的投入并不是那么多，但依然成果丰硕，<a href="https://talks.golang.org/2015/dynamic-tools.slide">Go Execution Tracer</a>、<a href="https://github.com/dvyukov/go-fuzz">go-fuzz</a>(据说要加入go核心)都是他的杰作。</p>
<hr />
<p>微博：<a href="http://weibo.com/bigwhite20xx">@tonybai_cn</a><br />
微信公众号：iamtonybai<br />
github.com: https://github.com/bigwhite</p>
<p style='text-align:left'>&copy; 2017, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2017/04/06/an-interview-with-me-as-a-lecturer-of-gopherchina-2017/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>关于编程语言学习的一些体会</title>
		<link>https://tonybai.com/2013/10/22/some-experience-about-learning-programming-language/</link>
		<comments>https://tonybai.com/2013/10/22/some-experience-about-learning-programming-language/#comments</comments>
		<pubDate>Tue, 22 Oct 2013 15:47:33 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Blogger]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[Common-Lisp]]></category>
		<category><![CDATA[Cpp]]></category>
		<category><![CDATA[Erlang]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[Google]]></category>
		<category><![CDATA[Haskell]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[Kernel]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[monad]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[Prolog]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[Ruby]]></category>
		<category><![CDATA[Scala]]></category>
		<category><![CDATA[七龙珠]]></category>
		<category><![CDATA[博客]]></category>
		<category><![CDATA[学习]]></category>
		<category><![CDATA[工作]]></category>
		<category><![CDATA[开源]]></category>
		<category><![CDATA[思考]]></category>
		<category><![CDATA[感悟]]></category>
		<category><![CDATA[程序员]]></category>
		<category><![CDATA[范型]]></category>

		<guid isPermaLink="false">http://tonybai.com/?p=1423</guid>
		<description><![CDATA[Learn at least one new language every year. &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; &#8212; Andy Hunt and Dave Thomas 自己一直是&#8220;每年学习一门新语言&#8221;的忠实拥趸，曾先后认真地学习了Haskell、Common Lisp、Python、Go等语言，对Prolog、Scala、Erlang、Lua、PHP也有一定了解。但几年下来，只有Python一门语言算 是真正被留在我的大脑里，用在了工作中。其他那几门语言留下来的只是一些思想了。这似乎符合了Andy Hunt和Dave Thomas在《程序员修炼之道》中对于这一实践目的的阐述：&#8220;学会用多种方式解决问题，扩展我们的视野，避免思路僵化和停滞不前&#8221;^_^。 即便是残存的思想，其实也并不深刻。要真正会运用新思维并非那么简单。一门编程语言从入门到精通，至少要经历学语法、做实践、用idioms（写出地道的代码）三个阶段。这让我深刻的感悟到：不以使用为目的的语言学习，都是在浪费生命！ 有精力多学习些语言自然很好，我迫切期待能拥有一个像&#8220;七龙珠&#8221;中孙悟空那样的&#8220;精神时光屋&#8221;呢。但现实中，人的精力是有限的，而我们要面对的计算机科学领域中的知识、技能以及问题却似乎是无限的。因此在&#8220;每年至少学习一门新语言&#8221;这一实践上，建议不要过于教条。 从编程语言自身来看，范型(Paradigm)是影响语言思维差异的主要因素，而编程语言的范型有限，主流的也就那么几种：命令式（过程式）、函数式、逻 辑式、面向对象等。每种范型的背后都有几种、十几种甚至几十种语言，我们其实没有必要都去学。从拓展视野的角度去说，从每种主流范式中找到一两门典型的语 言去学习就可以了。比如命令式的，我们可以选择C；函数式我们选择Haskell；逻辑式的选择Prolog；面向对象的选择Java等。 即便是从每个范型中挑出一门，你要付出的精力依旧不少，我们还要考虑其实用性：要以使用为目的。如果能将其用在工作中，天天与你相伴，被他人接受，自然最 好；退而求其次，你能找到一两个开源项目，并参与其中也是可以的，至少可以让你保持手热；如果这两点都无法做到，仅仅是凭借个人的热情与坚持，那是不会持 久的，若干时间后，你就会对其生疏，可能连基本的&#34;Hello World&#34;语法都记不得了。不过这个年头，思想也不能不要。在有剩余精力的前提下，挑选些牛人们极力&#8220;鼓吹&#8221;的语言，吸收一下其思想精华，说不定哪天就 能用得上，让自己和大家都感觉你很NB，抬高一下自己的身价^_^。记住：编程语言也是要拼爹的，系出名门的语言(诸如Go、Dart等)自然得到更多的青睐、使用和推广，出位的几率也就高出许多，尤其是在目前新编程语言百花齐放的阶段。因此在选择有思想的新语言时，最好在这些名门之后中做优选。 这个时代喜欢&#8220;专家&#8221;，因此我们在一两门语言上务必要做到&#8220;精专&#8221;，这是会给你带来黄油和面包的语言。要专到什么程度呢？我有一个同事，什么问题都用C解决。他甚至为此写了个不小的基础框架，所有业务问题的Code放在框架中被回调即可，即便是这个问题用Python实现只需几行代码。 计算机科学的研究核心是什么？我想肯定不是编程语言，就好比社会科学研究的核心不是人类语言一样。我比较欣赏这样的观点：作为程序员而言，最重要的是去创造，而不是研究。我们应更多的利用已经掌握的语言解决现实中的问题。做 编程语言研究的人可能要了解各种语言的特点与实现方式，但对于大多数的程序员来说，其实我们只需要关注问题域：做底层平台开发的，关注机器模型、通信原理 以及OS原理和实现细节；做算法的，很荣幸，那才是正统的程序设计的核心；前端攻城师则更多关注用户的体验。而在这些解决实际问题的过程中，我们更多采用 的是&#8220;制式&#8221;的编程语言。即做平台开发的，一般用C，C++等系统编程语言，更多的考虑的是性能；做前端开发的，PHP/JavaScript不可或缺。 我们要考虑的是如何利用这些制式的编程语言去解决问题，而在这些制式语言上，我们要做到精通。 从新兴语言中借鉴新思想，然后在旧语言中实现新语言的特性，其实更多是在旧语言中实现了某 种语法糖，你爱吃，不代表其他人也理解也爱吃，还容易被人误认为是&#8220;炫技&#8221;。如果你是技术负责人，且经过评估，新语言十分适合这个问题域，那莫不入直接引 入这门语言，让大家都能使用到这门语言的新思想、新特性。 辩证的说，任何一种编程语言都有其利与弊，比如Haskell，纯函数式语言，变量不能改变，无状态，对并行处理具有天然的适应性，但在处理基本IO时却要编写难于理解的monad；而在命令式语言中，这种IO处理简直简单的不得了。 关于函数式语言，个人感觉未来若干年内仍难以大行其道，建议还是跟上命令式语言的演化主线吧。 跨越问题域学习语言，通常收获不大。一个做平台服务端，用惯了C的资深程序员，让他去学PHP写前端代码，估计是无法迸发出任何火花的。 以上是自己这些年关于编程语言学习的一些体会，比较零散，但希望能有帮助。 &#169; 2013, bigwhite. 版权所有.]]></description>
			<content:encoded><![CDATA[<p><span class="st"><i>Learn at least one <em>new language every year</em>.<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8212; Andy Hunt and Dave Thomas</i></span></p>
<p><span style="line-height: 1.6em;">自己一直是&ldquo;每年学习一门新语言&rdquo;的忠实拥趸，曾先后认真地学习了<a href="http://tonybai.com/2010/11/14/the-chinese-translation-project-for-programming-in-haskell/">Haskell</a>、<a href="http://tonybai.com/2011/06/21/hello-common-lisp/">Common Lisp</a>、<a href="http://tonybai.com/2013/07/09/an-implementation-of-python-commandline-variables/">Python</a>、<a href="http://tonybai.com/2012/08/17/hello-go/">Go</a>等语言，对<a href="http://tonybai.com/2012/05/08/translate-seven-languages-in-seven-weeks/">Prolog</a>、<a href="http://www.scala-lang.org">Scala</a>、<a href="http://www.erlang.org">Erlang</a>、<a href="http://www.lua.org">Lua</a>、<a href="http://php.net">PHP</a>也有一定了解。但几年下来，只有<a href="http://www.python.org">Python</a>一门语言算 是真正被留在我的大脑里，用在了工作中。其他那几门语言留下来的只是一些思想了。这似乎符合了Andy Hunt和Dave Thomas在《<a href="http://www.douban.com/subject/1152111">程序员修炼之道</a>》中对于这一实践目的的阐述：&ldquo;学会用多种方式解决问题，扩展我们的视野，避免思路僵化和停滞不前&rdquo;^_^。</span></p>
<p>即便是残存的思想，其实也并不深刻。要真正会运用新思维并非那么简单。一门编程语言从入门到精通，至少要经历学语法、做实践、用idioms（写出地道的代码）三个阶段。这让我深刻的感悟到：<b>不以使用为目的的语言学习，都是在浪费生命</b>！</p>
<p>有精力多学习些语言自然很好，我迫切期待能拥有一个像&ldquo;<a href="http://book.douban.com/subject/2042982/">七龙珠</a>&rdquo;中孙悟空那样的&ldquo;精神时光屋&rdquo;呢。但现实中，人的精力是有限的，而我们要面对的计算机科学领域中的知识、技能以及问题却似乎是无限的。因此在&ldquo;每年至少学习一门新语言&rdquo;这一实践上，建议<b>不要过于教条</b>。 从编程语言自身来看，范型(Paradigm)是影响语言思维差异的主要因素，而编程语言的范型有限，主流的也就那么几种：命令式（过程式）、函数式、逻 辑式、面向对象等。每种范型的背后都有几种、十几种甚至几十种语言，我们其实没有必要都去学。从拓展视野的角度去说，从每种主流范式中找到一两门典型的语 言去学习就可以了。比如命令式的，我们可以选择C；函数式我们选择Haskell；逻辑式的选择Prolog；面向对象的选择Java等。</p>
<p>即便是从每个范型中挑出一门，你要付出的精力依旧不少，我们还要考虑其实用性：要以使用为目的。如果能将其用在工作中，天天与你相伴，被他人接受，自然最 好；退而求其次，你能找到一两个开源项目，并参与其中也是可以的，至少可以让你保持手热；如果这两点都无法做到，仅仅是凭借个人的热情与坚持，那是不会持 久的，若干时间后，你就会对其生疏，可能连基本的&quot;Hello World&quot;语法都记不得了。不过这个年头，思想也不能不要。在有剩余精力的前提下，挑选些牛人们极力&ldquo;鼓吹&rdquo;的语言，吸收一下其思想精华，说不定哪天就 能用得上，让自己和大家都感觉你很NB，抬高一下自己的身价^_^。记住：<a href="http://tonybai.com/2012/10/08/the-new-age-of-programming-language/"><b>编程语言也是要拼爹的</b></a>，<span class="st">系出名门的语言(诸如Go、Dart等)自然得到更多的青睐、使用和推广，出位的几率也就高出许多，尤其是在目前新编程语言百花齐放的阶段。因此在选择有思想的新语言时，最好在这些名门之后中做优选。</span></p>
<p><span class="st">这个时代喜欢&ldquo;专家&rdquo;，因此我们在一两门语言上务必要做到&ldquo;精专&rdquo;，这是会给你带来黄油和面包的语言。</span><span class="st">要专到什么程度呢？我有一个同事，什么问题都用C解决。他甚至为此写了个不小的基础框架，所有业务问题的Code放在框架中被回调即可，即便是这个问题用Python实现只需几行代码。</span></p>
<p><span class="st">计算机科学的研究核心是什么？我想肯定不是编程语言，就好比社会科学研究的核心不是人类语言一样。</span><span class="st">我比较欣赏这样的观点：</span><span class="st"><b>作为程序员而言，最重要的是去创造，而不是研究</b>。我们应更多的利用已经掌握的语言</span>解决现实中的问题。<span class="st">做 编程语言研究的人可能要了解各种语言的特点与实现方式，但对于大多数的程序员来说，其实我们只需要关注问题域：做底层平台开发的，关注机器模型、通信原理 以及OS原理和实现细节；做算法的，很荣幸，那才是正统的程序设计的核心；前端攻城师则更多关注用户的体验。而在这些解决实际问题的过程中，我们更多采用 的是&ldquo;制式&rdquo;的编程语言。即做平台开发的，一般用C，C++等系统编程语言，更多的考虑的是性能；做前端开发的，PHP/JavaScript不可或缺。 我们要考虑的是如何利用这些制式的编程语言去解决问题，而在这些制式语言上，我们要做到精通。</span></p>
<p>从新兴语言中借鉴新思想，<span class="st">然后在旧语言中实现新语言的特性，其实更多是在旧语言中实现了某 种语法糖，你爱吃，不代表其他人也理解也爱吃，还容易被人误认为是&ldquo;炫技&rdquo;。如果你是技术负责人，且经过评估，新语言十分适合这个问题域，那莫不入直接引 入这门语言，让大家都能使用到这门语言的新思想、新特性。</span></p>
<p><span class="st">辩证的说，任何一种编程语言都有其利与弊，比如<a href="http://haskell.org">Haskell</a>，纯函数式语言，变量不能改变，无状态，对并行处理具有天然的适应性，但在处理基本IO时却要编写难于理解的monad；而在命令式语言中，这种IO处理简直简单的不得了。</span></p>
<p><span class="st">关于函数式语言，个人感觉未来若干年内仍难以大行其道，建议还是跟上命令式语言的演化主线吧。</span></p>
<p>跨越问题域学习语言，通常收获不大。一个做平台服务端，用惯了C的资深程序员，让他去学PHP写前端代码，估计是无法迸发出任何火花的。</p>
<p><span class="st">以上是自己这些年关于编程语言学习的一些体会，比较零散，但希望能有帮助。</span></p>
<p style='text-align:left'>&copy; 2013, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2013/10/22/some-experience-about-learning-programming-language/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
	</channel>
</rss>
