Current section

Files

Jump to
lutil src lcycle.erl
Raw

src/lcycle.erl

-module(lcycle).
-behaviour(gen_server).
%% API - works with both stateful (pid) and stateless (list) versions
-export([new/1, next/1, last/1, take/2, lastn/2, reset/1, stop/1]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
-export([example/0]).
%%%===================================================================
%%% API - Polymorphic functions
%%%===================================================================
%% Create a new cycle gen_server with the given list (stateful only)
new(List) when is_list(List), List =/= [] ->
{ok, Pid} = gen_server:start_link(?MODULE, List, []),
Pid.
%% Get the next element - works with both pid (stateful) and list (stateless)
next(Pid) when is_pid(Pid) ->
gen_server:call(Pid, next);
next(List) when is_list(List), List =/= [] ->
next_stateless(List).
%% Get the last element - works with both pid (stateful) and list (stateless)
last(Pid) when is_pid(Pid) ->
gen_server:call(Pid, last);
last(List) when is_list(List), List =/= [] ->
last_stateless(List).
%% Take N elements - works with both pid (stateful) and list (stateless)
take(Count, State) when is_integer(Count), Count >= 0 ->
take_acc(Count, State, fun next_adapter/1, result_fun_for(State)).
%% Take N elements from end - works with both pid (stateful) and list (stateless)
lastn(Count, State) when is_integer(Count), Count >= 0 ->
lastn_acc(Count, State, fun last_adapter/1, result_fun_for(State)).
%% Reset the cycle to its initial state (stateful only)
reset(Pid) when is_pid(Pid) ->
gen_server:call(Pid, reset).
%% Stop the cycle gen_server (stateful only)
stop(Pid) when is_pid(Pid) ->
gen_server:stop(Pid).
%%%===================================================================
%%% gen_server callbacks
%%%===================================================================
init(List) ->
State = #{init => List, current => List},
{ok, State}.
handle_call(next, _From, #{current := CurrentList} = State) ->
{Value, NewCurrentList} = next_stateless(CurrentList),
NewState = State#{current := NewCurrentList},
{reply, Value, NewState};
handle_call(last, _From, #{current := CurrentList} = State) ->
{Value, NewCurrentList} = last_stateless(CurrentList),
NewState = State#{current := NewCurrentList},
{reply, Value, NewState};
handle_call(reset, _From, #{init := InitList} = State) ->
NewState = State#{current := InitList},
{reply, ok, NewState};
handle_call(_Request, _From, State) ->
{reply, {error, unknown_request}, State}.
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%%===================================================================
%%% Core stateless functions
%%%===================================================================
next_stateless([Head|Tail]) ->
NewList = Tail ++ [Head],
{Head, NewList}.
last_stateless(List) when is_list(List), List =/= [] ->
[LastElem|Rest] = lists:reverse(List),
NewList = [LastElem] ++ lists:reverse(Rest),
{LastElem, NewList}.
%% Result function selector
result_fun_for(State) when is_pid(State) -> fun stateful_result_fun/2;
result_fun_for(State) when is_list(State) -> fun stateless_result_fun/2.
%%%===================================================================
%%% Polymorphic accumulator functions
%%%===================================================================
%% Generic accumulator that works for both stateful and stateless
take_acc(Count, State, NextFun, ResultFun) ->
take_acc_loop(Count, State, NextFun, ResultFun, []).
take_acc_loop(0, State, _NextFun, ResultFun, Acc) ->
ResultFun(lists:reverse(Acc), State);
take_acc_loop(Count, State, NextFun, ResultFun, Acc) ->
{Value, NewState} = NextFun(State),
take_acc_loop(Count - 1, NewState, NextFun, ResultFun, [Value|Acc]).
lastn_acc(Count, State, LastFun, ResultFun) ->
lastn_acc_loop(Count, State, LastFun, ResultFun, []).
lastn_acc_loop(0, State, _LastFun, ResultFun, Acc) ->
ResultFun(Acc, State);
lastn_acc_loop(Count, State, LastFun, ResultFun, Acc) ->
{Value, NewState} = LastFun(State),
lastn_acc_loop(Count - 1, NewState, LastFun, ResultFun, [Value|Acc]).
%% Adapter functions that normalize stateful/stateless interfaces
next_adapter(Pid) when is_pid(Pid) ->
Value = gen_server:call(Pid, next),
{Value, Pid};
next_adapter(List) when is_list(List) ->
next_stateless(List).
last_adapter(Pid) when is_pid(Pid) ->
Value = gen_server:call(Pid, last),
{Value, Pid};
last_adapter(List) when is_list(List) ->
last_stateless(List).
stateful_result_fun(Acc, _State) -> Acc.
stateless_result_fun(Acc, State) -> {Acc, State}.
%%%===================================================================
%%% Example usage
%%%===================================================================
example() ->
io:format("=== Stateful version ===~n"),
% Create a cycle
Cycle = new([1, 2, 3, 4]),
io:format("Taking 6 elements forward: ~p~n", [take(6, Cycle)]),
io:format("Current next: ~p~n", [next(Cycle)]),
io:format("Current next: ~p~n", [next(Cycle)]),
io:format("Resetting...~n"),
reset(Cycle),
io:format("After reset, taking 10: ~p~n", [take(10, Cycle)]),
stop(Cycle),
io:format("~n=== Stateless version ===~n"),
% Stateless usage
List1 = [1, 2, 3, 4],
{Value1, List2} = next(List1),
io:format("Next from ~p: ~p, new state: ~p~n", [List1, Value1, List2]),
{Value2, List3} = last(List2),
io:format("Last from ~p: ~p, new state: ~p~n", [List2, Value2, List3]),
{Values, FinalList} = take(21, List3),
io:format("Take 21 from ~p: ~p, final state: ~p~n", [List3, Values, FinalList]).