#まえがき
どういう訳か小生の回り(電車で1時間30分の範囲)では、Elixirが熱い!そして Nervesが熱い!!
そんな訳でElixirを触ってはみるものの……組込系を生業としているが故に、Functional×パイプの特色にも、WebフレームワークのPhoenixにも、はたまたエコシステムのNervesHubにも、いまのところ「あ、そう。よかったね」程度の感想しか湧かないのである。Elixirの雰囲気を一通り味見した今、興味は専ら他言語拡張機能NIFs(Native Implemented Functions)に向かってしまうのである。
本稿は、そんな異端な記事なので、まっとうなElixir使いやよい子は真似しないように…
#お題目
さてNIFsで何を作ろうか?
ふむ、どうせ異端ならば、immutable/参照透過性を良しとする関数型言語の禁を破り、mutableなデータ構造fifo[*1]を実装してみよう
= FIFOモジュールの仕様 =
1. FIFO.create() : 新たにfifoを一つ作り、そのハンドルを %FIFO{handle: h} で返す
2. FIFO.front(%FIFO) : fifoの頭の要素を覗き見て返す。fifoが空の場合はnilを返す
3. FIFO.get(%FIFO) : fifoの頭の要素を取り外して返す。fifoが空の場合はnilを返す
4. FIFO.put(%FIFO, item): fifoのお尻にitemを追加する
5. FIFO.count(%FIFO) : fifoの中の要素の個数を返す
6. FIFO.clear(%FIFO) : fifoを空にする
こんなところかな…
[*1]実は、Erlangには double-ended fifo queueを実装した queueモジュールが用意されている。Elixirからは:queue.ほにゃらら
で利用できるのだ。この queueの実装は「"Purely Functional Data Structures" by Chris Okasaki」に載っているものと同等かな?
#めあて
日頃、息子たちに「過ぎ去った時間は、金をいくら積んでも取り戻すことができない」と口酸っぱく言っている手前、「NIFsを書いてみました。試してみました」と戯れただけに終わると示しがつかない。 オヤジの威厳を保つために、それなりの取り組み課題を設定しておこう
課題1. "Resource objects"の使い道の理解、C++でのコードの書き方の模索
課題2. thread safeなNIFsの書き方の模索
課題3. Elixirの任意のデータをNIFsで受け取って扱う方法の模索
#起: プロジェクトを用意する
まずは、いつもの様に mixでプロジェクトを用意する。
> mix new fifo
> cd fifo
さて、NIFはC++で実装するので、C++のソースコードを入れておくディレクトリが必要になる。
Erlangの"Directory Structure Guidelines" [5] によると、ディレクトリ名は "cc_src"とするのが正統派の様だ。
しかし、いろいろと手抜きをしたいが故に Nervesの Community libraries / circuits_gpio [4] に倣って "src"としておく。
作成するNIFのC++ソースコードのファイル名は "fifo_nif.cc"としよう。
> mkdir src
> touch src/fifo_nif.cc
"fifo_nif.cc"は、g++でコンパイル/リンクする。
今回は1ファイルだけなので、エイヤッと g++でビルドしても良いのだが、後学のために makeで行う。
mixから makeを呼び出すタスクは、既にHexに登録されていて elixir_makeだ。ありがたや
elixir_makeの Usageに従って、"mix.exs"の projectと depsに下記の設定を追加する。
そして、おもむろに mix deps.get
を実行して、elixir_makeをGitHubからダウンロードしておこう。
def project do
[
︙
compilers: [:elixir_make] ++ Mix.compilers,
︙
]
end
︙
defp deps do
[
{:elixir_make, "~> 0.4", runtime: false}
]
end
> mix deps.get
ふぅむ。【起】の最後の難関は Makefileの記述だ。
ここは手抜きをして、先出の circuits_gpioの Makefileを元に、必要な変更を加え、また不要な記述を削る方針で進める。
シンボル PREFIXと BUILDは、それぞれビルドで出来上がった動的ライブラリ(.so)と、その中間ファイル(.o)の格納先ディレクトリを表している。具体的には、
- PREFIX = _build/dev/lib/fifo/priv
- BUILD = _build/dev/lib/fifo/obj
となる。これはそのまま頂こう。
動的ライブラリを作りたいので、gcc/g++ではコンパイラ・オプションに -fPIC、リンカ・オプションに -fPIC -sharedを指定するのはお約束。これらを シンボル CFLAGS, LDFLAGSの定義に追加する。
NIFsのビルドでは、Erlang由来のライブラリをリンクする必要はなく、ヘッダーファイル "erl_nif.h"さえ参照できれば良い。故に、シンボル ERL_LDFLAGSの定義の -L$(ERL_EI_LIBDIR)
は不要なのだが、実害は無さそうなので取り敢えず残しておこう。
その他、サフィックス .cを .ccに置換したり、 $(CC)"を $(CXX)に置換したりして、以下の様になった。
ふぅ…
# Makefile for building the NIF
#
# MIX_APP_PATH path to the build directory
# ERL_EI_INCLUDE_DIR include path to erlang header
# ERL_EI_LIBDIR path to erlang/c libraries (Not necessaly for NIFs)
PREFIX = $(MIX_APP_PATH)/priv
BUILD = $(MIX_APP_PATH)/obj
NIF = $(PREFIX)/fifo_nif.so
CFLAGS ?= -O2 -Wall -Wextra -Wno-unused-parameter -pedantic -fPIC
LDFLAGS += -fPIC -shared
# Set Erlang-specific compile and linker flags
ERL_CFLAGS ?= -I$(ERL_EI_INCLUDE_DIR)
ERL_LDFLAGS ?= -L$(ERL_EI_LIBDIR)
SRC = src/fifo_nif.cc
HEADERS =$(wildcard src/*.h)
OBJ = $(SRC:src/%.cc=$(BUILD)/%.o)
all: install
install: $(PREFIX) $(BUILD) $(NIF)
$(OBJ): $(HEADERS) Makefile
$(BUILD)/%.o: src/%.cc
$(CXX) -c $(ERL_CFLAGS) $(CFLAGS) -o $@ $<
$(NIF): $(OBJ)
$(CXX) -o $@ $(ERL_LDFLAGS) $(LDFLAGS) $^
$(PREFIX):
mkdir -p $@
$(BUILD):
mkdir -p $@
clean:
$(RM) $(NIF) $(BUILD)/*.o
.PHONY: all clean install
と言うことで、プロジェクトのディレクトリ構造はこうなる。
fifo/
├──_build/
│ ├── dev/
│ │ ├── lib/
│ │ ├── fifo/
│ │ ├── obj/ -- C++ソースをコンパイルしたオブジェクトファイル(.o)の格納先
│ └── priv/ -- C++ソースをコンパイル/リンクして出来た動的ライブラリ(.so)の格納先
├── deps/
├── lib/
│ └── fifo.ex -- Elixir側のインターフェイス
├── src/
│ └── fifo_nif.cc -- C++で記述したNIFのソースコード
├── test/
├── Makefile -- C++をビルドする為のメイクファイル
├── README.md
└── mix.exs
#承: FIFO NIFの実装
本題である。
FIFOの実体はオーソドックスに STL std::dequeにする。
否、出来合いの std::dequeで手抜きしたいが故にわざわざ C++でNIFを記述することにしたのだ。
手抜きなので、当然 std::dequeの Allocatorはデフォルトのままで使う。C++11以降ではthread safeになっている様だし、たぶん大丈夫だろう。手抜きにはリスクが伴うものだ
さて、FIFOの実体が std::dequeに決まったことで、考えるべきことが 2つある。FIFOの**(1)生成/破壊の制御と(2)thread safe化**だ。そう、「めあて」に掲げた課題1と課題2だ。
####(1)FIFOの生成/破壊の制御
std::dequeを使うには、その前にどこかでstd::dequeのインスタンスを生成しなきゃならない。今回は、Elixirのコードの中で自由に使いたいので、コードの文脈から独立した寿命を持つインスタンスが必要だ。つまり、ヒープメモリからアロケート(new)することになる…
おっと、厄介ごとの始まりだ。ヒープメモリからアロケートしたインスタンスは、要らなくなったら必ずデリートしなければならない。そうしなければ、メモリ・リークが発生して、遠い将来にシステムがハングアップするかも知れない。GCを持たないC/C++のエンジニア泣かせな面だ。
愚痴を言っても始まらないので、とっとと設計しよう。設計の選択肢としては2つある。
- デリート用のNIF関数を用意し、Elixir側から明示的に不要になったFIFOをデリートする
- なんらかの仕組みで不要になったFIFOを見つけ、それが自動的にデリートされるようにする
インプリメントが簡単なのは選択肢1だ。だが、依然としてメモリリークのリスクが残るうえに不細工。
では選択肢2は? "なんらかの仕組み"を Elixir/Erlangが提供してくれなきゃ始まらないが、果たしてそんな仕組みがあるのか?
…あった! "Resource objects"がそれだ。
Erlang erl_nifのリファレンスマニュアルの "Resource objects"のパラグラフにこうある。
A resource type can have a user-supplied destructor function, which is automatically called when resources of that type are released (by either the garbage collector or enif_release_resource).
ふむふむ、NIFsのロード初期化関数load()の中で、"resource type"を enif_open_resource_type()で定義する。同時に destructorを登録か。こんな感じだな。
static static ErlNifResourceType* _ResType_DEQUE; // resource type
void fifo_destroy(ErlNifEnv* env, void* ptr)
{
MyDeque* deque_ptr = reinterpret_cast<MyDeque*>(ptr);
deque_ptr->~MyDeque() //インスタンスの破壊,メモリブロックの開放はElixir/Erlangが行う
}
int load(ErlNifEnv *env, void **priv_data, ERL_NIF_TERM load_info)
{
_ResType_DEQUE = enif_open_resource_type(env, NULL, "myfifo", fifo_destroy, ERL_NIF_RT_CREATE, NULL);
return 0;
}
一方で、インスタンスの生成は?
まず "resource type"を引数に enif_alloc_resource()でメモリブロックをアロケートする。そのメモリブロックに placement newで std::dequeをインスタンス化すれば良いかな。インスタンスの寿命をElixir/Erlangに委ねるために、enif_release_resource()で参照カウントを一つ減らしておくのを忘れずに。
ERL_NIF_TERM fifo_create(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
MyDeque* deque_ptr
= new(enif_alloc_resource(_ResType_DEQUE, sizeof(MyDeque))) MyDeque; // placement new
ERL_NIF_TERM term = enif_make_resource(env, deque_ptr); // makeすると漏れなく 参照カウント=1 となる
enif_release_resource(deque_ptr); // 参照カウントを一つ減らす
return term;
}
####(2)FIFOのthread safe化
残念ながら Elixir/Erlangは、Nifs そして"Resource objects"を thread safeには扱ってくれない。
以前、マルチメディア・ライブラリSMFL(OpenGL)の NIFsを実験的に書いたことがあるが、随分と痛い目にあった。Elixir/Erlangの最下層では、数個のthreadが並行に動いており、どのthreadから何時 NIFsがcallされるかは決まっていないのだ。
ならば NIFs側で自己防衛するしかあるまい。Mutexの出番である。
erl_nifのAPIにもMutexのオペレーションが用意されているが、STLの std::mutexに比べ少々扱いが面倒なので、お手軽な std::mutex & std::lock_guardを使うことにしよう。
Mutexによる排他制御が必要なコード・ブロックは、(1)std::dequeを破壊的に書き換えるコード塊と、(2)複数のstd::dequeメソッドをアトミックに実行したいコード塊だ。たとえばこう…
ERL_NIF_TERM fifo_pop(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
<略>
// mutual exclusion after here
std::lock_guard<std::mutex> lock(deque_ptr->m_mutex);
// empty(),front(),pop_front()をアトミックに実行
if (!deque_ptr->m_deque.empty()) {
ErlNifBinary bin = deque_ptr->m_deque.front();
deque_ptr->m_deque.pop_front();
<略>
}
<略>
}
####(3)データをNIFsで受け取る方法
ここまでで、なんとか課題を2つクリアできた
残るはあと一つ…「Elixirの任意のデータをNIFsで受け取って扱う方法」だ。
はっきり言ってこの課題は、小生の好奇心を満たすのが目的で、それ以上でもそれ以下でもない。超個人的な拘りに過ぎない
さてと。Erlang erl_nifのリファレンスマニュアルによると、Elixir/ErlangからNIFs関数をcallする際、その引数データは ERL_NIF_TERM型に変換して渡される。NIFs側では、APIの erl_get_***関数群を使って渡された ERL_NIF_TERMから元のデータを復元して使用することになる。こんな感じだ…
ERL_NIF_TERM foo(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
<略>
double prm;
if (!enif_get_double(env, argv[0], &prm)) {
return enif_make_badarg(env);
}
double twice = 2.0 * prm;
<略>
}
だが、これでは課題はクリアできない。上のコード例では、予め設計で決めたデータ型しか受け取ることができない。つまり、受け取るデータをdouble型と決めると、double型しか格納できないFIFOになってしまう。
そんなつまらないものを作ってどうする!
やりたいことは、double型でも、int型でも、リスト型でも Elixirで定義できるデータならば何でもFIFOに放り込みたいのだ。
さて、どうする?
あれこれと試行錯誤した結果、どうやら ERL_NIF_TERMを binaryとして受け取れば良さそうだ。使用する APIは enif_term_to_binary()。enif_term_to_binary()は、受け取った ERL_NIF_TERMを解析し、Erlang external term formatのRAWデータを生成するようだ。少々効率が悪そうなところには目を瞑ろう。
ERL_NIF_TERM baa(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
<略>
ErlNifBinary bin;
if (!enif_term_to_binary(env, argv[0], &bin)) {
return enif_make_badarg(env);
}
<略>
}
Wrap-up
無事、最後の課題もクリアした。
ここまでの成果と併せて、FIFOの仕様に謳った機能をインプリメントすると、NIFsのコードはこうなる。
/*** File Header ************************************************************/
/**
* @file fifo_nif.cc
*
* Helper templates for handling NIFs resource object
* @author Shozo Fukuda
* @date Create: Thu Sep 12 16:12:13 JST 2019
* System Linux Mint<br>
*
* Produced by Shozo Fukuda
*
**/
/**************************************************************************{{{*/
/***** INCLUDE *****/
#include <erl_nif.h>
#include <deque>
#include <algorithm>
#include <mutex>
/***** CONSTANT *****/
/***** TYPE *****/
struct DequeNifBinary {
std::mutex m_mutex;
std::deque<ErlNifBinary> m_deque;
};
/***** MACRO *****/
#define OK(env) enif_make_atom(env, "ok")
#define ERROR(env) enif_make_atom(env, "error")
/***** EXPORT VARIABLE *****/
/***** PRIVATE VARIABLE *****/
static ErlNifResourceType* _ResType_DEQUE = NULL;
/*** Module Header ******************************************************}}}*/
/**
* create fifo
* @par description
* generate new fifo and resource object
*
* @return fifo handle
**/
/**************************************************************************{{{*/
ERL_NIF_TERM fifo_create(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
DequeNifBinary* deque_ptr
= new(enif_alloc_resource(_ResType_DEQUE, sizeof(DequeNifBinary))) DequeNifBinary;
ERL_NIF_TERM term = enif_make_resource(env, deque_ptr);
enif_release_resource(deque_ptr);
return term;
}
/*** Module Header ******************************************************}}}*/
/**
* peep front
* @par description
* peep front item of fifo
*
* @return front item or error
**/
/**************************************************************************{{{*/
ERL_NIF_TERM fifo_front(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
if (argc != 1) {
return enif_make_badarg(env);
}
DequeNifBinary* deque_ptr;
if (!enif_get_resource(env, argv[0], _ResType_DEQUE, (void**)&deque_ptr)) {
return enif_make_badarg(env);
}
// mutual exclusion after here
std::lock_guard<std::mutex> lock(deque_ptr->m_mutex);
if (!deque_ptr->m_deque.empty()) {
ErlNifBinary bin = deque_ptr->m_deque.front();
ERL_NIF_TERM term_bin;
enif_binary_to_term(env, bin.data, bin.size, &term_bin, 0);
return enif_make_tuple2(env, OK(env), term_bin);
}
else {
return enif_make_tuple2(env, ERROR(env), enif_make_string(env, "empty", ERL_NIF_LATIN1));
}
}
/*** Module Header ******************************************************}}}*/
/**
* pop item
* @par description
* pop item from front of fifo
*
* @return poped item or error
**/
/**************************************************************************{{{*/
ERL_NIF_TERM fifo_pop(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
if (argc != 1) {
return enif_make_badarg(env);
}
DequeNifBinary* deque_ptr;
if (!enif_get_resource(env, argv[0], _ResType_DEQUE, (void**)&deque_ptr)) {
return enif_make_badarg(env);
}
// mutual exclusion after here
std::lock_guard<std::mutex> lock(deque_ptr->m_mutex);
if (!deque_ptr->m_deque.empty()) {
ErlNifBinary bin = deque_ptr->m_deque.front();
deque_ptr->m_deque.pop_front();
ERL_NIF_TERM term_bin;
enif_binary_to_term(env, bin.data, bin.size, &term_bin, 0);
enif_release_binary(&bin);
return enif_make_tuple2(env, OK(env), term_bin);
}
else {
return enif_make_tuple2(env, ERROR(env), enif_make_string(env, "empty", ERL_NIF_LATIN1));
}
}
/*** Module Header ******************************************************}}}*/
/**
* push item
* @par description
* push item into backend of fifo
**/
/**************************************************************************{{{*/
ERL_NIF_TERM fifo_push(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
if (argc != 2) {
return enif_make_badarg(env);
}
DequeNifBinary* deque_ptr;
if (!enif_get_resource(env, argv[0], _ResType_DEQUE, (void**)&deque_ptr)) {
return enif_make_badarg(env);
}
ErlNifBinary term_bin;
if (!enif_term_to_binary(env, argv[1], &term_bin)) {
return enif_make_badarg(env);
}
// mutual exclusion after here
std::lock_guard<std::mutex> lock(deque_ptr->m_mutex);
deque_ptr->m_deque.push_back(term_bin);
return OK(env);
}
/*** Module Header ******************************************************}}}*/
/**
* clear fifo
* @par description
* delete all contents in fifo and reset fifo
**/
/**************************************************************************{{{*/
ERL_NIF_TERM fifo_clear(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
if (argc != 1) {
return enif_make_badarg(env);
}
DequeNifBinary* deque_ptr;
if (!enif_get_resource(env, argv[0], _ResType_DEQUE, (void**)&deque_ptr)) {
return enif_make_badarg(env);
}
// mutual exclusion after here
std::lock_guard<std::mutex> lock(deque_ptr->m_mutex);
if (!deque_ptr->m_deque.empty()) {
std::for_each(deque_ptr->m_deque.begin(), deque_ptr->m_deque.end(),
[](ErlNifBinary& bin){ enif_release_binary(&bin); });
deque_ptr->m_deque.clear();
}
return OK(env);
}
/*** Module Header ******************************************************}}}*/
/**
* size of fifo
* @par description
* inquire number of contens in fifo
*
* @return size
**/
/**************************************************************************{{{*/
ERL_NIF_TERM fifo_size(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
if (argc != 1) {
return enif_make_badarg(env);
}
DequeNifBinary* deque_ptr;
if (!enif_get_resource(env, argv[0], _ResType_DEQUE, (void**)&deque_ptr)) {
return enif_make_badarg(env);
}
return enif_make_uint(env, deque_ptr->m_deque.size());
}
/*** Module Header ******************************************************}}}*/
/**
* destroy fifo
* @par description
* delete all contents in fifo and reset fifo
**/
/**************************************************************************{{{*/
void fifo_destroy(ErlNifEnv* env, void* ptr)
{
DequeNifBinary* deque_ptr = reinterpret_cast<DequeNifBinary*>(ptr);
if (!deque_ptr->m_deque.empty()) {
std::for_each(deque_ptr->m_deque.begin(), deque_ptr->m_deque.end(),
[](ErlNifBinary& bin){ enif_release_binary(&bin); });
}
deque_ptr->~DequeNifBinary(); // don't delete. you can destruct it only.
}
/*** Module Header ******************************************************}}}*/
/**
* Nifs load
* @par description
* Nifs initializer
**/
/**************************************************************************{{{*/
int load(ErlNifEnv *env, void **priv_data, ERL_NIF_TERM load_info)
{
_ResType_DEQUE = enif_open_resource_type(env, NULL, "mydeque", fifo_destroy, ERL_NIF_RT_CREATE, NULL);
return 0;
}
// Let's define the array of ErlNifFunc beforehand:
static ErlNifFunc nif_funcs[] = {
// {erl_function_name, erl_function_arity, c_function, dirty_flags}
{ "fifo_create", 0, fifo_create, 0 },
{ "fifo_front", 1, fifo_front, 0 },
{ "fifo_get", 1, fifo_pop, 0 },
{ "fifo_put", 2, fifo_push, 0 },
{ "fifo_clear", 1, fifo_clear, 0 },
{ "fifo_count", 1, fifo_size, 0 },
};
ERL_NIF_INIT(Elixir.FIFO, nif_funcs, load, NULL, NULL, NULL)
#転: Elixirインターフェイスの実装
NIFs/C++側の実装は終わった。Pythonの拡張モジュールの実装ならばココで終わりなのだが、Elixirではもう少し作業が必要だ。
さあ、Elixir側に目を視点を転じよう。
ElixirからNIFs関数をcallする為には、まずNIFsの入った動的ライブラリをロードしなきゃならない。そして、NIFs関数と同じ名前をもつSTUB関数が定義されていなければならな。STUB関数は、動的ライブラリがロードされるときに、NIFs関数でオーバーライドされるようだ。
その他諸々と併せて実装すると、こうなる。
defmodule FIFO do
@moduledoc """
mutable FIFO data structure & operations
"""
defstruct handle: nil
def create() do
%FIFO{handle: fifo_create()}
end
def front(%FIFO{handle: h}) do
fifo_front(h)
end
def get(%FIFO{handle: h}) do
fifo_get(h)
end
def put(%FIFO{handle: h}, x) do
fifo_put(h, x)
end
def clear(%FIFO{handle: h}) do
fifo_clear(h)
end
def count(%FIFO{handle: h}) do
fifo_count(h)
end
# loading NIF library
@on_load :load_nif
def load_nif do
nif_file = Application.app_dir(:fifo, "priv/fifo_nif")
:erlang.load_nif(nif_file, 0)
end
# stub implementations for NIFs (fallback)
def fifo_create(), do: raise "NIF fifo_create/0 not implemented"
def fifo_front(_a), do: raise "NIF fifo_front/1 not implemented"
def fifo_get(_a), do: raise "NIF fifo_get/1 not implemented"
def fifo_put(_a,_b), do: raise "NIF fifo_put/2 not implemented"
def fifo_clear(_a), do: raise "NIF fifo_clear/1 not implemented"
def fifo_count(_a), do: raise "NIF fifo_count/1 not implemented"
end
#結: 結果良ければ全て良し
ふぅ、一通り実装が終わった
では試してみよう。
> iex -S mix
Erlang/OTP 22 [erts-10.4.3] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe]
mkdir -p /home/shozo/fifo/_build/dev/lib/fifo/priv
mkdir -p /home/shozo/fifo/_build/dev/lib/fifo/obj
g++ -c -I/home/shozo/.asdf/installs/erlang/22.0.4/usr/include -O2 -Wall -Wextra -Wno-unused-parameter -pedantic -fPIC -o /home/shozo/fifo/_build/dev/lib/fifo/obj/fifo_nif.o src/fifo_nif.cc
g++ -o /home/shozo/fifo/_build/dev/lib/fifo/priv/fifo_nif.so -L/home/shozo/.asdf/installs/erlang/22.0.4/usr/lib -fPIC -shared /home/shozo/fifo/_build/dev/lib/fifo/obj/fifo_nif.o
Compiling 1 file (.ex)
Generated fifo app
Interactive Elixir (1.9.0) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> fifo = FIFO.create
%FIFO{handle: #Reference<0.2846310002.3727818754.139300>}
iex(2)> FIFO.front(fifo)
{:error, 'empty'}
iex(3)> FIFO.put(fifo, ["Hello", :world])
:ok
iex(4)> FIFO.put(fifo, 2.7182)
:ok
iex(5)> FIFO.count(fifo)
2
iex(6)> FIFO.get(fifo)
{:ok, ["Hello", :world]}
iex(7)> FIFO.get(fifo)
{:ok, 2.7182}
iex(8)>
良さそうだな。仕様通りに動いているようだ。
#ふりかえり
関数型言語にあるまじき異端な拡張モジュール"mutableなFIFO"を実装してみた。
immutableで安全な楽園から一歩外のNIFsの世界に踏み込むと、しごく日常的で危険な世界が広がっていた。脛に多くの傷をもつ経験と勘を頼りに、安全に心掛けたNIFsの実装を行ったつもりであるが、果たしてどうだか分らない。
う~む、極論すれば入力されたデータを加工してDBに放り込むだけの、関数型言語がしっくりとfitするWEB系が羨ましいなぁ。
尚、ここに至るまでは、多くの先人たちの投稿記事を参考にさせて頂いた。感謝m(_ _)m
#参考文献
[1] Erlang Run-Time System Application (ERTS) Reference Manual Version 10.4 / erl_nif
[2] "Using C from Elixir with NIFs" Andrea Leopardi
[3] "Elixir Native Interoperability – Ports vs. NIFs" TONY BAKER
[4] GitHub: "Elixir Circuits - GPIO"
[5] OTP Design Principles User's Guide Version 10.4 / 7.4 Directory Structure