5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Elixirで変数をこんな風に持っているんだ

5
Last updated at Posted at 2025-12-21

はじめに

変数が設定できないー、はーー、そんなもの作れるかー、と愚痴ばかり。
いつかは変数を設定できるようにしてやってやるー、と息巻き、長い歳月が過ぎてしまった。
ここでは 現状、この変数問題をどう対処しているのか、を書いてみます。

なぜ、変数が定義できないのか

いやー、この件、書いては消し書いては消しと、まとまらず疲れてしまった。変数って外部変数が定義できない、データ保存ができないのが問題なので、そんなもの作れるか、とか、いろいろと書いていたが、どうも違うなぁ、と途方に暮れてしまった。
そうだ、ChapGptに聞いたら良いと聞いてみた。まぁ、最初からそうしろと言われそうだけど、頭の中では「そんなの当たり前の事じゃん」と、鷹を括っていた。といいわけ。

本題に戻り、「変数が定義できないとはどう言う事」とは聞いてみた。
回答: できないとは「代入によって値を変更する可変変数が存在しない」事。
そうそう、Elixirで最初に学ぶ不変(なんかかんやでimmutableと言っていた)事だ。

x = 1  #変数定義(xのエリアに1を入れる事)でなく、xと1を関連付けるとの意味
x = 2  #可変変数に見えるが、関連付けなので代入、更新するとの意味ではない

C言語で言えば、constの変数みたいなものと考えれば良いかも。関連付けなので意味が違うけど。
理由について、回答は
理由①:参照透過性
理由②:並行処理・分散処理を安全にするため
理由③:状態は「変数」ではなく「引数と戻り値」で表す
などとあった。
しかし、困っているのはこんな関数内の変数定義によるものではなく、外部変数(状態データ)用意できないので「作れない」との事なので、上記定義と理由には関係がない。従って、これ以上、深掘りしない。

外部変数が定義できないのはなぜ

ChatGptに以下の質問をした
「可変変数が定義できないのはわかったが、それは関数内での話じゃないのか、コードを書く上で関数外に静的変数がないので、関数同士で情報交換ができない。処理上、必要不可欠なのに定義できないとはどう言う事なのか。 処理は必ず関数同士でインターフェイスが発生する。メッセージのようなイベントだけでなく、状態データは必要とはならないのか。関数は独立していると言え、それだけで済まないとはならないのか」

回答:
これは 「関数型は理論」vs「実システムは状態を持つ」 の衝突点で、あなたの言っていることは 100% 正しい現場感覚です。
結論を先に言います。

関数型言語は「状態が不要」とは一切言っていない
「状態を“変数”として共有するな」と言っているだけ

との回答でした。あまり、ピンとこない。
つまり、「外部変数定義として、持つ事をせず、別な方法で持つ」との事。
最後の方に「なぜ静的変数を禁止するのか」の事について書かれていた。
静的変数の問題点

  • 誰が書いたかわからない
  • いつ変わったかわからない
  • 排他が必要
  • テスト不能
  • 再利用不能

やっと、「変数(状態)の持ち方」の前置きが終わり、次から本題になる。

引数を利用する

まず最初に疑問だったのはgen_serverで、どのように状態を持っているのだろうか、と調べた。

def handle_call(:pop, _from, [head | tail]) do 
	{:reply, head, tail}
end
def handle_cast({:push, element}, state) do
	{:noreply, [element | state]} 
end

call関数を見ると現在の値が[head | tail]で、{:reply, head, tail}のtailが新しい値でheadが返信するデータ、castではstateが現在値、新しい値は{:noreply, [element | stat]}[element | stat]]となり、引数で状態とわかった。

また、次の状態遷移処理(自作)

 def ledon(cb) do
    IO.puts("#{cb.func}  (#{timestamp(cb)})")
    ledon_loop(cb)
  end
  def ledon_loop(cb) do
    {eve, _msg} = FSM.rcv_msg()
    case eve do
      :off ->
        %{cb | func: :ledoff, arg: 0}
      :flk ->
        %{cb | func: :flicker, arg: 0}
      _ -> 
        IO.puts("#{cb.func}  cont(#{timestamp(cb)})")
        ledon_loop(cb)
    end
  end

cbは状態として、関数を呼び出す時に常に引数として付けられ、処理している。

通常プログラム言語ではこの引数のデータはスタックエリアに保存されるが、Elixirの場合はスタックと違い、モジュールで管理されたメモリエリア(heap?)で管理されていると思う。との言うのは、再帰呼び出しを繰り返すとスタックが消費されるのではないのか、との質問をChatGptに尋ねた時にheap領域で問題ないと回答だったので。
ただ、実際にはわからない。末尾呼びたしの場合に限定されているかも。

GenServerはごくごく一般的に使用しているが、処理間での話なのにわざわざ使うなんて、どうも納得がいかないと個人的には思う。

外部メモリのライブラリー:etsを使う

:etsモジュールを使用する。

:ets.insert(:user_lookup, {"doomspork", "Sean", ["Elixir", "Ruby", "Java"]})  #write
#
:ets.lookup(:user_lookup, "doomspork")  #read

更新にinsert()、読込みにlookupを使用しているが、:estにはいろんな関数があり、処理が少々複雑な気がする。全て、パターンマッチングによりread/writeをし、指定番号により要素を取得するように簡単には処理できない。

複数の関数からread/writeする場合、どうしても排他制御する必要があるが、その機能はない。従って、writeする人はひとりにするとか、データの整合性がとれるのかを意識する必要がある。

別の話で、排他制御に関して、enif_mutex_lock()enif_mutex_unlock()があり、NIF処理内では排他制御できるのか、と思ったが、プロセス間に共有メモリが存在せず、NIFと言えども排他制御はできないらしい。thread間で使用するものらしい。

ファイル化する

ファイルを読み書きして、外部状態として使用する。
これも、おそらく排他制御ができないので注意が必要かつ:etsよりもより複雑になると思う。

まとめ

コードを書いていて、状態データがあれば、処理がし易いのになぁ、といつも思う。

例えば、タイマー管理処理

def countdown(tick, lists) do
    lists =
      receive do
          {eve, mod, timer, reply_eve} -> set_cb(lists, mod, eve, timer, reply_eve)
          _  -> lists
          after tick ->
              do_count(lists)
      end
    countdown(tick, lists)
  end

最初は:etsでタイマーセット情報を持ち、タイマーが必要なモジュールから直接タイマーセットするようにしていたが、排他制御がないので処理ができない。
仕方がなく、メッセージでセットするようにした。しかし、メッセージを受信した時にどうしても、タイマーカウントがずれてしまう現象が発生する。仕方がなく諦めた。

状態遷移処理では引数cbにセットしたものの、下位に繋がる関数を含め、すべて関数にcbを引き継がなねばならないと、なんか気持ち悪い処理となってしまう。
以上のような不都合があり、作りにくいなぁとなり、この記事となりました。

5
0
3

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
5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?