In-project Elixir support。ElixirとGleamの相互運用に期待したい

本記事は、[Elixir Advent Calendar 2022](https://qiita.com/advent-calendar/2022/elixir)のカレンダー1の18日目の記事となっております。

なお、前日の17日の記事は、@pojiro さんの「[behaviour を使ってできること](https://qiita.com/pojiro/items/84b059f4fa9b69569861)」でした。
behaviourのメリット、特に「**実装の切替**」を読んでて、「おぉぉー」と唸りました。
うーん、しゅごい（小並感

話は変わりますが、**Elixirのカレンダーは「15」** まであるんですね。
さすが、**Elixir生誕10周年**の年、盛り上がっていますね！
この勢いで、もっともっと普及していって欲しいなぁ。

## ElixirとGleam

本題。

私は、[Gleam](https://github.com/gleam-lang/gleam)という関数型プログラム言語に注目しています。
「ElixirのアドベントカレンダーなのになぜGleamのお話？」と思ってページを移動しようとされた方、少々お待ちください。
このGleamという言語、実はElixirととても親和性があるのです。[^1]

[^1]:余談ですが、Elixirの生みの親である[José Valim](https://github.com/josevalim)氏やElixirのデプロイ先の候補の一つであるアプリケーションプラットフォーム[Fly.io](https://fly.io/)がGleamの**GitHub Sponsors**になっていたりします。

例えば、Elixirに非常に似た形でコードが書けたり、ErlangVM上で動作させることができたりができます。
加えて、Elixirの関数を外部関数として実行できたり、Gleamのプロジェクト内にexファイルがあった場合は自動的にコンパイルされたり、といったことも可能です。

今回は、後者の「**Elixirの関数を外部関数として実行**」「**exファイルがGleamのプロジェクト内にあった場合は自動的にコンパイルされる**」を紹介します。

### Elixirの関数を外部関数として実行

Gleamのコードの関数内で、Elixirのモジュール名と関数名を指定します。
コード例としては以下。

```gleam
pub external fn inspect(a) -> a =  "Elixir.IO" "inspect"
```

`external fn`と外部関数を呼び出す関数として定義し、呼び出されるElixirのモジュール名と関数名を指定します。
上記の例で言うと、`"Elixir.IO" "inspect"`が外部関数にあたります。
これはElixirの標準モジュール「**IO**」中の関数「**inspect**」を呼び出す定義になります。[^2]

[^2]: Elixirで作られたbeamファイルは「`Elixir.モジュール名.beam`」というファイル名になる。

なお、Elixir側の関数に渡される引数は、Gleam側の外部関数として定義した際の引数がそのまま渡されます。そのため、Elixir側で利用する関数に必要な引数は、呼び出し元であるGleam側で全て把握して定義する必要があります。引数の数や型が合わない場合、コンパイルエラーではなくランタイムエラーとなります。

#### 実行してみる

以下のようなコードを作成して、実行。

```gleam:src/call_ex.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のコンパイルと一緒にコンパイルできるようになりました。

[^3]: https://gleam.run/news/gleam-v0.24-released/

プロジェクト構成は以下。
一部ファイルは記載を省略。

```
tree
.
├── src
│   ├── call_ex.gleam
│   └── uramasu.ex

```

このように、exファイルを`src`フォルダ以下に配置しておくのが前提。

なお、`src/uramasu.ex`の中身は、以下。
上から順番に、「文字列を返す」「加算式」「フィボナッチ数算出」「rot13を解く」を関数として定義しています。

```elixir:src/uramasu.ex
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ファイルで定義した関数を、**外部関数**として実行する定義を忘れずに。

```gleam:src/call_ex.gleam
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を触っていくぞ！！！