LoginSignup
21
1

More than 1 year has passed since last update.

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

Posted at

本記事は、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側で全て把握して定義する必要があります。引数の数や型が合わない場合、コンパイルエラーではなくランタイムエラーとなります。

実行してみる

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

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

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

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

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

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

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ファイルで定義した関数を、外部関数として実行する定義を忘れずに。

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を触っていくぞ!!!

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

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

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

21
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
21
1