返回资讯中心
外部精选
软件工程
#社区热议

Try Gleam:面向 BEAM 和 JavaScript 的函数式编程语言

来源标题:Try Gleam

Gleam 是一门面向 Erlang 虚拟机 BEAM 的函数式语言,同时也能编译成 JavaScript 在 Node.js 上运行。作者认为 Gleam 与 Rust 体验相似,约八成代码稍作语法替换即可在 Rust 中编译。Gleam 语法简单,并可与 Erlang、Elixir 生态互操作。

Hacker Newsnchagnet2026/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 的所有函数式部分,少了一些东西,但完全没有命令式的部分。 让我们从一个代码示例开始: // 省略导入 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 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。

打开原文
去社区讨论 →