Jak dynamicznie tworzyć i ładować moduły w czasie wykonywania w Elixir lub Erlang?
Podstawowy scenariusz jest następujący: muszę załadować tekst z bazy danych, a następnie przekształcić ten tekst w moduł Elixir (lub moduł Erlang), a następnie wykonywać połączenia do niego. Tekst jest faktycznie taki sam jak plik modułu. Jest to więc forma ładowania gorącego kodu. Chcę skompilować „plik”, a następnie załadować wynikowy moduł, a następnie wykonać wywołania. Później go rozładuję. Jedyną różnicą jest to, że kod istnieje w bazie danych, a nie plik na dysku. (i nie istnieje w momencie, gdy piszę kod, który będzie go ładował).
Wiem, że Erlang obsługuje ładowanie kodu na gorąco, ale wydaje się, że koncentruje się na kompilowaniu plików na dysku, a następnie ładowaniu wiązek. Chcę to zrobić jako bardziej dynamiczny proces i nie będę zastępował uruchomionego kodu, ale ładuję kod, a następnie go uruchomię, a następnie rozładuję.
W Elixir jest kilka udogodnień do oceny kodu w czasie wykonywania. Próbuję dowiedzieć się, jak to zrobić, a dokumentacja jest nieco rzadka.
Code.compile_string(string, "nofile")
„zwraca listę krotek, gdzie pierwszy element to nazwa modułu, a drugi to jego plik binarny”. Więc teraz mam nazwy modułów i ich pliki binarne, ale nie wiem, jak można załadować pliki binarne do środowiska wykonawczego i wywołać je. Jak bym to zrobił? (Nie widzę żadnej funkcji w bibliotece kodu).
Być może mógłbym wtedy użyć funkcji Erlang:
:code.load_binary(Module, Filename, Binary) ->
{module, Module} | {error, What}
Ok, więc zwraca krotkę z atomem „moduł”, a następnie Moduł. Jeśli ciąg załadowany z bazy danych zdefiniował moduł o nazwie „Paryż”, to jak w moim kodzie miałbym wtedy wykonać
paris.handler([parameters])
skoro nie wiem z góry, że będzie moduł zwany paris? Mogłem wiedzieć, że w bazie danych przechowywany jest także ciąg „paris”, który jest nazwą, ale czy istnieje jakikolwiek sposób wywołania modułu, używając ciągu jako nazwy wywoływanego modułu?
Jest również:
eval(string, binding // [], opts // [])
Który ocenia zawartość ciągu. Czy ten ciąg może być całą definicją modułu? Wygląda na to, że nie. Chciałbym móc napisać ten kod, który jest oceniany w taki sposób, że ma wiele funkcji, które wywołują się nawzajem - np. kompletny mały program ze wstępnie zdefiniowanym punktem wejścia (który może być głównym, takim jak „DynamicModule.handle ([parametr, lista])”
Następnie jest moduł EEx, który:
compile_string(source, options // [])
Co jest świetne do robienia szablonów. Ale ostatecznie wydaje się, że działa tylko w przypadku użycia, w którym znajduje się łańcuch i masz w nim kod Elixir. Ocenia łańcuch w kontekście opcji i tworzy ciąg. Próbuję skompilować ciąg znaków na jedną lub więcej funkcji, które mogę następnie wywołać. (Jeśli mogę tylko zrobić jedną funkcję, która jest w porządku, funkcja ta może dopasować dopasowanie lub przełączyć się na inne potrzebne rzeczy ...)
Wiem, że to niekonwencjonalne, ale mam swoje powody, by robić to w ten sposób i są dobre. Szukam porady, jak to zrobić, ale nie trzeba mówić „nie rób tego”. Wygląda na to, że powinno być możliwe, Erlang obsługuje ładowanie gorącego kodu, a Elixir jest dość dynamiczny, ale po prostu nie znam składni ani właściwych funkcji. Będę uważnie monitorować to pytanie. Z góry dziękuję!
EDITS na podstawie pierwszych odpowiedzi:
Dzięki za odpowiedzi, to dobry postęp. Jak pokazał Yuri, eval może zdefiniować moduł, a jak wskazuje José, mogę po prostu użyć eval kodu dla małych części kodu z powiązaniami.
Kod podlegający ocenie (niezależnie od tego, czy został przekształcony w moduł, czy nie) będzie dość złożony. A jego rozwój najlepiej byłoby podzielić na funkcje i wywoływać te funkcje.
Aby pomóc, pozwólcie, że przedstawię kontekst. Załóżmy, że buduję framework sieciowy. Kod załadowany z bazy danych to procedury obsługi określonych identyfikatorów URI. Tak więc, gdy nadchodzi połączenie HTTP, mogę załadować kod na przykład.com/blog/ Ta strona może obejmować kilka różnych rzeczy, takich jak komentarze, lista ostatnich postów itp.
Ponieważ wiele osób uderza w stronę w tym samym czasie, pojawia się proces obsługi każdego widoku strony. Tak więc wiele razy ten kod może być oceniany jednocześnie, dla różnych żądań.
Rozwiązanie modułowe pozwala na rozbicie kodu na funkcje dla różnych części strony (np .: lista postów, komentarze itp.). I załadowałbym moduł raz, przy starcie, i pozwoliłbym wielu procesom odrodzić to wywołanie w tym. Moduł jest globalny, prawda?
Co się stanie, jeśli jest już zdefiniowany moduł? EG: Gdy moduł się zmienia, a procesy już wywołują ten moduł.
W iex jestem w stanie przedefiniować już zdefiniowany moduł:
iex(20)> Code.eval "defmodule A do\ndef a do\n5\nend\nend"
nofile:1: redefining module A
Co się stanie, jeśli przedefiniuję moduł w czasie wykonywania do wszystkich procesów aktualnie wywołujących ten moduł? Czy ta redefinicja będzie działać poza iex, podczas normalnej pracy?
Zakładając, że ponowne zdefiniowanie modułu będzie problematyczne, a moduły globalne mogą napotkać problemy z kolizjami przestrzeni nazw, przyjrzałem się użyciu eval do zdefiniowania funkcji.
Jeśli mogę po prostu mieć kod z funkcji definiujących bazę danych, to funkcje te mieszczą się w zakresie dowolnego procesu i nie mamy możliwości globalnych kolizji.
Jednak wydaje się, że to nie działa:
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
Próbowałem też:
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
Podsumowując:
Czy można przedefiniować moduły za pomocą Code.eval, gdy są wywoływane procesy?
Czy można użyć Code.eval do tworzenia funkcji, których zakres jest powiązany z procesem, w którym wywołano Code.eval?
Jeśli rozumiesz, co próbuję zrobić, czy możesz zaproponować lepszy sposób, aby to zrobić?
Ponadto, jeśli istnieje lepsze forum, na którym powinienem zadać to pytanie, daj mi znać. A jeśli istnieją dokumenty lub odpowiednie przykłady, które powinienem przeczytać, proszę, wskaż mi ich. Nie próbuję cię zmusić do wykonania całej pracy, po prostu nie jestem w stanie sam tego zrozumieć.
Uczę się Elixir specjalnie dla możliwości dynamicznego rozwijania kodu, ale moja wiedza o Elixir jest teraz minimalna - właśnie zacząłem - i mój erlang też jest zardzewiały.
Wielkie dzięki!