本記事は、Elixir Advent Calendar 2022のカレンダー1の18日目の記事となっております。
なお、前日の17日の記事は、@pojiro さんの「behaviour を使ってできること」でした。
behaviourのメリット、特に「実装の切替」を読んでて、「おぉぉー」と唸りました。
うーん、しゅごい(小並感
話は変わりますが、Elixirのカレンダーは「15」 まであるんですね。
さすが、Elixir生誕10周年の年、盛り上がっていますね!
この勢いで、もっともっと普及していって欲しいなぁ。
ElixirとGleam
本題。
私は、Gleamという関数型プログラム言語に注目しています。
「ElixirのアドベントカレンダーなのになぜGleamのお話?」と思ってページを移動しようとされた方、少々お待ちください。
このGleamという言語、実はElixirととても親和性があるのです。1
例えば、Elixirに非常に似た形でコードが書けたり、ErlangVM上で動作させることができたりができます。
加えて、Elixirの関数を外部関数として実行できたり、Gleamのプロジェクト内にexファイルがあった場合は自動的にコンパイルされたり、といったことも可能です。
今回は、後者の「Elixirの関数を外部関数として実行」「exファイルがGleamのプロジェクト内にあった場合は自動的にコンパイルされる」を紹介します。
Elixirの関数を外部関数として実行
Gleamのコードの関数内で、Elixirのモジュール名と関数名を指定します。
コード例としては以下。
pub external fn inspect(a) -> a = "Elixir.IO" "inspect"
external fn
と外部関数を呼び出す関数として定義し、呼び出されるElixirのモジュール名と関数名を指定します。
上記の例で言うと、"Elixir.IO" "inspect"
が外部関数にあたります。
これはElixirの標準モジュール「IO」中の関数「inspect」を呼び出す定義になります。2
なお、Elixir側の関数に渡される引数は、Gleam側の外部関数として定義した際の引数がそのまま渡されます。そのため、Elixir側で利用する関数に必要な引数は、呼び出し元であるGleam側で全て把握して定義する必要があります。引数の数や型が合わない場合、コンパイルエラーではなくランタイムエラーとなります。
実行してみる
以下のようなコードを作成して、実行。
import gleam/io
pub fn main() {
inspect([1, 2, 3])
}
// Elixir modules start with `Elixir.`
pub external fn inspect(a) -> a =
"Elixir.IO" "inspect"
これを、gleam run
で実行してみる。
結果は以下。
[1, 2, 3]
IO.inspect/1
は、引数をそのまま返す関数ですから、Gleam側のinspect関数で渡した値[1,2,3]
が表示されています。
exファイルがGleamのプロジェクト内にあった場合は自動的にコンパイルされる
Gleamの v0.24.0 から追加された機能3です。
Gleamのプロジェクト配下にElixirのソースコード(exファイル)がある場合、Gleamのコンパイルと一緒にコンパイルできるようになりました。
プロジェクト構成は以下。
一部ファイルは記載を省略。
tree
.
├── src
│ ├── call_ex.gleam
│ └── uramasu.ex
このように、exファイルをsrc
フォルダ以下に配置しておくのが前提。
なお、src/uramasu.ex
の中身は、以下。
上から順番に、「文字列を返す」「加算式」「フィボナッチ数算出」「rot13を解く」を関数として定義しています。
defmodule Uramasu do
def hello do
"world"
end
def add_elixir(a, b) do
a + b
end
def fib_ex(0), do: 0
def fib_ex(1), do: 1
def fib_ex(x) when (x >= 2) do
fib_ex(x-1) + fib_ex(x-2)
end
def encode(string, shift \\ 13)
def encode(<<char>> <> tail, shift) do
<<rotate(char, shift)>> <> encode(tail, shift)
end
def encode(_, _), do: ""
def rotate(char, shift) when ((char >= ?a) and (char <= ?z)) do
?a + rem(char - ?a + shift, 26)
end
def rotate(char, shift) when ((char >= ?A) and (char <= ?Z)) do
?A + rem(char - ?A + shift, 26)
end
def rotate(char, _), do: char
end
そして、それらを呼び出すGleam側の処理はこちら。
exファイルで定義した関数を、外部関数として実行する定義を忘れずに。
import gleam/io
import gleam/int
pub fn main() {
inspect([1,2,3])
let aisatu = hello()
io.println(aisatu)
let tasizan = add(10, 20)
io.println(int.to_string(tasizan))
let fibonacci_num = fib(10)
io.println(int.to_string(fibonacci_num))
let words = "V ybir ryvkve"
io.println(rot13(words, 13))
}
// Elixir modules start with `Elixir.`
pub external fn inspect(a) -> a =
"Elixir.IO" "inspect"
pub external fn hello() -> String =
"Elixir.Uramasu" "hello"
pub external fn add(a: Int, b: Int) -> Int =
"Elixir.Uramasu" "add_elixir"
pub external fn fib(x: Int) -> Int = "Elixir.Uramasu" "fib_ex"
pub external fn rot13(words: String, shift: Int) -> String = "Elixir.Uramasu" "encode"
実行結果はこちら。
[1, 2, 3]
world
30
55
I love elixir
Gleamは、型指定で厳密な部分があったりで、柔軟な書き方ができない場合があります。
その際に、Elixirでチョチョッとコードを書いてGleam側で外部関数として呼び出す、なんて方法で「やりたいこと」が実現できる選択肢が取れるというと言うことですね。
まとめ
純粋なElixirの話ではなくて、ごめんなさい。
ただ、このように他の言語からElixirを呼び出すことができるようになっていると言うのは、Elixirの有効性故なのでは、と考えています。
Elixirすごい!!
来年はもっとElixirを触っていくぞ!!!
-
余談ですが、Elixirの生みの親であるJosé Valim氏やElixirのデプロイ先の候補の一つであるアプリケーションプラットフォームFly.ioがGleamのGitHub Sponsorsになっていたりします。 ↩
-
Elixirで作られたbeamファイルは「
Elixir.モジュール名.beam
」というファイル名になる。 ↩