[[Rust]]と[[Elixir]]の影響を受けている[[Gleam]]を[[Neovim]]でいじってみたくなったので。
## インストール
公式サイトを参考に。
<div class="link-card">
<div class="link-card-header">
<img src="https://gleam.run/images/lucy/lucy.svg" class="link-card-site-icon"/>
<span class="link-card-site-name">gleam.run</span>
</div>
<div class="link-card-body">
<div class="link-card-content">
<p class="link-card-title">Installing Gleam – Gleam</p>
<p class="link-card-description">Getting your computer ready for Gleam development.</p>
</div>
<img src="https://gleam.run//images/social-image.png" class="link-card-image" />
</div>
<a href="https://gleam.run/getting-started/installing/"></a>
</div>
[[Gleam]]本体をインストール。v1もう出ていたのか。
```console
$ mise use -g gleam
mise
[email protected] ✓ installed
```
[[Erlang]]をインストール。3分くらい時間がかかった。
```console
mise use -g erlang
```
## NeovimでLSPをセットアップ
[[nvim-lspconfig]]に設定を追加。
```lua
lspconfig.gleam.setup({ capabilities = capabilities })
```
[[nvim-treesitter|treesitter]]のインストール。
```lua
require("nvim-treesitter.configs").setup({
ensure_installed = {
"gleam",
}
})
```
## プロジェクトの作成
公式ドキュメントに従って進める。
<div class="link-card">
<div class="link-card-header">
<img src="https://gleam.run/images/lucy/lucy.svg" class="link-card-site-icon"/>
<span class="link-card-site-name">gleam.run</span>
</div>
<div class="link-card-body">
<div class="link-card-content">
<p class="link-card-title">Writing Gleam – Gleam</p>
<p class="link-card-description">The Gleam programming language</p>
</div>
<img src="https://gleam.run//images/social-image.png" class="link-card-image" />
</div>
<a href="https://gleam.run//writing-gleam/"></a>
</div>
```console
gleam new sandbox_gleam
```
テストを実行。
```console
gleam test
```
通常実行。
```console
gleam run
```
## ソースコードの編集
`src/sandbox_gleam.gleam` を編集。
```gleam
import gleam/io
fn sum(x: Int, y: Int) -> Int {
x + y
}
fn hello(x: Int) -> String {
case x {
1 -> "one"
2 -> "two"
_ -> "hogehoge"
}
}
pub fn main() {
io.debug(sum(1, 10))
io.debug(hello(1))
io.debug(hello(2))
io.debug(hello(3))
}
```
[[LSP]]周りはちょっと弱いような印象を受けた。言語仕様知らなくても[[LSP]]を頼りに書けるレベルには達していない印象。
```console
$ gleam run
Compiling sandbox_gleam
Compiled in 0.24s
Running sandbox_gleam.main
11
"one"
"two"
"hogehoge"
```
## JavaScriptの実行環境で実行
[[Gleam]]はデフォルトだと[[Erlang]]のコードにコンパイルされるが、[[JavaScript]]のコードにもコンパイル可能とのこと。試しに[[Bun]]のランタイム環境で動かしてみた。
```console
$ gleam run --target javascript --runtime bun
Compiling gleam_stdlib
Compiling gleeunit
Compiling sandbox_gleam
Compiled in 0.09s
Running sandbox_gleam.main
11
"one"
"two"
"hogehoge"
```
当然だが[[Erlang]]版と結果は一緒になる。生成された[[JavaScript]]のソースコードを確認するため、`build/dev/javascript/sandbox_gleam/sandbox_gleam.mjs`を見てみる。
```js
import * as $io from "../gleam_stdlib/gleam/io.mjs";
function sum(x, y) {
return x + y;
}
function hello(x) {
if (x === 1) {
return "one";
} else if (x === 2) {
return "two";
} else {
return "hogehoge";
}
}
export function main() {
$io.debug(sum(1, 10));
$io.debug(hello(1));
$io.debug(hello(2));
return $io.debug(hello(3));
}
```
この例はシンプルなのでコードとしてはほとんど変わらない。[[パターンマッチ (Gleam)|パターンマッチ]]もシンプルなので単純なif文で置き換えられていた。これだと面白くないので、もう少し複雑なコード例を確かめてみる。
```gleam
import gleam/int
import gleam/io
import gleam/list
import gleam/result
type Item {
String(String)
Int(Int)
}
fn aggregate(values: List(Item)) {
let total_or_err =
values
|> list.map(fn(x) {
case x {
String(s) -> result.unwrap(int.parse(s), 0)
Int(i) -> i
}
})
|> list.filter(fn(x) { x > 10 })
|> list.map(fn(x) { x * 10 })
|> list.reduce(fn(ac, x) { ac + x })
use r <- result.try(total_or_err)
Ok(r)
}
pub fn main() {
let r = aggregate([Int(10), String("one"), Int(20), String("two"), Int(30)])
case r {
Ok(x) -> io.debug("ok: " <> int.to_string(x))
Error(_) -> io.debug("error")
}
}
```
[[JavaScript]]だとこうなる。
```js
import * as $int from "../gleam_stdlib/gleam/int.mjs";
import * as $io from "../gleam_stdlib/gleam/io.mjs";
import * as $list from "../gleam_stdlib/gleam/list.mjs";
import * as $result from "../gleam_stdlib/gleam/result.mjs";
import { Ok, toList, CustomType as $CustomType } from "./gleam.mjs";
class String extends $CustomType {
constructor(x0) {
super();
this[0] = x0;
}
}
class Int extends $CustomType {
constructor(x0) {
super();
this[0] = x0;
}
}
function aggregate(values) {
let total_or_err = (() => {
let _pipe = values;
let _pipe$1 = $list.map(
_pipe,
(x) => {
if (x instanceof String) {
let s = x[0];
return $result.unwrap($int.parse(s), 0);
} else {
let i = x[0];
return i;
}
},
);
let _pipe$2 = $list.filter(_pipe$1, (x) => { return x > 10; });
let _pipe$3 = $list.map(_pipe$2, (x) => { return x * 10; });
return $list.reduce(_pipe$3, (ac, x) => { return ac + x; });
})();
return $result.try$(total_or_err, (r) => { return new Ok(r); });
}
export function main() {
let r = aggregate(
toList([
new Int(10),
new String("one"),
new Int(20),
new String("two"),
new Int(30),
]),
);
if (r.isOk()) {
let x = r[0];
return $io.debug("ok: " + $int.to_string(x));
} else {
return $io.debug("error");
}
}
```
こうして見ると、ゴリゴリのロジックに落とすというよりは[[Gleam]]に近いIFを持つ[[polyfill]]のようなものを準備して、記述を[[JavaScript]]に置き換える...というイメージの方が近そう。その結果、作成された[[JavaScript]]コード単体でも高い可読性を維持できていると感じた。
ただ、それは言い換えると、[[Gleam]]をビルドしたソースコードをポータビリティの高い状態で再利用できるわけではないと思えた。実行環境こそ[[Node.js]]、[[Deno]]、[[Bun]]、ブラウザと広く対応しているが、ポータビリティを高めるには[[webpack]]や[[esbuild]]、[[Rollup]]などの[[モジュールバンドラー]]が必要に思える。まあ、そこの再発明を[[Gleam]]でやる必要はないのでむしろその方が理にかなっていると言われればそれまで。個人的に、別言語で[[コンパイル]]したものを、さらに[[モジュールバンドラー]]でバンドルするのに少し抵抗があるだけかもしれない。
## 所感
[[Gleam]]は[[Rust]]と[[Elixir]]の良いとこどりをしているような言語に見える。[[Rust]]は[[所有権]]や[[ライフタイム]]周りが億劫に思えるし、[[Elixir]]は型にルーズで[[Erlang VM]]上で動かすことに(それがウリであっても)抵抗を感じていた。そのような人には響く言語なのかもしれない。
一方、メジャーな言語として仕事で利用できるかと言われると弱いと思う。まだv1なので... ということもあるが、個人的には[[Nim]]と同じようなポジションで、知る人ぞ知るちょっとしたことには使えるいぶし銀な言語... として一部のコミュニティに浸透していくのではと思っている。[[Rust]]の次なる言語として、[[Bun]]などで既に実績がある[[Zig]]も気になるところではあるが、[[Haskell]]をはじめとした関数型言語寄りの思想に何か惹かれる私としては、[[Gleam]]が流行って開発体験や実行環境がもう少し良くなったら...と何か期待する気持ちがあるような気がしてならない。