-module(buclambda). -export([ pipe/1 , compose/1 , curry/1 , curry/2 , curry/3 , curry/4 , f_curry/1 , f_curry/2 , f_curry/3 , f_curry/4 , rcurry/1 , rcurry/2 , rcurry/3 , rcurry/4 , f_rcurry/1 , f_rcurry/2 , f_rcurry/3 , f_rcurry/4 ]). % @doc % Performs left-to-right function composition. % %
% {ok, F} = buclambda:pipe([
% buclambda:f_curry(fun lists:filter/2, [fun(E) -> E > 10 end]),
% fun erlang:length/1
% ]).
% F([8, 9, 10, 11, 12]).
% % => 2
%
% @end
-spec pipe(Functions :: [function() | {function(), [term()]}]) -> {ok, function()} | error.
pipe(Functions) ->
case lists:all(buclambda:f_rcurry(fun erlang:is_function/2, [1]), Functions) of
true ->
{ok, fun(X) -> funcalls(lists:reverse(Functions), X) end};
false ->
error
end.
funcalls([], X) -> X;
funcalls([Function|Rest], X) ->
erlang:apply(Function, [funcalls(Rest, X)]).
% @doc
% Performs right-to-left function composition.
%
%
% {ok, F} = buclambda:compose([
% fun erlang:length/1,
% buclambda:f_curry(fun lists:filter/2, [fun(E) -> E > 10 end])
% ]).
% F([8, 9, 10, 11, 12]).
% % => 2
%
% @end
-spec compose(Functions :: [function()]) -> {ok, function()} | error.
compose(Functions) ->
case lists:all(buclambda:f_rcurry(fun erlang:is_function/2, [1]), Functions) of
true ->
{ok, fun(X) -> funcalls(Functions, X) end};
false ->
error
end.
% @doc
% Returns a curried equivalent of the provided function.
%
%
% buclambda:curry(fun lists:keystore/4).
% % => {ok, #Fun<erl_eval.6.99386804>}
% % => fun(A0) -> fun(A1) -> fun(A2) -> fun(A3) -> lists:keystore(A0, A1, A2, A3) end end end end.
%
% @end
-spec curry(Fun :: function()) -> {ok, function()} | error.
curry(Fun) when is_function(Fun) ->
case get_fun_info(Fun) of
{Module, Function, Arity} ->
curry(Module, Function, Arity);
{_Fun, _Arity} ->
error
% TODO: currify(Fun, Arity, Arity, lr)
end.
% @doc
% Same as buclambda:curry/1 but fails with an exception on error
% @end
-spec f_curry(Fun :: function()) -> function().
f_curry(Fun) ->
case curry(Fun) of
{ok, F} -> F;
Error -> error(Error)
end.
% @doc
% Returns a curried equivalent of the provided function, with the specified deep or params.
%
%
% buclambda:curry(fun lists:keystore/4, 4).
% % => {ok, #Fun<erl_eval.6.99386804>}
% % => fun(A0) -> fun(A1) -> fun(A2) -> fun(A3) -> lists:keystore(A0, A1, A2, A3) end end end end.
%
% buclambda:curry(fun lists:keystore/4, 3).
% % => {ok, #Fun<erl_eval.12.99386804>}
% % => fun(A0, A1) -> fun(A2) -> fun(A3) -> lists:keystore(A0, A1, A2, A3) end end end.
%
% buclambda:curry(fun lists:keystore/4, 2).
% % =>{ok, #Fun<erl_eval.18.99386804>}
% % => fun(A0, A1, A2) -> fun(A3) -> lists:keystore(A0, A1, A2, A3) end end.
%
% buclambda:curry(fun lists:keystore/4, 1).
% % => {ok, #Fun<erl_eval.4.99386804>}
% % => fun(A0, A1, A2, A3) -> lists:keystore(A0, A1, A2, A3) end.
%
% {ok, F} = buclambda:curry(fun lists:keyfind/3, [toto, 1]).
% F([{tata, 1}, {toto, 2}, {titi, 3}]).
% % => {toto, 2}
% F([{tata, 1}, {tutu, 2}, {titi, 3}]).
% % => false
%
% @end
-spec curry(Fun :: function(), DeepOrArgs :: integer() | [term()]) -> {ok, function()} | error.
curry(Fun, Deep) when is_integer(Deep) orelse is_list(Deep) ->
case get_fun_info(Fun) of
{Module, Function, Arity} ->
curry(Module, Function, Arity, Deep);
{_Fun, _Arity} ->
error
% TODO: case bucs:type(Deep) of
% TODO: integer ->
% TODO: currify(Fun, Arity, Deep, lr);
% TODO: _ ->
% TODO: case currify(Fun, Arity, Arity - length(Deep) + 1, lr) of
% TODO: {ok, CurryFun} -> {ok, erlang:apply(CurryFun, Deep)};
% TODO: Other -> Other
% TODO: end
% TODO: end
end.
% @doc
% Same as buclambda:curry/2 but fails with an exception on error
% @end
-spec f_curry(Fun :: function(), DeepOrArgs :: integer() | [term()]) -> function().
f_curry(Fun, Deep) ->
case curry(Fun, Deep) of
{ok, F} -> F;
Error -> error(Error)
end.
% @doc
% Returns a reverse curried equivalent of the provided function.
%
%
% buclambda:rcurry(fun lists:keystore/4).
% % => {ok, #Fun<erl_eval.6.99386804>}
% % => fun(A3) -> fun(A2) -> fun(A1) -> fun(A0) -> lists:keystore(A0, A1, A2, A3) end end end end.
%
-spec rcurry(Fun :: function()) -> {ok, function()} | error.
rcurry(Fun) when is_function(Fun) ->
case get_fun_info(Fun) of
{Module, Function, Arity} ->
rcurry(Module, Function, Arity);
{_Fun, _Arity} ->
error
% TODO: currify(Fun, Arity, Arity, rl)
end.
% @doc
% Same as buclambda:r_curry/1 but fails with an exception on error
% @end
-spec f_rcurry(Fun :: function()) -> function().
f_rcurry(Fun) ->
case rcurry(Fun) of
{ok, F} -> F;
Error -> error(Error)
end.
% @doc
% Returns a reverse curried equivalent of the provided function, with the specified deep or params.
%
%
% buclambda:rcurry(fun lists:keystore/4, 4).
% % => {ok, #Fun<erl_eval.6.99386804>}
% % => fun(A3) -> fun(A2) -> fun(A1) -> fun(A0) -> lists:keystore(A0, A1, A2, A3) end end end end.
%
% buclambda:rcurry(fun lists:keystore/4, 3).
% % => {ok, #Fun<erl_eval.12.99386804>}
% % => fun(A3, A2) -> fun(A1) -> fun(A0) -> lists:keystore(A0, A1, A2, A3) end end end.
%
% buclambda:rcurry(fun lists:keystore/4, 2).
% % => {ok, #Fun<erl_eval.18.99386804>}
% % => fun(A3, A2, A1) -> fun(A0) -> lists:keystore(A0, A1, A2, A3) end end.
%
% buclambda:rcurry(fun lists:keystore/4, 1).
% % => {ok, #Fun<erl_eval.4.99386804>}
% % => fun(A3, A2, A1, A0) -> lists:keystore(A0, A1, A2, A3) end.
%
% {ok, F} = buclambda:rcurry(lists, keyfind, 3, [[{tata, 1}, {toto, 2}, {titi, 3}], 1]).
% F(toto).
% % => {toto, 2}
% F(tutu).
% % => false
%
% @end
-spec rcurry(Fun :: function(), DeepOrArgs :: integer() | [term()]) -> {ok, function()} | error.
rcurry(Fun, Deep) when is_integer(Deep) orelse is_list(Deep) ->
case get_fun_info(Fun) of
{Module, Function, Arity} ->
rcurry(Module, Function, Arity, Deep);
{_Fun, _Arity} ->
error
% TODO: case bucs:type(Deep) of
% TODO: integer ->
% TODO: currify(Fun, Arity, Deep, lr);
% TODO: _ ->
% TODO: case currify(Fun, Arity, Arity - length(Deep) + 1, rl) of
% TODO: {ok, CurryFun} -> {ok, erlang:apply(CurryFun, Deep)};
% TODO: Other -> Other
% TODO: end
% TODO: end
end.
% @doc
% Same as buclambda:rcurry/2 but fails with an exception on error
% @end
-spec f_rcurry(Fun :: function(), DeepOrArgs :: integer() | [term()]) -> function().
f_rcurry(Fun, Deep) ->
case rcurry(Fun, Deep) of
{ok, F} -> F;
Error -> error(Error)
end.
% @doc
% Returns a curried equivalent of the provided function.
%
%
% buclambda:curry(lists, keystore, 4).
% % => {ok, #Fun<erl_eval.6.99386804>}
% % => fun(A0) -> fun(A1) -> fun(A2) -> fun(A3) -> lists:keystore(A0, A1, A2, A3) end end end end.
%
% @end
-spec curry(Module :: module(), Function :: atom(), Arity :: integer()) ->
{ok, function()} | error.
curry(Module, Function, Arity) ->
currify({Module, Function}, Arity, Arity, lr).
% @doc
% Same as buclambda:curry/3 but fails with an exception on error
% @end
-spec f_curry(Module :: module(), Function :: atom(), Arity :: integer()) -> function().
f_curry(Module, Function, Arity) ->
case curry(Module, Function, Arity) of
{ok, F} -> F;
Error -> error(Error)
end.
% @doc
% Returns a curried equivalent of the provided function, with the specified deep or params.
%
%
% buclambda:curry(lists, keystore, 4, 4).
% % => {ok, #Fun<erl_eval.6.99386804>}
% % => fun(A0) -> fun(A1) -> fun(A2) -> fun(A3) -> lists:keystore(A0, A1, A2, A3) end end end end.
%
% buclambda:curry(lists, keystore, 4, 3).
% % => {ok, #Fun<erl_eval.12.99386804>}
% % => fun(A0, A1) -> fun(A2) -> fun(A3) -> lists:keystore(A0, A1, A2, A3) end end end.
%
% buclambda:curry(lists, keystore, 4, 2).
% % =>{ok, #Fun<erl_eval.18.99386804>}
% % => fun(A0, A1, A2) -> fun(A3) -> lists:keystore(A0, A1, A2, A3) end end.
%
% buclambda:curry(lists, keystore, 4, 1).
% % => {ok, #Fun<erl_eval.4.99386804>}
% % => fun(A0, A1, A2, A3) -> lists:keystore(A0, A1, A2, A3) end.
%
% {ok, F} = buclambda:curry(lists, keyfind, 3, [toto, 1]).
% F([{tata, 1}, {toto, 2}, {titi, 3}]).
% % => {toto, 2}
% F([{tata, 1}, {tutu, 2}, {titi, 3}]).
% % => false
%
% @end
-spec curry(Module :: module(), Function :: atom(), Arity :: integer(), DeepOrArgs :: integer() | [term()]) ->
{ok, function()} | error.
curry(Module, Function, Arity, Deep) when is_integer(Deep) ->
currify({Module, Function}, Arity, Deep, lr);
curry(Module, Function, Arity, Args) when is_list(Args) ->
case curry(Module, Function, Arity, Arity - length(Args) + 1) of
{ok, Fun} -> {ok, erlang:apply(Fun, Args)};
Other -> Other
end.
% @doc
% Same as buclambda:curry/4 but fails with an exception on error
% @end
-spec f_curry(Module :: module(), Function :: atom(), Arity :: integer(), DeepOrArgs :: integer() | [term()]) ->
function().
f_curry(Module, Function, Arity, Deep) ->
case curry(Module, Function, Arity, Deep) of
{ok, F} -> F;
Error -> error(Error)
end.
% @doc
% Returns a reverse curried equivalent of the provided function.
%
%
% buclambda:rcurry(lists, keystore, 4).
% % => {ok, #Fun<erl_eval.6.99386804>}
% % => fun(A3) -> fun(A2) -> fun(A1) -> fun(A0) -> lists:keystore(A0, A1, A2, A3) end end end end.
%
% @end
-spec rcurry(Module :: module(), Function :: atom(), Arity :: integer()) ->
{ok, function()} | error.
rcurry(Module, Function, Arity) ->
currify({Module, Function}, Arity, Arity, rl).
% @doc
% Same as buclambda:rcurry/3 but fails with an exception on error
% @end
-spec f_rcurry(Module :: module(), Function :: atom(), Arity :: integer()) -> function().
f_rcurry(Module, Function, Arity) ->
case rcurry(Module, Function, Arity) of
{ok, F} -> F;
Error -> error(Error)
end.
% @doc
% Returns a reverse curried equivalent of the provided function, with the specified deep or params.
%
%
% buclambda:rcurry(lists, keystore, 4, 4).
% % => {ok, #Fun<erl_eval.6.99386804>}
% % => fun(A3) -> fun(A2) -> fun(A1) -> fun(A0) -> lists:keystore(A0, A1, A2, A3) end end end end.
%
% buclambda:rcurry(lists, keystore, 4, 3).
% % => {ok, #Fun<erl_eval.12.99386804>}
% % => fun(A3, A2) -> fun(A1) -> fun(A0) -> lists:keystore(A0, A1, A2, A3) end end end.
%
% buclambda:rcurry(lists, keystore, 4, 2).
% % => {ok, #Fun<erl_eval.18.99386804>}
% % => fun(A3, A2, A1) -> fun(A0) -> lists:keystore(A0, A1, A2, A3) end end.
%
% buclambda:rcurry(lists, keystore, 4, 1).
% % => {ok, #Fun<erl_eval.4.99386804>}
% % => fun(A3, A2, A1, A0) -> lists:keystore(A0, A1, A2, A3) end.
%
% {ok, F} = buclambda:rcurry(lists, keyfind, 3, [[{tata, 1}, {toto, 2}, {titi, 3}], 1]).
% F(toto).
% % => {toto, 2}
% F(tutu).
% % => false
%
% @end
-spec rcurry(Module :: module(), Function :: atom(), Arity :: integer(), DeepOrArgs :: integer() | [term()]) ->
{ok, function()} | error.
rcurry(Module, Function, Arity, Deep) when is_integer(Deep) ->
currify({Module, Function}, Arity, Deep, rl);
rcurry(Module, Function, Arity, Args) when is_list(Args) ->
case rcurry(Module, Function, Arity, Arity - length(Args) + 1) of
{ok, Fun} -> {ok, erlang:apply(Fun, Args)};
Other -> Other
end.
% @doc
% Same as buclambda:rcurry/4 but fails with an exception on error
% @end
-spec f_rcurry(Module :: module(), Function :: atom(), Arity :: integer(), DeepOrArgs :: integer() | [term()]) ->
function().
f_rcurry(Module, Function, Arity, Deep) ->
case rcurry(Module, Function, Arity, Deep) of
{ok, F} -> F;
Error -> error(Error)
end.
% -----------------------------------------------------------------------------
currify(Function, Arity, Deep, Orient) when ((is_tuple(Function) andalso
size(Function) == 2 andalso
is_atom(element(1, Function)) andalso
is_atom(element(1, Function)))
orelse is_function(Function, Arity)),
is_integer(Arity),
is_integer(Deep),
Deep =< Arity,
(Orient == lr orelse Orient == rl) ->
FunStr = lists:flatten([currify(Function, Arity, Deep, Deep, Orient), "."]),
io:format("% => ~s~n% => ", [FunStr]),
case erl_scan:string(FunStr) of
{ok, Scanned, _} ->
case erl_parse:parse_exprs(Scanned) of
{ok, Parsed} ->
case erl_eval:exprs(Parsed, []) of
{value, Fun, _Env} -> {ok, Fun};
_ -> error
end;
_Error ->
error
end;
_Error ->
error
end;
currify(_Function, _Arity, _Deep, _Orient) ->
error.
currify({Module, Function}, Arity, _Deep, 0, _Orient) ->
io_lib:format("~p:~p(~s)", [Module, Function, string:join(args(0, Arity, 1), ", ")]);
% TODO: currify(Function, Arity, _Deep, 0, _Orient) when is_function(Function) ->
% TODO: io_lib:format("(~s)(~s)", [fun_to_string(Function), string:join(args(0, Arity, 1), ", ")]);
currify(Function, Arity, Deep, N, lr) when N == Deep ->
NB = Arity - (Deep - 1),
io_lib:format("fun(~s) -> ~s end", [string:join(args(0, NB, 1), ", "), currify(Function, Arity, Deep, N - 1, lr)]);
currify(Function, Arity, Deep, N, lr) ->
io_lib:format("fun(A~p) -> ~s end", [(Deep - N + 1) + (Arity - (Deep + 1)), currify(Function, Arity, Deep, N - 1, lr)]);
currify(Function, Arity, Deep, N, rl) when N == Deep ->
NB = Arity - (Deep - 1),
io_lib:format("fun(~s) -> ~s end", [string:join(args(Arity - 1, NB, -1), ", "), currify(Function, Arity, Deep, N - 1, rl)]);
currify(Function, Arity, Deep, N, rl) ->
io_lib:format("fun(A~p) -> ~s end", [N - 1, currify(Function, Arity, Deep, N - 1, rl)]).
args(_Start, 0, _Add) -> [];
args(Start, N, Add) ->
[lists:flatten(io_lib:format("A~p", [Start]))|args(Start + Add, N - 1, Add)].
get_fun_info(Fun) ->
Infos = erlang:fun_info(Fun),
{module, Module} = lists:keyfind(module, 1, Infos),
{name, Function} = lists:keyfind(name, 1, Infos),
{arity, Arity} = lists:keyfind(arity, 1, Infos),
case lists:member({Function, Arity}, Module:module_info(exports)) of
true ->
{Module, Function, Arity};
false ->
{Fun, Arity}
end.
% TODO: fun_to_string(Fun) ->
% TODO: {env, [{_, _, _, Abs}]} = erlang:fun_info(Fun, env),
% TODO: lists:flatten(erl_pp:expr({'fun', 1, {clauses, Abs}})).