Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Currying Functions Erlang

I'm trying to redo all of my Haskell homework problems using Erlang, and one thing that gets me is how to use a list of functions that don't have all of their parameters.

Example: I'm trying to use this fold, but I don't know how to pass in the functions so that it operates on the accumulator

%%inside my module)
add(X,Y) -> X + Y.

multiply(X,Y) -> X*Y.

Afterwards using this in the command line:

lists:foldl(fun(Function,Accumulator) -> Function(Accumulator) end, 3, [add(3),multiply(5)]).
like image 232
Kelsey Abreu Avatar asked Apr 24 '13 05:04

Kelsey Abreu


4 Answers

In terms of native Erlang there is not any form of partial evaluation like you want. You will have to create your own fun's to do it. However if you use the Erlando Monad Library then you can use pattern matching to create it. It works by the fact that the erlang compiler lets you play with the AST on compiling code so you can do cool stuff like this.

like image 179
Zachary K Avatar answered Nov 16 '22 03:11

Zachary K


-module(f).
-export([curry/1]).

curry(AnonymousFun) ->
    {arity, Arity} =
        erlang:fun_info(AnonymousFun, arity),

    do_curry(AnonymousFun, Arity, [[], [], []]).

do_curry(Fun, 0, [Fronts, Middle, Ends] = X) ->
    % Fronts ++ Middle ++ ")" ++ Ends;
    [F, M, E] =
        lists:map(fun(L) -> string:join(L, "") end, X),
    Fstring =
        F ++ "Run(" ++ string:trim(M, trailing, ",") ++ ")" ++ E,

    {ok, Tokens, _} =
        erl_scan:string(Fstring ++ "."),
    {ok, Parsed} =
        erl_parse:parse_exprs(Tokens),

    FunBinding =
        erl_eval:add_binding(
          'Run',
          Fun,
          erl_eval:new_bindings()
        ),
    {value ,CurriedFun, _} =
        erl_eval:exprs(Parsed, FunBinding),

    CurriedFun;

do_curry(Fun, Arity, [Fronts, Middle, Ends]) ->
    VarName = [64 + Arity],
    NewFronts = ["fun(" ++ VarName ++ ") -> " | Fronts] ,
    NewMiddle = [VarName ++ ","|Middle],
    NewEnds = [" end"|Ends],
    do_curry(Fun, Arity-1, [NewFronts, NewMiddle, NewEnds]).

Usage (noise culled from shell output):

72> c("./f") % If `f.erl` is in the current dir that is.

73> F = f:curry(fun(A,B,C) -> A + B + C end).

74> F(1).
75> G = F(1).
76> G(2).
77> H = G(2).
78> H(3).
6

79> I = (F(1))(2).
80> I(3).
6

82> F2 = mtest:curry(fun erlang:'++'/2).  

83> F2("lofa").

84> (F2("lofa"))("miez").
"lofamiez"

85> ((f:curry(fun lists:map/2))((f:curry(fun filename:join/2))("dir")))(["a_file", "b_file"]).
["dir/a_file","dir/b_file"]

Useful resources:

  • http://erlang.org/pipermail/erlang-questions/2005-April/015279.html
  • https://grantwinney.com/how-to-evaluate-a-string-of-code-in-erlang-at-runtime/
  • Convert erlang terms to string, or decode erlang binary
  • Erlang trying to evaluate a string
  • Knowing the number of parameters of a passed function (erlang)
  • https://erlang.org/doc/man/erl_eval.html
like image 24
toraritte Avatar answered Nov 16 '22 04:11

toraritte


In Erlang you must call function passing all parameters it requires. But you can easily avoid it by creating an anonymous function which takes only those parameters you need and then calls your function rightly. If you need a function which takes one parameter X and calls function add(3, X) you can create an anonymous function like that:

fun (X) -> add(3, X) end

This is an example for your task:

lists:foldl(fun (Function, Accumulator) -> Function(Accumulator) end, 3,
    [fun (X) -> add(3, X) end, fun (X) -> multiply(5, X) end]).
like image 33
Danil Onishchenko Avatar answered Nov 16 '22 04:11

Danil Onishchenko


One can fairly easily write a partial application function which is called analogous way to erlang:apply/3. It lacks the elegance you have in languages that support currying.

-module(partial).

-export([apply/4]).

apply(Module, Name, Arity, Args) when length(Args) < Arity ->
    Left = Arity - length(Args),
    fun(Args1) when length(Args1) < Left ->
            fun(Args2) ->
                apply(Module, Name, Arity, Args2 ++ Args1 ++ Args)
            end;
       (Args1) when length(Args1) > Left ->
            erlang:error(badarg);
       (Args1) ->
            erlang:apply(Module, Name, Args1 ++ Args)
    end;
apply(_, _, Arity, Args) when length(Args) > Arity ->
    erlang:error(badarg);
apply(Module, Name, _, Args) ->
    erlang:apply(Module, Name, Args).
like image 38
Jan Henry Nystrom Avatar answered Nov 16 '22 02:11

Jan Henry Nystrom