Current section

Files

Jump to
erlang_python examples reentrant_demo.erl
Raw

examples/reentrant_demo.erl

%%% @doc Reentrant callback demonstration.
%%%
%%% This module demonstrates transparent Python ↔ Erlang reentrant callbacks.
%%% The suspension/resume mechanism is invisible to both Python and Erlang code.
%%%
%%% Usage:
%%% 1> reentrant_demo:start().
%%% 2> reentrant_demo:demo_basic().
%%% 3> reentrant_demo:demo_fibonacci().
%%% 4> reentrant_demo:demo_nested().
%%% 5> reentrant_demo:demo_processor().
-module(reentrant_demo).
-export([
start/0,
stop/0,
demo_basic/0,
demo_fibonacci/0,
demo_nested/0,
demo_processor/0,
demo_all/0
]).
%% @doc Start the application and register callback functions.
start() ->
{ok, _} = application:ensure_all_started(erlang_python),
%% Load our Python demo module
{ok, _} = py:exec(<<"
import sys
sys.path.insert(0, 'examples')
import reentrant_demo
">>),
%% Register Erlang functions that Python can call
register_callbacks(),
io:format("Reentrant demo started. Try:~n"),
io:format(" reentrant_demo:demo_basic().~n"),
io:format(" reentrant_demo:demo_fibonacci().~n"),
io:format(" reentrant_demo:demo_nested().~n"),
io:format(" reentrant_demo:demo_processor().~n"),
io:format(" reentrant_demo:demo_all().~n"),
ok.
%% @doc Stop the application.
stop() ->
application:stop(erlang_python).
%% @doc Register all callback functions.
register_callbacks() ->
%% Basic: Erlang function that calls Python's double()
py:register_function(double_via_python, fun([X]) ->
{ok, Result} = py:call(reentrant_demo, double, [X]),
Result
end),
%% Fibonacci with memoization
py:register_function(fib_cache, fun([N]) ->
%% Check cache (using process dictionary for simplicity)
case get({fib, N}) of
undefined ->
%% Not cached - compute by calling Python's fibonacci
%% This creates: Python -> Erlang -> Python -> Erlang -> ...
{ok, A} = py:call(reentrant_demo, fibonacci, [N - 1]),
{ok, B} = py:call(reentrant_demo, fibonacci, [N - 2]),
Result = A + B,
put({fib, N}, Result),
Result;
Cached ->
Cached
end
end),
%% Nested computation step
py:register_function(nested_step, fun([N, Depth]) ->
%% Call back into Python with reduced depth
{ok, Result} = py:call(reentrant_demo, nested_compute, [N + 1, Depth - 1]),
Result
end),
%% Item validation that calls back to Python
py:register_function(validate_and_transform, fun([Item]) ->
{ok, Validated} = py:call(reentrant_demo, validate_item, [Item]),
case Validated of
#{<<"valid">> := true, <<"data">> := Data} ->
%% Transform valid data
{ok, Transformed} = py:call(reentrant_demo, transform_data, [Data]),
Transformed;
#{<<"valid">> := false} = Error ->
Error
end
end),
%% Rules checking (simple implementation)
py:register_function(check_rules, fun([Type]) ->
%% Simulate rules database lookup
ValidTypes = [<<"user">>, <<"product">>, <<"order">>],
lists:member(Type, ValidTypes)
end),
%% Multiplier lookup
py:register_function(get_multiplier, fun([Type]) ->
case Type of
<<"premium">> -> 3;
<<"standard">> -> 2;
_ -> 1
end
end),
%% Processor handler
py:register_function(processor_handle, fun([Name, Value]) ->
%% Call back to the processor's compute method
{ok, Result} = py:eval(iolist_to_binary(io_lib:format(
"reentrant_demo.DataProcessor('~s').compute(~p, 100)", [Name, Value]))),
Result
end),
%% Factor lookup for processor
py:register_function(get_factor, fun([_Name]) ->
2 % Simple factor
end),
ok.
%% @doc Demonstrate basic reentrant callback.
demo_basic() ->
io:format("~n=== Basic Reentrant Demo ===~n"),
io:format("Calling Python's add_one(10) which calls Erlang's double_via_python~n"),
io:format("which calls Python's double(), then adds 1.~n~n"),
%% Python: add_one(10) -> erlang.call('double_via_python', 10) + 1
%% Erlang: double_via_python -> py:call(double, [10])
%% Python: double(10) -> 20
%% Back to Python: 20 + 1 = 21
{ok, Result} = py:call(reentrant_demo, add_one, [10]),
io:format("Flow: Python -> Erlang -> Python -> Erlang (result)~n"),
io:format("Result: ~p (expected: 21)~n", [Result]),
io:format("Test ~s~n", [if Result == 21 -> "PASSED"; true -> "FAILED" end]),
Result.
%% @doc Demonstrate Fibonacci with reentrant memoization.
demo_fibonacci() ->
io:format("~n=== Fibonacci Reentrant Demo ===~n"),
io:format("Computing Fibonacci using Python with Erlang memoization.~n"),
io:format("Each recursive call goes: Python -> Erlang -> Python~n~n"),
%% Clear the cache
erase(),
{ok, Fib10} = py:call(reentrant_demo, fibonacci, [10]),
io:format("fibonacci(10) = ~p (expected: 55)~n", [Fib10]),
{ok, Fib15} = py:call(reentrant_demo, fibonacci, [15]),
io:format("fibonacci(15) = ~p (expected: 610)~n", [Fib15]),
io:format("Test ~s~n", [if Fib10 == 55, Fib15 == 610 -> "PASSED"; true -> "FAILED" end]),
{Fib10, Fib15}.
%% @doc Demonstrate deeply nested callbacks.
demo_nested() ->
io:format("~n=== Nested Callbacks Demo ===~n"),
io:format("Testing deep nesting: Python -> Erlang -> Python -> ...~n~n"),
%% nested_compute(0, 5) will create 5 levels of nesting
%% Each level: Python calls Erlang, Erlang calls Python
{ok, Result5} = py:call(reentrant_demo, nested_compute, [0, 5]),
io:format("nested_compute(0, 5) = ~p (expected: 5, depth levels traversed)~n", [Result5]),
{ok, Result10} = py:call(reentrant_demo, nested_compute, [0, 10]),
io:format("nested_compute(0, 10) = ~p (expected: 10)~n", [Result10]),
io:format("Test ~s~n", [if Result5 == 5, Result10 == 10 -> "PASSED"; true -> "FAILED" end]),
{Result5, Result10}.
%% @doc Demonstrate OOP-style reentrant callbacks.
demo_processor() ->
io:format("~n=== DataProcessor Demo ===~n"),
io:format("Using a Python class with methods called from Erlang.~n~n"),
%% Create a processor and call its process method
%% This triggers: Python.process -> Erlang.processor_handle -> Python.compute -> Erlang.get_factor
{ok, Result} = py:eval(<<"reentrant_demo.DataProcessor('test').process(42)">>),
io:format("DataProcessor('test').process(42) = ~p~n", [Result]),
io:format("Expected: processor='test', result=(42+100)*2=284~n"),
#{<<"result">> := ComputeResult} = Result,
io:format("Test ~s~n", [if ComputeResult == 284 -> "PASSED"; true -> "FAILED" end]),
Result.
%% @doc Run all demos.
demo_all() ->
io:format("~n========================================~n"),
io:format(" REENTRANT CALLBACKS DEMONSTRATION~n"),
io:format("========================================~n"),
demo_basic(),
demo_fibonacci(),
demo_nested(),
demo_processor(),
io:format("~n========================================~n"),
io:format(" ALL DEMOS COMPLETE~n"),
io:format("========================================~n"),
ok.