Gleam 言語
- カテゴリ:
- その他プログラミング言語
- コメント数:
- Comments: 0
◆ 静的型付けの関数型言語
◆ Erlang または JavaScript にコンパイルしてそれらのランタイムで動かす
◆ シンプルで使いやすそうな気がする
◆ Erlang または JavaScript にコンパイルしてそれらのランタイムで動かす
◆ シンプルで使いやすそうな気がする
Gleam という言語が今月の頭に 1.0 リリースを迎えてました
かわいい星のキャラクターです
https://gleam.run/
後で触れてみようと思っていたのに忘れていて遅くなりましたが 軽く使ってみたので 使い方とか感想です
不満に感じる部分もほぼなくて良さそうに感じました
コンパイルする言語でコンパイル先が Erlang か JavaScript という少し特殊なものでした
ローカルで動かすなら Erlang でブラウザなら JavaScript という感じでの使い分けです
なのでローカルで動かす場合は Gleam に加えて Erlang も必要になります
信頼性のある Erlang のランタイムを使えることが魅力のように書かれてました
たしかに Erlang ってそういう評価をよく見ます
ただそういう作りということもあってか 標準ライブラリにはファイルシステムへのアクセスや日付の取得などが含まれていないようです
サードパーティのライブラリがあるのでそれを使う必要があります
ライブラリの中を見ると Erlang や JavaScript で実装されていてコンパイル対象によってそれぞれを使い分けてるようでした
JavaScript に変換するくらいなら最初から JavaScript を書くので 期待するのはローカルで動かすことなのですが Erlang は全然書けないんですよね
なので私の場合はできることは限られそうで そこが欠点かもしれません
コンパイルする以外にもプロジェクトのテンプレート作成やパッケージマネージャやテストやフォーマッターなど一通り揃っています
珍しいところではドキュメント作成機能もありました
deno みたいな感じです
gleam new して作られたフォルダに移動して gleam run で動かせます
gleam new で作られるテンプレートにはテストコードもあるので gleam test でテスト実行もできます
Rust に結構似てると思います
簡単なコードだとこんな感じです
静的型付け言語なので型を書いてますが 引数も推論されるので省略可能です
ただ省略可能でも書くほうが推奨されるようです
外部公開なら書いておいたほうがいいかもですが 内部用で処理を分割するだけで型を書くのは面倒なので省略可能なのはいいところです
とはいえ やりすぎるとコードにミスがあったときに変な推論結果を元にエラーが出るので 原因がわかりづらくなります
ここまで省略可能だと静的型付け言語でも手間はあまりないですし 動的言語に近い感じで書けます
gleam.toml が依存関係とかプロジェクトの情報を書くところです
Node.js の package.json や Rust の Cargo.toml や Python の pyproject.toml みたいなものです
src/foo.gleam と test/foo_test.gleam はこんなのです
これを実行するときは gleam run で テストするなら gleam test です
そもそも return キーワードはないので 早期リターンはないです
ここは不便に思うことはあるかもですが ↓みたいなロジックで作ればいいので ないならないでそんなに困らないかなと思ってます
わかりやすくするようここは JavaScript のコードです
mut や mutable と言ったキーワードはなくミュータブルにはできません
ただ こういうことはできます
同じ名前ですが 新しい foo を作る形なのでエラーになりません
新しい foo を作るときの右辺で古い foo を参照できます
ただし文字列の結合は少し変わっていて <> 演算子です
${} みたいな String Interpolation 機能は現在はないようです
ただ <> で十分か見てみるために今のところは対応しない みたいな感じで将来的に需要があれば追加されるのかもです
https://github.com/gleam-lang/gleam/issues/1473
string.concat 関数を使うこともできます
関数を作った場所の外側のスコープを参照することもできます
基本は関数で操作します
よくある形で内側から読まないといけなくて関数のネストがあるととても読みづらいです
ですがパイプオペレーターがあるのでメソッドチェーンのように上から下の流れで書けます
Gleam では関数はカリー化されていないので引数が足りないとエラーになります
ですがパイプオペレーターの右辺に書くものだと 一番最初の引数に左辺の値が入るので そこでのみ引数不足の関数呼び出しのコードを書けます
_ を使う方法は他でも使えます
_ 単体は特殊な意味を持つので 通常の変数に使えません
_ に代入は値を捨てるような動きになります
これは f(_) が関数呼び出しにならず関数を作るだけになって呼び出さないので出力はありません
他言語だと _ がユニット型と呼ばれているのもありますが Gleam だと Nil があってこれがユニット型になります
便利ですね
少し特殊なのは 式のグループ化にもこれを使います
掛け算より足し算を優先させたいとき 一般的には () を使いますが Gleam ではこれも {} になります
() は使えず {} を使うようエラーメッセージが出ます
慣れないと他の言語の感覚で書けないので不便ですが けっこうアリな気はしています
else if をしたいときにはちょっと不便です
switch を使える言語では true に対するマッチングで case に式を書いたりしますが Gleam の case はパターンの構文であって式ではないです
guard を使えば if に式を書けますが少し長くて見づらい感じです
単純に case をネストすることもできますが else if がいくつもあるとインデントが深くなっていきます
Type1 のところの中には Value の 1 パターンだけですが ここに色々追加できます
それぞれがバリアントと呼ばれて 値を持つ場合はレコードと呼ばれます
レコードが 1 種類しかないときは 型名と名前を合わせてるのをよく見かけるので合わせるのが一般的かもしれません
更新したい場合は よくあるスプレッド構文と更新内容の部分フィールドの組み合わせを使います
スプレッドは .. で 点が 2 つです
ディクショナリの更新は dict2 みたいな感じで dict.insert を使います
JavaScript でいうこういう処理です
シンプルにしてるのでこれなら配列のメソッドで済むのですが こういう手続的に処理したいことがあります
そういう場合は 再帰関数にして count に当たるものを更新しながら常に次の関数に渡していって最後まできたらそれを返すと良いです
Gleam には Tail call optimisation があるらしいので再帰でスタックがあふれる心配はしなくて良さそうです
中身は Ok と Error のレコードです
Ok と Error はインポートなしで使えます
これが例外の代わりで 結果が Ok または Error に包まれて帰ってきます
これらの型が Result になってます
値がある場合は Some で包んで 値がなければ None です
2 つめの引数がデフォルト値で Error の場合や None の場合に返す値を指定します
result の場合は unwrap_both でどちらの場合でも取り出して unwrap_error でエラーの場合のみ取り出すことができます
同じ感じで map を使えば Result 型は Ok の場合のみ Option 型は Some の場合のみ値を更新できます
Error や None はそのまま維持されます
Result 型には try もあって map だと結果の値を返せば Ok で包まれるのに対して try は Ok か Error (Result 型) を返さないといけないです
map で Result 型を返すと Result 型がネストされるので失敗する可能性があるなら try の方を使う感じです
名前の try にあってますね
関数に関数を渡す場合にこういう感じでどんどんネストが深くなります
Node.js で懐かしのコールバック地獄というやつですね
これを回避する記法が use でこんな感じにできます
他の関数型言語の let などで見かけるようなやつです
use 以降の式全体が関数に包まれるような形になるので use を使う関数の返り値に気をつける必要があります
use を使わない場合を見比べるとわかりやすいですが 最初の use の <- の右側の関数が返すものになります
これは try と組み合わせて使うことが多いようです
この例では直接 callback に数値を入れてますが try だと unwrap した内容を渡してくれます
Result 型の中身を使う場合どうしてもネストしてしまって unwrap するにしても早期リターンがないので場合分けのネストになってしまいます
なので Gleam だとこういう書き方になるようです
try を使うと最後で Result を返さないといけなくなるので Ok(Nil) を置いてます
この場合は返り値を使わないので こういうケースは try じゃなくて map でも良さそうです
ブロックコメントはなさそうです
ドキュメンテーションコメントは
モジュール → ////
関数や型 → ///
と分かれています
Java 系の Doc みたいな余計な装飾がなくていいですね
JavaScript のこれみたいな感じです
構文的には型も含めてこんな感じになります
型を省略すると
になるので C 系の言語を見慣れていると a や c が型に見えてしまうんですよね
省略すると同じ名前になるのかというとそういうわけでもなく キーワード引数機能が無効になってしまいます
内部と同じ名前にしたいなら同じ名前を指定します
今のファイルからの相対パスは使えません
常に src からなら統一感があって 見た目は綺麗になりそうですが 同じフォルダのファイルのインポートで長いパスを書かないといけないのは面倒そうです
特にフォルダ単位で移動させたときに相対パスにしていれば修正がいらないこともありますが src からの相対パスは絶対パスみたいなものなので 確実に修正が必要になります
日付ライブラリの birl を入れて使う場合はこんな感じです
src フォルダに birl.gleam モジュールがあると被ります
そうなると優先されるのは src フォルダの方です
ライブラリにアクセスできなくなります
ライブラリがあまり一般的な名前を使わなければ問題ないかもですが html とか json みたいな名前だと結構被りそうなんですよね
ちなみに gleam というモジュール名は予約語になっていて使えないようでした
あとは標準ライブラリの関数などでできることが増える感じです
それくらいにシンプルな言語のようです
個人的には今まで触れた関数型言語の中では一番とっつきやすくて良さそうに感じました
関数型言語になると変に複雑なものが多いですが Gleam はそういうのがほとんどないですし いろいろなところで使いやすさを感じました
普段使いするものというわけではないですが 今後の進化が気になる言語です
かわいい星のキャラクターです
https://gleam.run/
後で触れてみようと思っていたのに忘れていて遅くなりましたが 軽く使ってみたので 使い方とか感想です
Gleam
かなりシンプルな言語でドキュメントも短めでした不満に感じる部分もほぼなくて良さそうに感じました
コンパイルする言語でコンパイル先が Erlang か JavaScript という少し特殊なものでした
ローカルで動かすなら Erlang でブラウザなら JavaScript という感じでの使い分けです
なのでローカルで動かす場合は Gleam に加えて Erlang も必要になります
信頼性のある Erlang のランタイムを使えることが魅力のように書かれてました
たしかに Erlang ってそういう評価をよく見ます
ただそういう作りということもあってか 標準ライブラリにはファイルシステムへのアクセスや日付の取得などが含まれていないようです
サードパーティのライブラリがあるのでそれを使う必要があります
ライブラリの中を見ると Erlang や JavaScript で実装されていてコンパイル対象によってそれぞれを使い分けてるようでした
JavaScript に変換するくらいなら最初から JavaScript を書くので 期待するのはローカルで動かすことなのですが Erlang は全然書けないんですよね
なので私の場合はできることは限られそうで そこが欠点かもしれません
CLI
最近の言語に多いと思いますが CLI ツールがリッチですコンパイルする以外にもプロジェクトのテンプレート作成やパッケージマネージャやテストやフォーマッターなど一通り揃っています
珍しいところではドキュメント作成機能もありました
deno みたいな感じです
gleam new して作られたフォルダに移動して gleam run で動かせます
gleam new で作られるテンプレートにはテストコードもあるので gleam test でテスト実行もできます
構文
関数型言語というだけあって 他の関数型言語に近いと思いますRust に結構似てると思います
簡単なコードだとこんな感じです
import gleam/io
pub fn main() {
io.debug(add(1, 2))
}
fn add(a: Int, b: Int) -> Int {
a + b
}
静的型付け言語なので型を書いてますが 引数も推論されるので省略可能です
import gleam/io
pub fn main() {
io.debug(add(1, 2))
}
// ↓これでもいい
fn add(a, b) {
a + b
}
ただ省略可能でも書くほうが推奨されるようです
外部公開なら書いておいたほうがいいかもですが 内部用で処理を分割するだけで型を書くのは面倒なので省略可能なのはいいところです
とはいえ やりすぎるとコードにミスがあったときに変な推論結果を元にエラーが出るので 原因がわかりづらくなります
ここまで省略可能だと静的型付け言語でも手間はあまりないですし 動的言語に近い感じで書けます
実行
gleam new でプロジェクトのテンプレートを作ります[root@ddef652c5abe foo]# gleam new .
Your Gleam project foo has been successfully created.
The project can be compiled and tested by running these commands:
gleam test
[root@ddef652c5abe foo]# tree .
.
├── README.md
├── gleam.toml
├── src
│ └── foo.gleam
└── test
└── foo_test.gleam
3 directories, 4 files
gleam.toml が依存関係とかプロジェクトの情報を書くところです
Node.js の package.json や Rust の Cargo.toml や Python の pyproject.toml みたいなものです
name = "foo"
version = "1.0.0"
# Fill out these fields if you intend to generate HTML documentation or publish
# your project to the Hex package manager.
#
# description = ""
# licences = ["Apache-2.0"]
# repository = { type = "github", user = "username", repo = "project" }
# links = [{ title = "Website", href = "https://gleam.run" }]
#
# For a full reference of all the available options, you can have a look at
# https://gleam.run/writing-gleam/gleam-toml/.
[dependencies]
gleam_stdlib = "~> 0.34 or ~> 1.0"
[dev-dependencies]
gleeunit = "~> 1.0"
src/foo.gleam と test/foo_test.gleam はこんなのです
[root@ddef652c5abe foo]# cat src/foo.gleam
import gleam/io
pub fn main() {
io.println("Hello from foo!")
}
[root@ddef652c5abe foo]# cat test/foo_test.gleam
import gleeunit
import gleeunit/should
pub fn main() {
gleeunit.main()
}
// gleeunit test functions end in `_test`
pub fn hello_world_test() {
1
|> should.equal(1)
}
これを実行するときは gleam run で テストするなら gleam test です
[root@ddef652c5abe foo]# gleam run
Resolving versions
Downloading packages
Downloaded 2 packages in 0.07s
Compiling gleam_stdlib
Compiling gleeunit
Compiling foo
Compiled in 1.87s
Running foo.main
Hello from foo!
[root@ddef652c5abe foo]# gleam test
Compiled in 0.01s
Running foo_test.main
.
Finished in 0.021 seconds
1 tests, 0 failures
return がない
関数型言語でよく見かけるように 返り値は return 不要で最後の式の結果が使われますそもそも return キーワードはないので 早期リターンはないです
ここは不便に思うことはあるかもですが ↓みたいなロジックで作ればいいので ないならないでそんなに困らないかなと思ってます
わかりやすくするようここは JavaScript のコードです
const fn = (value) => {
if (!value) {
return null
} else {
return _fn(value)
}
}
const _fn = (value) => {
// ...
}
イミュータブル
こっちも関数型言語でよく見かけるように値はイミュータブルですmut や mutable と言ったキーワードはなくミュータブルにはできません
ただ こういうことはできます
import gleam/io.{debug}
pub fn main() {
let foo = 100
let foo = foo + 1
debug(foo)
}
同じ名前ですが 新しい foo を作る形なのでエラーになりません
新しい foo を作るときの右辺で古い foo を参照できます
文字列の扱い
Rust みたいな文字列の扱いづらさはないようで動的言語と近い感じで扱えるようですただし文字列の結合は少し変わっていて <> 演算子です
${} みたいな String Interpolation 機能は現在はないようです
ただ <> で十分か見てみるために今のところは対応しない みたいな感じで将来的に需要があれば追加されるのかもです
https://github.com/gleam-lang/gleam/issues/1473
string.concat 関数を使うこともできます
import gleam/io.{debug}
import gleam/string
pub fn main() {
let str1 = "foo" <> "bar"
let str2 = string.concat(["foo", "bar"])
debug(str1)
debug(str2)
}
"foobar"
"foobar"
関数の扱い
関数は変数のように扱えますし 式の中で直接書くこともできますimport gleam/io.{debug}
pub fn main() {
let return_one = fn () { 1 }
call(return_one)
call(fun1)
call(fn() { "text" })
}
fn fun1() {
10
}
fn call(fun) {
debug(fun())
}
1
10
"text"
関数を作った場所の外側のスコープを参照することもできます
import gleam/io.{debug}
pub fn main() {
let get = make_get()
debug(get())
}
fn make_get() {
let value = 100
fn() { value }
}
100
パイプオペレーター
オブジェクト指向言語じゃないのでオブジェクトのメソッドみたいなものはありません基本は関数で操作します
import gleam/io.{debug}
import gleam/string
import gleam/list
pub fn main() {
debug(string.length("foo"))
debug(string.repeat("abc", 3))
debug(string.starts_with("xyz", "x"))
debug(string.join(["foo", "bar"], "/"))
debug(string.split("foo-bar", "-"))
debug(string.slice("abcdef", 2, 4))
debug(string.replace("1-2", "-", "/"))
debug(
string.join(
list.reverse(
list.map(
string.split("1-2-3-4-5", "-"),
fn(x) { "[" <> x <> "]" }
)
),
" "
)
)
}
3
"abcabcabc"
True
"foo/bar"
["foo", "bar"]
"cdef"
"1/2"
"[5] [4] [3] [2] [1]"
よくある形で内側から読まないといけなくて関数のネストがあるととても読みづらいです
ですがパイプオペレーターがあるのでメソッドチェーンのように上から下の流れで書けます
import gleam/io.{debug}
import gleam/string
import gleam/list
pub fn main() {
debug(
"1-2-3-4-5"
|> string.split("-")
|> list.map(fn(x) { "[" <> x <> "]" })
|> list.reverse
|> string.join(" ")
)
}
"[5] [4] [3] [2] [1]"
特殊な動き
パイプオペレーターの後は少し特殊な書き方ができるようですimport gleam/io.{debug}
pub fn main() {
debug(100 |> div(2))
debug(100 |> div(_, 2))
debug(100 |> div(200, _))
}
fn div(a, b) {
a / b
}
50
50
2
Gleam では関数はカリー化されていないので引数が足りないとエラーになります
import gleam/io.{debug}
pub fn main() {
let div10 = div(10)
debug(div10(2))
}
fn div(a, b) {
a / b
}
error: Incorrect arity
┌─ /opt/foo/src/foo.gleam:4:14
│
4 │ let div10 = div(10)
│ ^^^^^^^ Expected 2 arguments, got 1
ですがパイプオペレーターの右辺に書くものだと 一番最初の引数に左辺の値が入るので そこでのみ引数不足の関数呼び出しのコードを書けます
_ を使う方法は他でも使えます
import gleam/io.{debug}
pub fn main() {
let div2 = div(_, 2)
debug(div2(100))
}
fn div(a, b) {
a / b
}
50
_ 単体は特殊な意味を持つので 通常の変数に使えません
_ に代入は値を捨てるような動きになります
import gleam/io.{debug}
pub fn main() {
let _ = 1
f(_)
}
fn f(v) {
debug(v)
}
これは f(_) が関数呼び出しにならず関数を作るだけになって呼び出さないので出力はありません
他言語だと _ がユニット型と呼ばれているのもありますが Gleam だと Nil があってこれがユニット型になります
ブロック
ブロックが式として値を返しますimport gleam/io.{debug}
pub fn main() {
let result = {
let tmp = 1 + 2
tmp * tmp
}
debug(result)
}
9
便利ですね
少し特殊なのは 式のグループ化にもこれを使います
掛け算より足し算を優先させたいとき 一般的には () を使いますが Gleam ではこれも {} になります
import gleam/io.{debug}
pub fn main() {
let value = { 1 + 2 } * 10
debug(value)
}
30
() は使えず {} を使うようエラーメッセージが出ます
import gleam/io.{debug}
pub fn main() {
let value = ( 1 + 2 ) * 10
debug(value)
}
error: Syntax error
┌─ /opt/foo/src/foo.gleam:4:14
│
4 │ let value = ( 1 + 2 ) * 10
│ ^ This parenthesis cannot be understood here
Hint: To group expressions in gleam use "{" and "}".
慣れないと他の言語の感覚で書けないので不便ですが けっこうアリな気はしています
分岐
if 文がなくて case によるパターンマッチングのみですelse if をしたいときにはちょっと不便です
switch を使える言語では true に対するマッチングで case に式を書いたりしますが Gleam の case はパターンの構文であって式ではないです
guard を使えば if に式を書けますが少し長くて見づらい感じです
単純に case をネストすることもできますが else if がいくつもあるとインデントが深くなっていきます
import gleam/io.{debug}
pub fn main() {
debug(use_nested_case(1))
debug(use_nested_case(-1))
debug(use_nested_case(0))
debug(use_guard(1))
debug(use_guard(-1))
debug(use_guard(0))
}
fn use_nested_case(v) {
case v == 0 {
True -> "zero"
False -> case v > 0 {
True -> "positive"
False -> "negative"
}
}
}
fn use_guard(v) {
case v {
_ if v > 0 -> "positive"
_ if v < 0 -> "negative"
_ -> "zero"
}
}
"positive"
"negative"
"zero"
"positive"
"negative"
"zero"
オブジェクト的なもの
リテラルで書ける JavaScript のオブジェクトのようなものはなくて事前に定義が必要なレコード型か タプルのリストからディクショナリ型を作るかになってデータをまとめるのは動的な言語よりは少し面倒ですレコード
import gleam/io.{debug}
type Type1 {
Value(num: Int, str: String)
}
pub fn main() {
let value1 = Value(1, "a")
let value2 = Value(num: 1, str: "a")
debug(value1.num)
debug(value2.str)
debug(value1 == value2)
}
1
"a"
True
Type1 のところの中には Value の 1 パターンだけですが ここに色々追加できます
それぞれがバリアントと呼ばれて 値を持つ場合はレコードと呼ばれます
レコードが 1 種類しかないときは 型名と名前を合わせてるのをよく見かけるので合わせるのが一般的かもしれません
type Type1 {
Type1(num: Int, str: String)
}
更新したい場合は よくあるスプレッド構文と更新内容の部分フィールドの組み合わせを使います
スプレッドは .. で 点が 2 つです
import gleam/io.{debug}
type Foo {
Foo(a: Int, b: Int, c: Int)
}
pub fn main() {
let foo = Foo(1, 2, 3)
debug(Foo(..foo, b: 10))
debug(Foo(..foo, a: 100, c: 300))
Nil
}
Foo(1, 10, 3)
Foo(100, 2, 300)
ディクショナリ
ディクショナリはリテラルで書けず リストから変換するか一つずつ要素を追加ですimport gleam/io.{debug}
import gleam/dict
import gleam/result
pub fn main() {
let list1 = [#("foo", 1), #("bar", 2)]
let dict1 = dict.from_list(list1)
let dict2 = dict.new()
|> dict.insert("foo", 1)
|> dict.insert("bar", 2)
debug(dict1 |> dict.get("foo") |> result.unwrap(-1))
debug(dict2 |> dict.get("bar") |> result.unwrap(-1))
debug(dict1 == dict2)
debug(dict1 |> dict.get("baz") |> result.unwrap(-1))
debug(dict1)
}
1
2
True
-1
dict.from_list([#("bar", 2), #("foo", 1)])
ディクショナリの更新は dict2 みたいな感じで dict.insert を使います
リストのループ
gleam/list に map や filter を始めリスト処理の関数が揃っているのでリストは扱いやすい方ですが ときどき一つずつループするようなことをしたいですJavaScript でいうこういう処理です
const items = [true, false, true]
let count = 0
for (const item of items) {
if (item) count++
}
console.log(count)
シンプルにしてるのでこれなら配列のメソッドで済むのですが こういう手続的に処理したいことがあります
そういう場合は 再帰関数にして count に当たるものを更新しながら常に次の関数に渡していって最後まできたらそれを返すと良いです
import gleam/io.{debug}
pub fn main() {
count_true([True, False, True]) |> debug
}
fn count_true(list) {
rec(list, 0)
}
fn rec(list, count) {
case list {
[first, ..rest] -> case first {
True -> rec(rest, count + 1)
False -> rec(rest, count)
}
[] -> count
}
}
Gleam には Tail call optimisation があるらしいので再帰でスタックがあふれる心配はしなくて良さそうです
ラップされた型
Result
最近は結構いろいろなところで見る気がする Result 型があります中身は Ok と Error のレコードです
Ok と Error はインポートなしで使えます
import gleam/io.{debug}
pub fn main() {
debug(Ok(1))
debug(Error(2))
}
Ok(1)
Error(2)
これが例外の代わりで 結果が Ok または Error に包まれて帰ってきます
これらの型が Result になってます
Option
また null はなくて ユニット型に Nil がありますが Nullable ぽいものとして Option 型がありますpub type Option(a) {
Some(a)
None
}
値がある場合は Some で包んで 値がなければ None です
操作
これらは unwrap で中身を取り出せます2 つめの引数がデフォルト値で Error の場合や None の場合に返す値を指定します
result の場合は unwrap_both でどちらの場合でも取り出して unwrap_error でエラーの場合のみ取り出すことができます
import gleam/io.{debug}
import gleam/result
import gleam/option
pub fn main() {
debug("--- result.unwrap ---")
Ok("OK") |> result.unwrap("default") |> debug
Error("ERROR") |> result.unwrap("default") |> debug
debug("--- result.unwrap_both ---")
Ok("OK") |> result.unwrap_both() |> debug
Error("ERROR") |> result.unwrap_both() |> debug
debug("--- result.unwrap_error ---")
Ok("OK") |> result.unwrap_error("default") |> debug
Error("ERROR") |> result.unwrap_error("default") |> debug
debug("--- option.unwrap ---")
option.Some("OK") |> option.unwrap("default") |> debug
option.None |> option.unwrap("default") |> debug
}
"--- result.unwrap ---"
"OK"
"default"
"--- result.unwrap_both ---"
"OK"
"ERROR"
"--- result.unwrap_error ---"
"default"
"ERROR"
"--- option.unwrap ---"
"OK"
"default"
同じ感じで map を使えば Result 型は Ok の場合のみ Option 型は Some の場合のみ値を更新できます
Error や None はそのまま維持されます
Result 型には try もあって map だと結果の値を返せば Ok で包まれるのに対して try は Ok か Error (Result 型) を返さないといけないです
import gleam/io.{debug}
import gleam/result
pub fn main() {
result.map(Ok(1), fn(x) { x + 1 }) |> debug
result.try(Ok(1), fn(x) { Ok(x + 1) }) |> debug
}
Ok(2)
Ok(2)
map で Result 型を返すと Result 型がネストされるので失敗する可能性があるなら try の方を使う感じです
名前の try にあってますね
use
use を使うと関数のネスト減らせます関数に関数を渡す場合にこういう感じでどんどんネストが深くなります
import gleam/io.{debug}
pub fn main() {
getvalue1(fn(foo) {
getvalue2(fn(bar, baz) {
debug(foo)
debug(bar)
debug(baz)
})
})
}
fn getvalue1(callback) {
callback(1)
}
fn getvalue2(callback) {
callback(10, 20)
}
1
10
20
Node.js で懐かしのコールバック地獄というやつですね
これを回避する記法が use でこんな感じにできます
import gleam/io.{debug}
pub fn main() {
use foo <- getvalue1()
use bar, baz <- getvalue2()
debug(foo)
debug(bar)
debug(baz)
}
fn getvalue1(callback) {
callback(1)
}
fn getvalue2(callback) {
callback(10, 20)
}
1
10
20
他の関数型言語の let などで見かけるようなやつです
use 以降の式全体が関数に包まれるような形になるので use を使う関数の返り値に気をつける必要があります
use を使わない場合を見比べるとわかりやすいですが 最初の use の <- の右側の関数が返すものになります
これは try と組み合わせて使うことが多いようです
この例では直接 callback に数値を入れてますが try だと unwrap した内容を渡してくれます
Result 型の中身を使う場合どうしてもネストしてしまって unwrap するにしても早期リターンがないので場合分けのネストになってしまいます
import gleam/io.{debug}
import gleam/result
pub fn main() {
let value1 = getvalue1()
case result.is_ok(value1) {
True -> {
let value2 = getvalue2()
case result.is_ok(value2) {
True -> {
let value1 = result.unwrap(value1, 0)
let value2 = result.unwrap(value2, 0)
debug(value1 + value2)
Nil
}
False -> Nil
}
}
False -> Nil
}
}
fn getvalue1() {
Ok(1)
}
fn getvalue2() {
Ok(2)
}
なので Gleam だとこういう書き方になるようです
import gleam/io.{debug}
import gleam/result
pub fn main() {
use value1 <- result.try(getvalue1())
use value2 <- result.try(getvalue2())
debug(value1 + value2)
Ok(Nil)
}
fn getvalue1() {
Ok(1)
}
fn getvalue2() {
Ok(2)
}
3
try を使うと最後で Result を返さないといけなくなるので Ok(Nil) を置いてます
この場合は返り値を使わないので こういうケースは try じゃなくて map でも良さそうです
コメント
コメントは // を使いますブロックコメントはなさそうです
ドキュメンテーションコメントは
モジュール → ////
関数や型 → ///
と分かれています
Java 系の Doc みたいな余計な装飾がなくていいですね
キーワード引数
少し変わった機能で キーワード引数に内部用の変数名と別の名前をつけることができますJavaScript のこれみたいな感じです
const fn = ({ foo: bar }) => console.log(bar)
fn({ foo: 1 })
// 1
構文的には型も含めてこんな感じになります
import gleam/io.{debug}
pub fn main() {
func1(for_external: 1)
}
fn func1(for_external for_internal: Int) {
debug(for_internal)
}
1
型を省略すると
fn func1(a b, c d) {
//
}
になるので C 系の言語を見慣れていると a や c が型に見えてしまうんですよね
省略すると同じ名前になるのかというとそういうわけでもなく キーワード引数機能が無効になってしまいます
内部と同じ名前にしたいなら同じ名前を指定します
インポートのパス
相対パスのみ
インポートするときのパスですが 常に src フォルダからの相対パスでの指定になるようです今のファイルからの相対パスは使えません
常に src からなら統一感があって 見た目は綺麗になりそうですが 同じフォルダのファイルのインポートで長いパスを書かないといけないのは面倒そうです
特にフォルダ単位で移動させたときに相対パスにしていれば修正がいらないこともありますが src からの相対パスは絶対パスみたいなものなので 確実に修正が必要になります
ライブラリとの競合
インストールしたパッケージと src 内のファイルでインポート方法に違いがありません日付ライブラリの birl を入れて使う場合はこんな感じです
import birl
import gleam/io.{debug}
pub fn main() {
let now = birl.now()
debug(now |> birl.to_iso8601)
}
"2024-03-30T16:57:07.726Z"
src フォルダに birl.gleam モジュールがあると被ります
そうなると優先されるのは src フォルダの方です
ライブラリにアクセスできなくなります
ライブラリがあまり一般的な名前を使わなければ問題ないかもですが html とか json みたいな名前だと結構被りそうなんですよね
ちなみに gleam というモジュール名は予約語になっていて使えないようでした
おわり
思ってたより長くなりましたが ここに書いただけでドキュメントにある言語機能はほとんど含まれてると思いますあとは標準ライブラリの関数などでできることが増える感じです
それくらいにシンプルな言語のようです
個人的には今まで触れた関数型言語の中では一番とっつきやすくて良さそうに感じました
関数型言語になると変に複雑なものが多いですが Gleam はそういうのがほとんどないですし いろいろなところで使いやすさを感じました
普段使いするものというわけではないですが 今後の進化が気になる言語です