Gleam 是一种类型安全的函数式编程语言,用于构建可扩展的并发系统。它是否如其宣传的那样友好?我们来一探究竟。
当我的一位朋友读到Virgil 的文章(Wasm 联合创始人介绍新编程语言 Virgil)时,他建议我看看Gleam 。
Gleam 语言很酷也很新,其版本 1.0 在今年 3 月正式发布,并且它在函数式编程方面表现突出。
Gleam 是一种类型安全的函数式编程语言,用于构建可扩展的并发系统。
它可编译为Erlang和JavaScript,因此可与其他“BEAM”语言(如 Erlang 和 Elixir)直接互操作。(BEAM 是在 Erlang 运行时系统中执行用户代码的虚拟机,它是Bogdan 的 Erlang 抽象机的缩写)
Erlang 是一种早期的电信行业语言,非常注重并发性和容错性。它的做事方式至今仍受到尊重,这也是Elixir如此受欢迎的原因。在这篇文章中,我不会假设您熟悉这些;实际上,Gleam 特别友好,因此它也不会做太多假设。
让我们从hello world开始:
import gleam/io
pub fn main() {
io.println("hello world!")
}
这与Zig 中的相同内容非常相似。
有一个非常令人愉快的语言之旅,它利用 Gleam 的编译为 JavaScript 来进行动态检查。您也可以把它用作“游乐场”。
安装 Gleam也意味着要安装 Erlang。对于我的 Mac,使用了 Homebrew:
> brew install gleam
Homebrew 将自动安装 Erlang。
Gleam 带有模板(或项目)生成器,这点与 Rails 非常相似。因此要创建一个新的hello项目,只需输入如下:
“hello world”风格的单行代码已经作为默认代码存在于hello.gleam中:
此时,如果我运行整个项目:
请您注意,这两个包仅在第一次运行时进行编译。
包管理
有两个.toml文件(Tom 自己的标记语言),用作配置。
由于它们很简单,我们可以快速浏览一下。在gleam.toml 中:
[dependencies]
gleam_stdlib = ">= 0.34.0 and < 2.0.0"
请注意,它们有一个版本限制——提及最高版本以减少不兼容性。
manifest.toml 文件中提到了实际下载和当前使用的版本。
下面是一个简单的示例,我们可以学习一点 Gleam 并使用包管理器。我们将添加几个包,并编写一些代码来打印出环境变量。我将使用相同的hello项目模板,但插入了新代码。
首先,我们将添加新的软件包以允许读取环境(envoy)和读取命令行参数(argv)——您可能希望它是内置的,但可能会反映系统差异。
让我们将hello.gleam中的代码替换为按需打印出环境变量,代码如下:
import argv
import envoy
import gleam/io
import gleam/result
pub fn main() {
case argv.load().arguments {
["get", name] -> get(name)
_ -> io.println("Usage: get ")
}
}
fn get(name: String) -> Nil {
let value = envoy.get(name) |> result.unwrap("")
io.println(format_pair(name, value))
}
fn format_pair(name: String, value: String) -> String {
name <> "=" <> value
}
添加到公共main入口点后,我们有两个函数。它们使用的格式与在 Virgil 中看到的完全相同。
事实证明,类型注释是可选的,但被认为是良好做法。现在,我们有点功能性了。argv load执行了您期望的操作,并提取了一个希望恰好包含两个字符串的列表 — 其中第一个字符串等于“get”。这在语句中使用case。
顺便说一下,Gleamcase比大多数非函数式语言更灵活一些。下面我们来比较一下列表的内容:
let result = case x {
[] -> "Empty list"
[1] -> "List of just 1"
[4, ..] -> "List starting with 4"
[_, _] -> "List of 2 elements"
_ -> "Some other list"
}
因此,可以在 case 语句中比较模式。下划线_表示默认,并且会详尽检查可能的情况。
回到我们的环境变量读取代码,如果模式不是两个字符串的列表,则输出帮助文本。否则,它将调用get函数。
我们看到了管道函数,它有助于使长函数调用从左到右更易读。
let value = envoy.get(name) |> result.unwrap("")
这与以下内容相同:
let value = result.unwrap(envoy.get(name),"")
由于 Gleam 不会引发异常,因此它使用内置的Result类型,并解包获取好的路径值。
最后的奇怪之处是:
name <> "=" <> value
这只是字符串连接。
在这里,我第二次运行它,并使用所需的参数:
Gleam 没有 null,没有隐式转换,也没有异常。所以如果它编译成功,那就没问题了。此外,没有数值运算符重载,因此用于添加整数的代码与用于添加浮点数的代码不同:
io.debug(1 + 1) //ints
io.debug(1.0 +. 1.5) //floats
相等性适用于任何类型。通过使用函数式语言可以最好地体验不变性的一般概念,因此我不会对此进行掩盖。它确实有助于消除一大堆错误。
代数数据类型
最后,我们看到了Virgil中使用的代数数据类型(ADT) ,所以我很想看看 Gleam 中与之等效的工作原理。事实上,我们已经看到了case语句的用法。
我们获得自定义类型,然后对其进行模式匹配:
pub type Season {
Spring
Summer
Autumn
Winter
}
fn weather(season: Season) -> String {
case season {
Spring -> "Mild"
Summer -> "Hot"
Autumn -> "Windy"
Winter -> "Cold"
}
}
类型可以在记录中保存数据,这就是我们接近 Virgil 示例的方式:
import gleam/io
pub type Travel {
Walk(hours: Int)
Cycle(hours: Int)
Drive(hours: Int, speed: Int)
}
pub fn main() {
let walking = Walk(1)
let cycling = Cycle(1)
let bus_trip = Drive(2, 50)
let trip = [walking, cycling, bus_trip]
io.debug(trip)
}
// [Walk(hours: 1), Cycle(hours: 1), Drive(hours: 2, speed: 50)]
我认为我无法在类型内关联方法,但我可以访问记录值以获得与 Virgil 中类似的结果。我将把这留给更熟练的用户作为练习!
对于像我这样不怎么使用函数式代码的人来说,Gleam 非常容易上手,不会让我立即陷入“柯里化”等术语和其他函数式冲击中。
但如果您还不是 Gleam 的拥护者,那么 Gleam 应该是一种让您领略编程不变性优势的好方法。