返回资讯中心
外部精选
软件工程
#开发者实时流

Gleam 语言初体验:面向 BEAM 与 JavaScript 的简单函数式语言

来源标题:Try Gleam

作者分享了对 Gleam 语言的初步体验,认为其语法简单、与 BEAM 生态(Erlang 和 Elixir)互操作,还可编译为 JavaScript 在 Node.js 上运行。Gleam 是面向 Erlang 虚拟机的函数式语言,作者称熟悉 Rust 的开发者会感到亲切,约 80% 的代码经简单语法替换即可在 Rust 中编译。Gleam 官网将其定位为“用于构建可扩展类型安全系统的友好语言”。

Hacker News 最新nchagnet2026/11/9 · 今天30 分钟阅读

以下正文由 AI 翻译自 Hacker News 最新(原文版权归原站所有),可能存在偏差,重要内容请以原文为准。

programming 引言 如果你读过我最近那篇关于编程语言的吐槽,就会知道我对 Gleam 相当感兴趣。Gleam 是一门函数式语言,目标平台是 BEAM,也就是 Erlang 虚拟机,¹ 语法简洁,并且能与更广泛的 BEAM 生态(Erlang 和 Elixir)互操作。有意思的是,Gleam 还可以编译到 JavaScript,也就是说,Gleam 能被编译成 JavaScript 并通过 Node.js 运行。 我业余项目大多用 Rust,目前它是唯一真正让我觉得写代码是种享受的语言,而 Gleam 也成功挠到了同一个痒处。如果你是 Rust 老手,Gleam 会让你感到很熟悉,只是有几处关键差异。我感觉自己写的代码有 80% 可以直接复制到 Rust(稍微搜索替换一下语法)就能编译通过。 这篇博文不会对这门语言做详尽介绍,想深入学习的推荐去看那个优秀的交互式教程。不到半小时你就能对 Gleam 有个不错的体感。 在官网上,Gleam 被描述为“一门用于构建可扩展的类型安全系统的友好语言!”(强调是他们的),这句话很好地传达了 Gleam 的核心定位。 语法极其简单,大部分复杂的东西都藏在 Erlang 运行时里,除非你想碰,否则根本不用跟它打交道!当然,Gleam 是函数式语言,如果你一辈子都活在 C 风格的命令式世界里,一开始可能会有点发怵。但要是拿它跟其他函数式语言(OCaml、Haskell、Erlang)比,你很快就会发现 Gleam 简单得多。用上一阵子后,感觉它就像 Rust 里所有函数式的部分,少了几个特性,而且完全没有命令式的部分。 从一个代码示例开始吧: // 省略 import pub fn main() { let total = "1,X,5,6" // String |> string.split(on: ",") // List(String) |> list.map(int.parse) // List(Result(Int, Nil)) |> result.values // List(Int),无效项被丢弃 |> list.reduce(fn(acc, x) { acc + x }) // Result(Int, Nil) |> result.unwrap(0) // Int echo total // 12 Rust fn main() { let total = "1,X,5,6" // &str .split(",") .map(|s| s.parse::<i64>()) .filter_map(Result::ok) // 这里本可以用 .sum() .reduce(|acc, x| acc + x) .unwrap_or(0); println!("{}", total); // 12 拆解一下,你能看到 Gleam 的一些特点: 跟 Rust 代码片段对比,可以看出除了少数命名选择外,两者看起来相当相似。这个例子里主要区别其实就是管道语法 vs. 链式调用语法。Gleam 的类型没有方法,所以用管道代替。 管道操作符 |> 是你在本文中会遇到的三只 Gleam 吉祥物特性中的第一只,另外两只是 use 模式和尾调用优化。 如果你写过大量 JavaScript,可能对嵌套回调时出现的缩进爬升很熟悉 someFunc(arg, function(param) { someOtherFunc(param, function(otherParam) { // ... }) }) 在 Gleam 里,很多函数都期望一个回调,比如作为一种控制资源访问、并在处理完成后清理的方式(关闭数据库连接、释放文件句柄等)。为了避免这种回调金字塔,Gleam 提供了语法糖:use param <- some_func(arg) 等价于 some_func(arg, fn(param) { ... })。回调必须始终是函数的最后一个参数。这跟 |> 正好相反,后者填充的是第一个参数。 use param <- some_func(arg) use other_param <- some_other_func(param) echo other_param 不用 use 的版本 some_func(arg, fn(param) { some_other_func(param, fn(other_param) { echo other_param }) }) 尾调用优化 如果你不熟悉这个,这一节可能真会把你绊住。首先你需要知道,Gleam 里没有循环!同样,如果你之前写过 OCaml 或 Haskell,这点不会让你意外。98% 的情况下,⁴ 循环不过是对有界迭代的简单操作。想想对列表或集合的 for 循环,正是那种在 Rust 里你总有两种写法的事情:一种命令式,用货真价实的 for 循环;一种更函数式,用迭代器操作的组合。嗯,看看上一节的代码示例,你会发现 Gleam 用的是第二种,并且有大量函数可以遍历列表、集合、元组、字符串,任何东西。 但如果你需要更复杂的东西呢?那 2% 的情况呢?这时候你就得改用递归了!一个能说明这一点的简单算法是计算最大公约数(GCD)的欧几里得算法。下面你可以看到 Rust 的迭代版本和 Gleam 的递归版本。 fn gcd(mut a: u64, mut b: u64) -> u64 { while b != 0 { let remainder = a % b; a = b; b = remainder; a Gleam fn gcd(a: Int, b: Int) -> Int { case b { 0 -> a _ -> gcd(b, a % b) 当然,你也可以在 Rust 里写递归版本,但这通常不是个好主意。命令式语言里的递归很容易失控:每次调用都会新增一个栈帧,递归太深就会爆栈(臭名昭著的栈溢出)。 在 BEAM 上没有固定大小的栈,所以失控的递归会吃掉堆内存,直到进程被杀掉,但道理还是一样:深递归等于麻烦。为了避免这个问题,函数式语言的编译器实现了一种叫尾调用优化(TCO)的技术,当递归调用是函数做的最后一件事时就会生效。此时现有的栈帧会被复用,而不是再叠一个新的,从而获得接近命令式 while 循环的性能。⁵

(原文较长,此处译出前 6000 字,完整内容请点「打开原文」。)

正文由 探How 从来源站点 RSS 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。

打开原文
去社区讨论 →