TypeScriptユーザーに贈るGleam入門
スマホの人向け目次
最近v1に到達したGleamという静的型付けな関数型言語があります。
GleamはErlangとJavaScriptをターゲットに実行できるため、今TypeScriptを使っている領域でも使うことができます。
この記事ではTypeScriptユーザー向けにGleamの文法を解説していきます。
記事を通してGleamの良さを感じていただければ幸いです。
Gleamの公式サイトでは以下の言語のユーザー向けのチートシートもあるため、この中に知っている言語があるのならそちらを読んでみるのがオススメです。
- Elixir
- Elm
- Erlang
- PHP
- Python
- Rust
また、個人的にGleamの情報をCosense(Scrapbox)にまとめているので、リファレンスがてら覗いてみてください。
organizationとして管理していきたいと考えているので、編集のリクエスト等も歓迎です。
編集したい場合は自分のTwitterにDMを送るかメンションしてください。
Erlang/OTPについて
この記事ではTypeScriptユーザーに馴染み深いJavaScriptターゲットを前提として解説していきますが、ここで少しErlangとOTPについて説明します。
Erlangは可用性の高いアプリケーションを構築するためのオープンソースの動的型付けな関数型プログラミング言語です。
スウェーデンの通信会社エリクソンによって開発され社内で利用されていましたが、後にOSSとして公開されたという経緯をもっています。
Erlang VM(BEAM)というVM上で実行され、高い並列性・堅牢性を持っています。
Erlang/OTPは高い並列性・堅牢性を生かして以下のようなプロダクトで使われています。
- Discord(Elixir)
あんまり知られてないですがDiscordはfastglobal等著名なElixir向けライブラリを公開しています。
- RabbitMQ(Erlang)
OSSのメッセージキューイングシステムです。
- CouchDB(Erlang)
OSSのドキュメント指向のデータベースです。
Obsidian Livesyncのバックエンドなんかで使われています。
- Nintendo Switchプッシュ通知システム(Erlang & ejabberd)
Erlang/OTPは主に以下のような機能を備えています。
- 軽量なプロセスベースの並列処理
- プロセスの状態を自動で管理するSupervisor
- 実行中のプログラムをシャットダウンせずに入れ替えるホットスワップ
- 他のErlang VM上の関数を容易に呼び出せる
Erlangのプロセスは非常に軽く、一般的なマシン上で万単位のプロセスを起動できます。
また、Erlang VM上で動く言語はErlang以外にも以下のものが挙げられます。
- Elixir
- LFE(Lisp Flavoured Erlang)
Gleamはこれらの言語を呼び出したり、逆にElixirなどからライブラリとして使うことができます。
これらの連携方法は以下を参照してください。
OTP
OTPはErlangに付属しているライブラリ郡です。
よく使われるのは以下のモジュールです。
- プロセスを監視して自動的に再起動させたりできる
Supervisor
- 状態と振舞いを実装できる
GenServer
- 内蔵かつ堅牢なインメモリストア
Erlang Term Storage
- 同じく内蔵された分散データベース
Mnesia
Erlangの並列性を体感してみる
Erlangの並列性を感じるため、実際に動かしてみます。
ここではgleam_otp
という公式のOTPバインディングを使ってみます。
以下のコードはこちらで公開しています。
import gleam/erlang/process
import gleam/int
import gleam/io
import gleam/iterator
import gleam/list
import gleam/otp/task
import gleam/string
/// 並列に実行しない場合
fn not_parallel() {
iterator.range(0, 10)
|> iterator.map(fn(n) { sleep(n) })
|> iterator.to_list
}
/// 並列に実行する場合
fn parallel() {
iterator.range(0, 10)
|> iterator.map(fn(n) { task.async(fn() { sleep(n) }) })
|> iterator.to_list
|> list.map(fn(t) { task.await_forever(t) })
}
// Sleep処理。1秒処理を停止させる。
fn sleep(n: Int) {
io.println(string.concat(["[", int.to_string(n), "]", " Sleeping..."]))
process.sleep(1000)
}
not_parallel
を実行すると10秒かかりますが、parallel
を実行すると2秒程度で終わります。
JavaScriptでは似たような処理をするためにPromise.all
を使います。
ですが、Erlangの並列処理はこれと違いtask.asyncの時点で並列に動作するプロセスが生成されているというのが大きな違いです。
GleamとTypeScriptは何が違うのか
Gleamには以下に挙げるような構文・機能が存在しません。
- for・while文
- if文
- throw-catch(大域脱出)
- クラス
逆に、GleamにはありTypeScriptにはない構文・機能は以下になります。
- パターンマッチ
- Result型
- パイプライン
総じて以下のように考える人が向いていると考えています。
- 関数型言語の構文や静的型付けをJavaScriptでも使いたい
- 馴染みのある文法でErlang VMの能力を生かした堅牢・高可用なWebアプリケーションを作りたい
インストール方法
Gleamを実行するためにはGleamコンパイラとそれぞれのターゲットの実行環境が必要です。
インストール方法は公式サイトに書かれているので割愛します。
miseを使うとGleamコンパイラのみならず、Erlangやraber、Node.jsなどコンパイラや実行環境がまとめて手に入るのでオススメです。
GleamとJavaScript
GleamはES6に準拠したJavaScriptを出力します。
また、Gleamは珍しくNode.jsやブラウザー以外にDenoとBunの実行もサポートしています。
Denoに関しては設定ファイルgleam.toml
にて実行時権限の指定が可能です。
例えば実行にDenoを使い、--allow-net
を指定する場合は以下のように書きます。
name = "my_project"
version = "1.0.0"
licences = ["MIT"]
description = "Gleam bindings to..."
target = "javascript"
runtime = "deno"
[dependencies]
gleam_stdlib = ">= 0.18.0 and < 2.0.0"
[javascript.deno]
allow_net = true
実行方法
GleamコンパイラはRustで書かれたシングルバイナリとして配布されているので、コンパイラをダウンロードすればコードの変換は可能です。
実行するためにはErlangターゲットの場合はErlangとraber3、JavaScriptターゲットの場合はNode.jsまたはDenoとBunが必要です。
gleam new
コマンドで生成されるプロジェクトのディレクトリ構造は以下のようになっています。
.
├── gleam.toml
├── README.md
├── src
│ └── sample.gleam <- `gleam run`するとこのファイルのmain関数が実行される。
└── test
└── sample_test.gleam
import gleam/io
pub fn main() {
io.println("Hello from sample!")
}
初回実行時は依存パッケージのダウンロードも併せて行われます。
❯ gleam run
Downloading packages
Downloaded 2 packages in 0.01s
Compiling gleam_stdlib
Compiling gleeunit
Compiling sample
Compiled in 1.18s
Running sample.main
Hello from sample!
エントリポイント
TypeScriptは基本的に言語側で決められたエントリポイントは存在しません。
Gleamではgleam run
コマンドが実行されるとsrc/
配下にあるプロジェクト名.gleam
というファイルのmain
という関数が実行されます。
またgleam run -m tmp
のように、-m
オプションを付けることで実行されるモジュールを指定できます。
この場合、指定されたモジュールのmain
関数が実行されます。
個人的にはsrc/tmp.gleam
にプロトタイプを記述して挙動を確認しているのでとても重宝しています。
console.log("Hello!")
import gleam/io
pub fn main() {
io.println("Hello!")
}
標準出力
TypeScriptにはconsole.log
という関数が組込みで使えます。
Gleamでは標準出力はgleam/io
というライブラリで提供されてます。
gleam/io
ライブラリにあるio.debug
という関数はあらゆる型を受け入れ標準出力する関数で、デバッグする際非常に便利です。
またこの関数は入力された値をそのまま返すという特性を持っているためパイプラインに容易に挿入できます。
let a = 1
let b = 2
a
|> io.debug // => 2
|> int.add(b)
console.log(149)
import gleam/io
io.debug(149)
変数
Gleamではlet
キーワードで変数を束縛できます。
また、同じ変数名で変数を束縛すると上書きされます。
Gleamでは型以外大文字の使用が禁止されているため、変数は基本的に小文字のスネークケースで表記されます。
const name = "temari" // name is temari
const name = "kotone" // name is kotone
console.log(name) // "kotone"
let name = "temari" // name is temari
let name = "kotone" // name is kotone
io.debug(name) // "kotone"
定数
const
で定数を宣言できます。
Gleamのconst
はコンパイル時に確定するため、TypeScriptとは異なりconst
の右辺には関数呼び出しを含めることはできません。
また、モジュールのトップレベルでしか宣言できません。
import gleam/io
const name = "arisu"
pub fn main() {
io.debug(name) // "arisu"
}
プリミティブ型
プリミティブ型として以下のものがあります。
- String
- Int
- Bool
- Float
String
UTF-8で表現された文字列です。
<>
で文字列同士を結合できます。
\
を使うことでエスケープシーケンスが使えます。
複数行の記述は""
で可能です。
let name = "arisu"
let kiremeki = `
キラキラ夢見よう 夢を見ることに
早すぎるも遅すぎるもないから 自分らしい夢を!
`
let name = "arisu"
let kiremeki = "
キラキラ夢見よう 夢を見ることに
早すぎるも遅すぎるもないから 自分らしい夢を!
"
io.println("name is" <> name)
TypeScriptでよく使われるテンプレートリテラルはGleamには存在しません。
代わりに文字列のListを結合するstring.concat
関数を使うと似た感じで書くことができます。
もし結合する際の文字を指定したい場合はstring.join
関数を使います。
const name = "hifumi"
console.log(`${name} daisuki`)
import gleam/string
let name = "hifumi"
let tmpl = [name, "daisuki"]
string.concat(tmpl)
Int
小数を含まない数値を扱います。また、負数も束縛できます。
Erlangターゲットの場合は大きさに制限がありませんが、JavaScriptターゲットの場合はnumber
として扱われるためnumber
の制約がかかります。
const u = 149
const n = -3
let u = 149
let n = -3
コレクション型
以下のコレクション型があります。
- List
- Tuple
- Dict
List
順序付けされた値が格納される型です。全ての要素が同じ値でないといけません。
また単一リストで作られており、先頭から要素を追加したり削除したりする処理が非常に非効率な特徴があります。
インデックスに直接アクセスするような処理は非常に非効率なため推奨されておらず、Gleam標準ライブラリv0.38.0からListのインデックスにアクセスする関数list.at
が削除されています。
そのようなケースではiteratorの方が向いてるでしょう。
const even = [2, 4, 6, 8, 10]
const words = ["hello", "world"]
let even = [2, 4, 6, 8, 10]
let words = ["hello", "world"]
Tuple
型の異なる値をまとめて扱える型です。
このようなケースではカスタム型を作った方が良いため、あまり使われるケースはありません。
const arisu: [string, number] = ["arisu", 12]
let arisu: #(String, Int) = #("arisu", 12)
Dict
Key Value形式で値をまとめて扱えます。
それぞれの要素の型は全て同じである必要があります。
また、Dict型はタプルのリストから作るケースが多いです。
その際はdict.from_list
関数を使います。
const arisu = {name: "arisu", age: 12}
let arisu = dict.from_list([#("name", "arisu"), #("age", "12")])
let arisu = dict.from_list([#("name", "arisu"), #("age", 12)]) // => `12`はInt型なのでコンパイルエラーになる。
その他の型
その他の型として以下のものがあります。
- Nil
- Result
Nil
TypeScriptでいうundefined
やvoid
のような型です。
Gleamは全ての関数が値を返す必要があるため、返す値がない場合はNilが返されます。
console.log("Hello") // -> undefined
io.println("Hello") // -> Nil
Result
Resultは失敗する可能性のある処理を行う関数が返す型です。
TypeScriptはResult
型が存在しないので、ts-resultsを使っています。[1]
import { Ok, Err, Result } from 'ts-results';
function iseven(n: number): Result<number, string> {
if (n % 2 == 0) {
return Ok(n)
} else {
return Err("Not even.")
}
}
iseven(2) // t { ok: true, err: false, val: 2 }
iseven(5) // t { ok: false, err: true, val: "Not even.", _stack: "..." }
fn iseven(n: Int) -> Result(Int, String) {
case n % 2 {
0 -> Ok(n)
_ -> Error("Not even.")
}
}
iseven(2) // Ok(2)
iseven(5) // Error("Not even.")
型変換(キャスト)
Gleamは型変換が言語仕様として存在しないので、標準ライブラリを使って変換します。
また、TypeScriptには小数を扱う専用の型が存在しないのでFloatのサンプルコードは省略しています。
Int -> String
String(149) // "149"
import int
int.to_string(149) // => "149"
String -> Int
Number(149) // => "149"
Number("arisu") // => NaN
import int
int.parse("149") // => Ok(149)
int.parse("arisu") // => Error(Nil)
String -> Float
import float
float.parse("14.9") // => Ok(14.9)
float.parse("arisu") // => Error(Nil)
Float -> Int
import float
float.round(14.9) // => 15
Float -> String
import float
float.to_string(14.9) // => "14.9"
Bool -> String
String(true) // => "true"
String(false) // => "true"
import bool
bool.to_string(True) // => "True"
bool.to_string(Bool) // => "Bool"
Bool -> Int
Number(true)
Number(false)
import bool
bool.to_int(True) // => 1
bool.to_int(False) // => 0
カスタム型
type
キーワードで型を定義できます。型定義はモジュールのトップレベルに記述する必要があります。
let
やconst
、fn
キーワードと同様にpub
キーワードを使って外部に公開できます。
Gleamの型定義はTypeScriptと違って、型の中にバリアントを含む構造をとっています。
また、バリアントの要素は.
でアクセスでき、バリアントそのものがコンストラクタになっています。
このコンストラクタさえあればどこでも型の実体を生成できます。
ですが、プリミティブ型に何らかの制約を課している型(e.g. メールアドレスを定義した型など)を作りたい場合などはコンストラクタを自分で定義したくなるケースがあります。そのような場合に使えるのがopaque
です。
opeque
は不透明型と言って、型自体は公開されているものの、そのバリアントを自ら呼び出せないよう制限されている型を指します。
詳しくは以下の記事で解説しています。
ちょっと紛らわしい[2]ですが、typeで宣言しているIdol
とバリアントのIdol
は全く異なるものです。
import
で型を指定する際には以下のようにimportします。
-
type
で宣言している型
import modname.{type TypeName}
でimportします。 -
バリアント
import modname.{VariantName}
でimportします。
interface Idol {
name: string
age: number
}
type Idol {
Idol(name: String, age: Int)
}
const idol: Idol = {name: "arisu", age: 12}
let idol: Idol = Idol("arisu", 12) // バリアント名で型の実体を生成している。
idol.name // => "arisu"
idol.age // => 12
カスタム型はcase
にてパターンマッチを行えます。
これを使うことで状態による条件分岐をスッキリと記述できます。
pub type State {
Continue
Shutdown
State(val: Int)
}
let state = State(149)
case state {
State(val) -> io.println("state: " <> int.to_string(val))
Continue -> io.println("Continue")
Shutdown -> io.println("Continue")
}
let state = Continue
case state {
State(val) -> io.println("state: " <> int.to_string(val))
Continue -> io.println("Continue")
Shutdown -> io.println("Continue")
}
四則演算
TypeScriptでも使えるような演算子がGleamでも使えます。
Gleamは他の言語と異なりゼロ除算で0を返します。
これに疑問を感じる方が多いと予想しているので、詳しい説明を以下折り畳みにて書いておきました。
興味があったらぜひ読んでみてください。
なぜGleamは0を返すことを選んだのか
Gleam 0.13のリリースノートのType safe divisionの項目にはこのように書かれています。
Gleam aims to be an exception free language, and the assert keyword is intended as being the only way to crash a process. Errors should be represented by the type system, so it is not in keeping with the design and goals of the language for division to sometimes crash when dividing numbers.
Languages such as JavaScript that follow IEEE 754 return an Infinity value when dividing by zero, which may be positive or negative. This is a familiar solution to many programmers, and it fits well with Gleam’s “never implicitly crash” goal.
Unfortunately there is no Infinity value on the Erlang virtual so Gleam would have to implement this. While possible this would cause problems with Erlang and Elixir interop- we could no longer safely pass Gleam numbers to Erlang functions as they may be this special Infinity value, which would likely cause a crash. The same problem would occur when calling Gleam code from Erlang or Elixir, resulting in adding Gleam libraries to your Erlang or Elixir application being less appealling.
邦訳すると以下のようになります。
Gleamは例外のない言語を目指しており、assertキーワードはプロセスをクラッシュさせる唯一の方法であることを目的としています。
エラーは型システムで表現される必要があるため、数値を除算するときに時々クラッシュすることは除算の言語設計と目標に沿っていません。
IEEE754に準拠するJavaScriptなどの言語はゼロで除算すると正または負の無限値を返します。
これは多くのプログラマーにとって馴染みのある挙動であり、Gleamの「暗黙的にクラッシュしない」という目標によく適合します。
残念ながら、Erlang仮想にはInfinity値がないためGleamはこれを実装する必要があります。
これにより、ErlangとElixirの相互運用性で問題が発生する可能性があります。
Gleamの整数型はこの特別なInfinity値である可能性があるため、Erlang関数に安全に渡すことができなくなりクラッシュが発生する可能性があります。
ErlangまたはElixirからGleamコードを呼び出すときにも同じ問題が発生し、その結果ErlangまたはElixirアプリケーションにGleamライブラリを追加する魅力が低下します。
とあるように、GleamではErlangやElixirなどの言語との相互運用性と、「例外が起こらない言語」を目指す目標を加味した結果このような選択をしました。
700 + 31
200 - 51
72 * 2
24 / 2
700 + 31
200 - 51
72 * 2
24 / 2
小数の演算は別で、専用の演算子が用意されています。
100.0 + 1.8
100.0 - 1.8
100.0 * 1.8
100.0 / 1.8
100.0 +. 1.8
100.0 -. 1.8
100.0 *. 1.8
100.0 /. 1.8
比較
比較に関しても基本的にTypeScriptと同じ演算子が使えます。
四則演算子と同じように小数は専用の演算子が存在します。
等号だけは例外で、全ての型で同じ演算子が使えます。
100 < 200
100 <= 200
30.0 > 50.0
30.0 >= 50.0
100 == 200
1.0 == 1.2
100 < 200
100 <= 200
30.0 >. 50.0
30.0 >=. 50.0
100 == 200
1.0 == 1.2
パターンマッチ
Gleamには強力なパターンマッチが存在します。
これはTypeScriptのswitch
などと違い、リストの中身などでも条件分岐を行えます。
また複数の値によるマッチングも行えます。
また以下のケースではコンパイルエラーになります。
- 全てのパターンを網羅していない。
- 戻り値の型が一致しない。
_
は全ての値にマッチします。
また、左辺に変数を配置することでそのパターンに合致した部分の値を変数として使えます。
パターンマッチの網羅性
パターンマッチが網羅されていないとエラーが発生します。
const n = 100
if (n == 100) {
console.log("Number is 100")
} else if (n == 200) {
console.log("Number is 200")
} else if (n == 300) {
console.log("Number is 300")
}
case 100 {
100 -> io.println("Number is 100")
200 -> io.println("Number is 200")
300 -> io.println("Number is 300")
} // => 全てのパターンが記述されていないためコンパイルエラーになる。
逆に、パターンが網羅されていればコンパイルが通ります。
const n = 100
if (n == 100) {
console.log(100)
} else {
console.log(n * 2)
}
また、パターンマッチの一部に変数を置いてマッチした値を使えます。
case 100 {
100 -> io.debug(100)
number -> io.debug(number * 2)
} // => 全てのパターンが網羅されているためコンパイルできる
パターンマッチと型の同一性
パターンマッチは右辺全てが同じ型を返さないとコンパイルエラーになります。
const n = 100
if (n == 100) {
console.log("Number is 100")
} else {
console.log(n * 2)
}
これは左辺に変数を割り当てている場合でも同じです。
case 100 {
100 -> io.debug("Number is 100") // String
number -> io.debug(number * 2) // Int
} // => 右辺の戻り値の型が一致しないためコンパイルエラーになる。
const n = 100
if (n == 100) {
console.log("Number is 100")
} else {
console.log("Number is not 100")
}
パターンマッチとワイルドカード
_
を用いると全てのパターンを記述できます。
const n = 100
if (n == 100) {
} else {
console.log("Number is not 100")
}
case 100 {
100 -> io.println("Number is 100")
_ -> io.println("Number is not 100")
} // => 全てのパターンが記述されているためコンパイルできる
case ["user", "arisu"] {
["user", name] -> io.println("username is " <> name)
_ -> io.println("username is not found.")
} // => リストもパターンマッチ可能。変数を置くことでその値を使える。
例外処理
GleamではエラーをResult
という型で表現します。
Result
は正常な場合のOk
とエラーのError
の2つになりえます。
fn iseven(n: Int) -> Result(Int, String) {
case n / 2 {
0 -> Ok(n)
_ -> Error("Not even.")
}
}
Result
から値を取り出すには以下の方法があります。
let assert
最も簡単に値を取り出せます。値がError
だった場合は実行時エラーが発生します。
let assert Ok(n) = iseven(8)
パターンマッチ
一番オーソドックスな方法です。
やや冗長になりやすい所があります。
case iseven(8) {
Ok(n) -> io.println(int.to_string(n) <> "is even.") // => "8 is even."
Error(err) -> io.println(err) // => "Not even."
}
result.try
Result
の値がOk
だった場合、コールバックが実行されます。Error
だった場合はそのError
が返されます。
特にResult
な値に依存している値を処理したい場合に効果を発揮します。
また内部でcase
を使っているため、case
のように戻り値の型は同じでなくてはなりません。
import gleam/result
result.try(iseven(5), fn (n) { Ok(int.to_string(n) <> "is even.") })
result.try(iseven(5), fn (n) {
result.try(iseven(n / 3), fn (n) {
Ok(n)
})
})
use
コールバックを展開して記述できる構文糖です。
上記のresult.try
を用いたサンプルコードは少し読みづらいですが、この構文を用いるとスッキリと記述できます。
use n <- result.try(iseven(5))
use n <- result.try(iseven(n / 3))
Ok(n)
use
についての詳しい解説は以下の記事を参照してください。
関数
fn
キーワードで関数を定義できます。
pub
キーワードを付けると外部のモジュールから呼び出せる関数を定義できます。
また、Gleamの関数は巻き上げ(ホイスティング)されるため、同一モジュール内ならどこで宣言されていても呼び出せます。
Gleamにおける型注釈はオプションなので、付けなくてもコンパイルは通りますし自動的に型推論されコンパイル時に型が検証されます。
ですがコンパイルエラーが分かりやすくなったり、gleam docs build
で生成されるドキュメントに型情報が載ったりするので極力書くのをおすすめします。
function fizzbuzz(n: number): string {
const mod = n % 15
if (mod == 0) {
console.log("FizzBuzz")
} else if (mod == 3 || mod == 6 || mod == 9 || mod == 12) {
console.log("Fizz")
} else if (mod == 5 || mod == 10) {
console.log("Buzz")
}
}
fn fizzbuzz(n: Int) -> String {
case n % 15 {
0 -> "FizzBuzz"
3 | 6 | 9 | 12 -> "Fizz"
5 | 10 -> "Buzz"
}
}
ラムダ関数(無名関数)
fn () {}
でラムダ関数を定義できます。
Gleamの関数は一級関数なので変数に束縛可能です。
const greet = (name: string => `Hello! ${name}!`)
greet("arisu") // => Hello! arisu!
let greet = fn (name) {"Hello! " <> name <> "!"}
greet("arisu") // => Hello! arisu!
即時関数
GleamではJavaScriptでおなじみの即時関数も使えます。
(()=> {console.log("Hello!")})()
fn () {io.println("Hello!")}()
ですが、このようなケースではブロック構文を使った方が良いでしょう。
ブロック構文では複数の式をまとめて実行しその返り値を変数に束縛できます。
let _ = {
io.println("Hello!")
}
パイプライン
Gleamには連続する関数の呼び出しをスッキリと記述できるパイプライン演算子があります。
パイプライン演算子を使って関数を呼び出すと、左辺の関数の戻り値が右辺の関数の第一引数に渡されます。
const add = (a: number, b: number) => a + b
const mul = (a: number, b: number) => a * b
const tmp = add(1, 2)
const n = mul(tmp, 3)
let add = fn (a, b) {a + b}
let mul = fn (a, b) {a * b}
let n = add(1, 2) |> mul(3)
関数キャプチャ
Gleamには関数キャプチャという構文があります。
これを使うと、いわゆるcurryingのように与えられていない引数を受け取る新たな関数を作れます。
const add = (a: number, b: number) => a + b
const add1 = (a: number) => add(a, 1)
add1(2) // => 3
引数を_
にして値を与えないことで関数がキャプチャされ、新たな関数add1
が束縛されます。
let add = fn (a: Int, b: Int) { a + b }
let add1 = add(a, _)
add1(2) // => 3
ジェネリクス
Gleamはジェネリクスをサポートしており、柔軟な型付けが行えます。
function chooseRandomly<T>(a: T, b: T): T {
return Math.random() <= 0.5 ? a : b
}
import gleam/int
import gleam/bool
fn choose_randomly(a: t, b: t) -> t {
use <- bool.guard(when: int.random(10) >= 5, return: a)
b
}
Gleamの型変数はTypeScriptと異なり、命名規則さえ守っていればどんな名前でもコンパイルが通ります。
例えば以下のような型変数も可能です。
import gleam/int
import gleam/bool
fn choose_randomly(a: hifumi_daisuki, b: hifumi_daisuki) -> hifumi_daisuki {
use <- bool.guard(when: int.random(10) >= 5, return: a)
b
}
モジュール
import
でモジュールを読み込めます。
モジュールのパスは全てsrc/
からの絶対パスになっています。
.
├── gleam.toml
├── manifest.toml
├── README.md
├── src
│ ├── sample.gleam
│ └── sub.gleam
└── test
└── sample_test.gleam
pub fn greet(name) {
"Hello! " <> name <> "!"
}
import gleam/io
import sub
pub fn main() {
io.println(sub.greet("arisu"))
}
外部関数
GleamはErlangとJavaScriptに変換されて実行されます。
そのため、Gleamからそれらターゲットの言語の関数や機能を扱いたい場合が往々にしてあります。
関数定義の上の行に@external
から始まるアノテーションを付けると、Gleam外部の関数を呼び出せます。
書式は以下の通りです。
JavaScriptターゲットの場合はこのように呼び出します。
デフォルトの実行先がNode.jsなため、ファイル名の拡張子は.mjs
にする必要があります。
@external(ターゲット, "ファイル名", "呼び出す関数名")
@external(javascript, "./ffi.mjs", "now") // ffi.mjsのnow関数を呼び出す
Erlangターゲットの場合はこのように呼び出します。
@external(ターゲット, "モジュール名", "呼び出す関数名")
@external(erlang, "calendar", "local_time") // calendar:local_time/0を呼び出す
@external(javascript, "./ffi.mjs", "now")
.
├── gleam.toml
├── manifest.toml
├── README.md
├── src
│ ├── ffi.mjs
│ ├── sample.gleam
│ └── sub.gleam
└── test
└── sample_test.gleam
パッケージ
Gleamでは他のユーザーが作成したパッケージを簡単に使えます。
Gleamのパッケージはhex.pmという、Erlang VM上で動く言語全てが共有しているパッケージレジストリにて公開されています。
プロジェクトに依存を追加するにはgleam add
コマンドを使います。
ここでは試しに、ANSIエスケープシーケンスを提供しているgleam_community_ansi
を追加してみます。
gleam add gleam_community_ansi
import gleam/io
import gleam_community/ansi
pub fn main() {
ansi.bg_pink(ansi.black("Hello Gleam!"))
|> io.println
}
こんな感じで表示されたら成功です。
Gleamパッケージの探し方
Gleam PackagesというHex.pmのGleamパッケージのみを表示してくれるサイトがあるので、自分は専らそこから探してきています。
また、Awesome GleamというGleamで評価の高いパッケージなどが集められているリストがあるので、そこから探すのもオススメです。
GleamにはDiscordコミュニティがあり、sharingチャンネルでは毎日ライブラリの更新情報や記事の投稿などがシェアされています。
新しいパッケージの情報がよく見つかるので自分も毎日覗いてます。
寄付について
Gleamのブログやリポジトリを見ると必ず最後にスポンサーの名前が載っています。
GitHub sponsers経由でGleamを開発しているLouis Pilfoldさんへ寄付ができるので、もしGleamのことを気に入ったのならぜひ寄付してみてください。
ちなみに毎月寄付をしていると先述したスポンサー一覧に名前が載ります。
自分も寄付しているのでぜひ探してみてください。
参考文献
Discussion