ElixirプロジェクトでGleamとElixirを相互に呼び出してみる
ついにGleamがversion 1に到達しました!🎉
そんな訳で、今後ますます成長していくであろうGleamと今海外でアツイらしいElixirを連携させる方法を紹介していきます。
GleamについてはこのGleam Language Tourが対話的になってて分かりやすいです。
また、GleamプロジェクトからElixirライブラリを使う方法はこちらの記事を参照してください。
追記や他の日本語の資料など
3/10 追記:
有志の方が日本語訳を公開してくださっているそうです。
Gleam Language Tour(日本語版)
日本語の情報が見たい人はここらへんを参照してください。
GleamとElixirを連携させる
下準備
さて、本題のElixirの連携をしていきます。
予めGleamとElixirをインストールしておいてください。
ElixirのインストールにはKiexを使うのがオススメです。
まず始めにGleamプロジェクトを作成します。
ドキュメントではElixirプロジェクトを先に作成していますが、後でGleamプロジェクトも作成するのと後からGleamプロジェクトを作成すると面倒なことになるので先に作っちゃいます。
# Gleamプロジェクトを作成
# gleam new proj_nameでもOK
mkdir proj_name
gleam new .
cd proje_name
# Elixirプロジェクトを作成
# 途中既にあるREADMEを上書きするかどうか聞かれるので、適当に答えちゃってください。
mix new .
するとこんな感じでディレクトリが出来ていると思います。
.
|-- README.md
|-- _build
|-- build
|-- deps
|-- gleam.toml
|-- idols.json
|-- lib
| `-- ex_mix_gleam.ex ←プロジェクト名によって変わる
|-- manifest.toml
|-- mix.exs
|-- mix.lock
|-- src
| `-- ex_mix_gleam.gleam ←プロジェクト名によって変わる
`-- test
lib/
とsrc/
の2つのディレクトリが確認出来たら次はmix.exs
を編集します。
mix.exsを開くとこんな感じになっていると思うので、
defmodule Tmp.MixProject do
use Mix.Project
def project do
[
app: :tmp,
version: "0.1.0",
elixir: "~> 1.15",
start_permanent: Mix.env() == :prod,
deps: deps()
]
end
# これより下にも設定が続く
end
project
ブロック内のリスト([]
)の中身を以下の様に書き変えます。
defmodule ExGleamWithFloki.MixProject do
use Mix.Project
@app : # プロジェクト名(projectのapp:にもともとあった文字列)
def project do
[
app: @app,
version: "0.1.0",
elixir: "~> 1.15",
start_permanent: Mix.env() == :prod,
deps: deps(),
archives: [mix_gleam: "~> 0.6.2"],
compilers: [:gleam] ++ Mix.compilers(),
aliases: [
# Or add this to your aliases function
"deps.get": ["deps.get", "gleam.deps.get"]
],
erlc_paths: [
"build/dev/erlang/#{@app}/_gleam_artefacts",
# For Gleam < v0.25.0
"build/dev/erlang/#{@app}/build"
],
erlc_include_path: "build/dev/erlang/#{@app}/include",
# For Elixir >= v1.15.0
prune_code_paths: false,
start_permanent: Mix.env() == :prod
]
end
# 他の設定はそのままにしておく
# Run "mix help deps" to learn about dependencies.
defp deps do
[
{:gleam_stdlib, "~> 0.36.0"}, # Gleamの標準ライブラリ
{:gleeunit, "~> 1.0"}
]
end
end
次に、以下のコマンドを実行してください。
mix deps.get
gleam add gleam_stdlib
その後に
iex -S mix
を実行してみてください。
恐らくElixirのシェルが起動するはずです。
GleamからElixirのコードを呼び出す
まず始めにGleamからElixirの関数を呼び出してみます。
Gleamで外部の関数を呼び出すには@external
キーワードを使います。
@external
キーワードの書式は以下の通りです。
- Erlangの場合
@external(erlang, "対象の関数が存在しているモジュール名。", "対象の関数名")
// 例
@external(erlang, "io", "format")
fn format(str: String) -> Nil
- Elixirの場合
@external(erlang, "対象の関数が存在しているモジュール名。必ずElixirから始める。", "対象の関数名")
// 例
@external(erlang, "Elixir.IO", "puts")
pub fn puts(strs: List(String)) -> Nil
- JavaScriptの場合
@external(javascript, "対象のJavaScriptファイル名")
// 例
@external(javascript, "fetch.ffi.js")
fn fetch(req: Request) -> Responce
@external以下の関数宣言は全て共通です。
// 関数を公開する場合はpubを付ける
pub fn 関数名(引数) -> 戻り値
fn 関数名(引数) -> 戻り値 {
// Gleam実装でフォールバックする場合はここに実装を記述
}
ちなみに、Gleamは@external
に付けられた型付けを完全に信用するので、不正確な型付けを行なうと実行中にクラッシュする恐れがあります。
必要ならgleam/dynamicパッケージを使ってDynamic型のまま任意のタイミングで値を変換するのも手です。Dynamicについても後々記事を書きたいですね...
ElixirからGleamのコードを呼び出す
次はElixirからGleamの関数を呼び出してみます。
-
src/
ディレクトリ内にproj_name.gleam
-
lib/
ディレクトリ内にproj_name.ex
-
Elixir
のプロジェクト名がProjName
だとするとこんな感じになります。以下はFizzBuzzのサンプルコードです。
import gleam/int
pub fn fizzbuzz(num: Int) -> String {
case num % 15 {
0 -> "FizzBuzz"
3 | 6 | 9 | 12 -> "Fizz"
5 | 10 -> "Buzz"
_ -> int.to_string(num)
}
}
defmodule ProjName do
def fizzbuzz do
Range.to_list(1..30)
|> Enum.map(fn n -> :proj_name.fizzbuzz(n) end)
end
end
:proj_name.fizzbuzz(n)
の箇所がElixirからGleamを呼び出している所です。
実はこれ、ElixirからErlangを呼び出す方法と同じです。
というのも、mix_gleamはGleamプログラムを一旦Erlangプログラムに変換しているからです。[1]
なので、Elixirから見たGleamファイルは実質Erlangプログラムだったりします。[2]
また、この方法でgleam shell
で実行されるErlang Shellからプログラムの関数を直接呼び出せたりします。
> io:format(proj_name:fizzbuzz(1)).
1ok
Gleamプロジェクトに依存を追加したい
下準備でElixirプロジェクトとGleamプロジェクトの両方にgleam_stdlib
を追加したように、
Gleamプロジェクトに新たな依存を追加するとElixirプロジェクト側にも追加する必要が出てきます。(地味に面倒なのでなんとかしたい)
最後に、ElixirのFlokiを使ってGleamでHacker Newsのヘッドラインを取得するサンプルを書いたのでぜひ参考にしてください。
下のツイートにも書いてありますが、GleamでHTMLをパースするライブラリはすでにあるのでただGleamでHTMLをパースしたい人はそちらを使ったほうが良いと思います。
Discussion