39

基本的なシナリオは次のとおりです。データベースからテキストをロードし、そのテキストを Elixir モジュール (または Erlang モジュール) に変換して呼び出しを行う必要があります。テキストは実質的にモジュール ファイルと同じです。したがって、これはホット コード ロードの一種です。「ファイル」をコンパイルしてから、結果のモジュールをロードし、それを呼び出します。後で荷降ろしします。唯一の違いは、コードがディスク上のファイルではなくデータベースに存在することです。(そして、それをロードするコードを書いている時点では存在しません。)

Erlang がホット コード ロードをサポートしていることは知っていますが、ディスク上のファイルをコンパイルしてからビームをロードすることに重点を置いているようです。これをより動的なプロセスとして実行したいと考えており、実行中のコードを置き換えるのではなく、コードをロードしてから実行し、アンロードします。

Elixir には、実行時にコードを評価するための機能がいくつかあります。私はそれらでこれを行う方法を理解しようとしていますが、ドキュメントは少しまばらです。

Code.compile_string(string, "nofile")

「最初の要素がモジュール名で、2 番目の要素がそのバイナリであるタプルのリストを返します」。これで、モジュール名とそのバイナリを取得できましたが、バイナリをランタイムにロードして呼び出す方法がわかりません。どうすればいいですか?(私が見ることができるコードライブラリにはそのための機能はありません。)

おそらく、Erlang 関数を使用できます。

:code.load_binary(Module, Filename, Binary)  ->
           {module, Module} | {error, What}

わかりました、これはアトム「モジュール」とモジュールのタプルを返します。データベースからロードされた文字列が「Paris」というモジュールを定義した場合、コードでどのように実行しますか

paris.handler([parameters])

parisというモジュールがあることを前もって知らないので?文字列「paris」もデータベースに保存することで、これが名前であることを知ることができましたが、呼び出しているモジュールの名前として文字列を使用して、モジュールを呼び出す方法はありますか?

もあります:

eval(string, binding // [], opts // [])

文字列の内容を評価します。この文字列をモジュールの定義全体にすることはできますか? そうではないようです。相互に呼び出す複数の関数を持つような方法で評価されるこのコードを記述できるようにしたいと考えています。たとえば、事前定義されたエントリ ポイント (メインなどの「DynamicModule.handle([パラメータ, リスト])」として

次に、EEx モジュールがあります。これには次のものがあります。

compile_string(source, options // [])

これは、テンプレートを作成するのに最適です。しかし、最終的には、文字列があり、Elixir コードが埋め込まれているユースケースでのみ機能するようです。オプションのコンテキストで文字列を評価し、文字列を生成します。文字列を、呼び出し可能な 1 つ以上の関数にコンパイルしようとしています。(問題のない関数を 1 つだけ作成できる場合、その関数はパターン マッチするか、必要な他のことを行うように切り替えることができます....)

これが型にはまらないことはわかっていますが、このようにする理由があり、それらは良いものです。これを行う方法についてアドバイスを探していますが、「そうしないでください」と言われる必要はありません。Erlang はホット コード ロードをサポートし、Elixir は非常に動的ですが、構文や適切な関数がわかりません。私はこの質問を注意深く監視します。前もって感謝します!


最初の回答に基づく編集:

答えてくれてありがとう、これは良い進歩です。Yuri が示したように、eval はモジュールを定義できます。また、José が指摘するように、コード eval はバインディングを含むコードの小さな部分に使用できます。

評価されるコードは (モジュールに変換されるかどうかにかかわらず) かなり複雑になります。そして、その開発は、それを関数に分解し、それらの関数を呼び出すことを含むのが最善です.

参考までに、いくつかのコンテキストを提供させてください。Web フレームワークを構築しているとします。データベースからロードされたコードは、特定の URI のハンドラーです。したがって、HTTP 呼び出しが着信すると、example.com/blog/ のコードをロードすることがあります。このページには、コメント、最近の投稿のリストなど、いくつかの異なる内容が含まれる場合があります。

多くの人が同時にページにアクセスしているため、各ページ ビューを処理するプロセスを生成しています。したがって、異なる要求に対して、このコードが同時に評価される場合が多くあります。

モジュール ソリューションを使用すると、コードをページのさまざまな部分 (例: 投稿、コメントのリストなど) の関数に分割できます。また、起動時にモジュールを 1 回ロードし、それを呼び出す多くのプロセスを生成します。それに。モジュールはグローバルですよね?

モジュールが既に定義されている場合はどうなりますか? EG: モジュールが変更され、そのモジュールを既に呼び出しているプロセスがある場合。

iex では、既に定義されているモジュールを再定義できます。

iex(20)> Code.eval "defmodule A do\ndef a do\n5\nend\nend"
nofile:1: redefining module A

そのモジュールを現在呼び出しているすべてのプロセスに対して、実行時にモジュールを再定義するとどうなりますか? また、この再定義は iex 以外の通常の操作で機能しますか?

モジュールの再定義には問題があり、モジュールがグローバルであると名前空間の衝突で問題が発生する可能性があると想定して、関数を定義するために eval を使用することを検討しました。

データベースからのコードで関数を定義するだけでよい場合、それらの関数はどのようなプロセスのスコープ内にもあり、グローバルな衝突の可能性はありません。

ただし、これは機能しないようです。

iex(31)> q = "f = function do
...(31)> x, y when x > 0 -> x+y
...(31)> x, y -> x* y
...(31)> end"
"f = function do\nx, y when x > 0 -> x+y\nx, y -> x* y\nend"
iex(32)> Code.eval q
{#Fun<erl_eval.12.82930912>,[f: #Fun<erl_eval.12.82930912>]}
iex(33)> f
** (UndefinedFunctionError) undefined function: IEx.Helpers.f/0
    IEx.Helpers.f()
    erl_eval.erl:572: :erl_eval.do_apply/6
    src/elixir.erl:110: :elixir.eval_forms/3
    /Users/jose/Work/github/elixir/lib/iex/lib/iex/loop.ex:18: IEx.Loop.do_loop/1

iex(33)> f.(1,3)
** (UndefinedFunctionError) undefined function: IEx.Helpers.f/0
    IEx.Helpers.f()
    erl_eval.erl:572: :erl_eval.do_apply/6
    erl_eval.erl:355: :erl_eval.expr/5
    src/elixir.erl:110: :elixir.eval_forms/3
    /Users/jose/Work/github/elixir/lib/iex/lib/iex/loop.ex:18: IEx.Loop.do_loop/1

私も試しました:

    iex(17)> y = Code.eval "fn(a,b) -> a + b end"
{#Fun<erl_eval.12.82930912>,[]}
iex(18)> y.(1,2)
** (BadFunctionError) bad function: {#Fun<erl_eval.12.82930912>,[]}
    erl_eval.erl:559: :erl_eval.do_apply/5
    src/elixir.erl:110: :elixir.eval_forms/3
    /Users/jose/Work/github/elixir/lib/iex/lib/iex/loop.ex:18: IEx.Loop.do_loop/1

つまり、要約すると:

  1. モジュールを呼び出すプロセスがある場合、Code.eval を使用してモジュールを再定義できますか?

  2. Code.eval を使用して、Code.eval が呼び出されたプロセスにスコープがバインドされた関数を作成することはできますか?

  3. 私がやろうとしていることを理解できたら、より良い方法を提案してもらえますか?

また、私がこれを尋ねるべきであるより良いフォーラムがあれば、遠慮なく私に知らせてください. また、読むべきドキュメントや関連する例があれば、遠慮なくそれらを教えてください。私はあなたにすべての仕事をさせようとしているわけではなく、自分でそれを理解することができません.

特にコードを動的に評価する機能のために Elixir を学んでいますが、Elixir の知識は今では最小限です。

どうもありがとう!

4

3 に答える 3

37

あなたが説明したように、最終的には 1) コードのコンパイルと 2) コードの評価の 2 つの異なるカテゴリに要約することによって、取ることができるさまざまなアプローチがあります。上記の例では、モジュールを定義するコンパイルが必要であり、それを呼び出す必要があります。ただし、お気づきのように、モジュール名を定義し、データベースが変更されたときにそれらのモジュールを削除して破棄する可能性があります。また、モジュールを定義すると、モジュールごとにアトムが作成されるため、アトム テーブルが使い果たされる可能性があることに注意してください。最大で 12 個のモジュールをコンパイルする必要がある場合にのみ、このアプローチを使用します。

(小さなメモ、Code.compile_stringモジュールは既に定義されているため、呼び出す必要はありませんload_binary)。

おそらく、より簡単なアプローチはCode.eval、データベースからコードを渡すことを呼び出して、コードを評価することです。いくつかのカスタム ルールを評価するだけであれば、問題なく動作します。はCode.evalバインディングを受け入れます。これにより、パラメーターをコードに渡すことができます。データベースに "a + b" が格納されているとします。abはパラメーターであり、次のように評価できます。

Code.eval "a + b", [a: 1, b: 1]

質問の編集に基づいて編集

コードを評価している場合はCode.eval、コードと新しいバインディングを評価した結果が返されることに注意してください。したがって、上記の例は次のように記述したほうがよいでしょう。

q = "f = function do
x, y when x > 0 -> x+y
x, y -> x* y
end"

{ _, binding } = Code.eval q
binding[:f].(1, 2)

ただし、ユースケースを考えると、 eval の使用は検討しませんが、実際にはモジュールをコンパイルします。情報はデータベースから取得されるため、この事実を使用して、レコードごとに一意のモジュールを生成できます。たとえば、次のように、開発者がモジュールのコンテンツをデータベースに追加すると想定できます。

def foo, do: 1
def bar, do: 1

データベースからこの情報を取得したら、次を使用してコンパイルできます。

record   = get_record_from_database
id       = record.id
contents = Code.string_to_quoted!(record.body)
module   = Module.concat(FromDB, "Data#{record.id}")
Module.create module, contents, Macro.Env.location(__ENV__)
module

record は、データベースから取得したものです。レコード ID が であると仮定すると1、モジュールが定義され、それをFromDB.Data1呼び出すことができます。foobar

コードのリロードに関しては、 と の両方defmoduleModule.create使用:code.load_binaryしてモジュールをロードします。つまり、モジュールを更新すると、別の新しいバージョンがロードされるまで古いバージョンが保持されます。

キャッシュの有効期限も追加する必要があるため、リクエストごとにモジュールをコンパイルする必要はありません。これは、レコードの内容を変更するたびにインクリメントするデータベースに lock_version がある場合に実行できます。最終的なコードは次のようになります。

record  = get_record_from_database
module  = Module.concat(FromDB, "Data#{record.id}")
compile = :code.is_loaded(module) == false or
            record.lock_version > module.lock_version

if compile do
  id       = record.id
  contents = Code.string_to_quoted!(record.body)
  contents = quote do
    def lock_version, do: unquote(record.lock_version)
    unquote(contents)
  end
  Module.create module, contents, Macro.Env.location(__ENV__)
end

module
于 2012-11-05T18:11:57.117 に答える
11

Code.eval を使用してモジュールを定義できます。

iex(1)> Code.eval "defmodule A do\ndef a do\n1\nend\nend" 
{{:module,A,<<70,79,82,49,0,0,2,168,66,69,65,77,65,116,111,109,0,0,0,98,0,0,0,11,8,69,108,105,120,105,114,45,65,8,95,95,105,110,102,111,95,95,4,100,111,99,115,9,102,117,...>>,{:a,0}},[]}
iex(2)> A.a
1

これは役に立ちますか?

于 2012-11-04T22:14:04.423 に答える
4

チェックしましたか: Dynamic Compile Library by Jacob Vorreuter。以下の例を参照してください

1> 文字列 = "-module(add).\n -export([add/2]). \n add(A,B) -> A + B. \n".
"-module(add).\n -export([add/2]). \n add(A,B) -> A + B. \n"
2> dynamic_compile:load_from_string(文字列)。
{モジュール、追加}
3> 追加: 追加 (2,5)。
7
4>
また、この質問とその回答を確認してください

于 2012-11-05T09:21:57.683 に答える