17611538698
webmaster@21cto.com

新型函数式编程语言 Gleam

编程语言 0 127 2024-06-28 07:33:05

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) //intsio.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 应该是一种让您领略编程不变性优势的好方法。

作者:福星

评论