quark icon indicating copy to clipboard operation
quark copied to clipboard

Add multiple anon function arities in defpartial

Open expede opened this issue 8 years ago • 5 comments

Current behaviour for defpartial is to give multiple options for the first invocation, but then you have to apply single arguments after that. It should return anonymous functions with multiple heads after being invoked to give a more true partial application.

expede avatar Oct 21 '16 08:10 expede

@OvermindDL1 expanded on "tuple calls" (see #25), and he also mentioned that it is going away. Returning anonymous functions with different arity heads is also an error.

iex(6)> fn 
...(6)> (a) -> a    
...(6)> (a,b) -> {a,b}
...(6)> end
** (CompileError) iex:6: cannot mix clauses with different arities in anonymous functions

The main problem is that only fixed arity lambdas can be returned in a function call (and this applies to macros as well).

toraritte avatar Mar 21 '19 23:03 toraritte

A simple (but ugly) solution:

defmodule A do
  defmacro defpartialx({fun_name, ctx, args}, do: body) do
    scanned_args = [[]] ++ args_scan(args)
    quote do
      unquote do: make_curried_clauses(scanned_args, {fun_name, ctx, body})
    end
  end

  defp make_curried_clauses([args], {fun_name, ctx, body}) do
    quote do
      def unquote({fun_name, ctx, args}), do: unquote(body)
    end
  end

  defp make_curried_clauses([args|rest], {fun_name, ctx, _} = fun_attrs) do
    quote do
      def unquote({fun_name, ctx, args}) do
        &apply(__MODULE__, unquote(fun_name), unquote(args) ++ List.wrap(&1))
      end
      unquote do: make_curried_clauses(rest, fun_attrs)
    end
  end
  defp args_scan(args), do: Enum.scan(args, [], &(&2 ++ [&1]))
end

defmodule B do
  import A
  defpartialx m(a,b,c), do: a-b-c
end

iex(28)> B.m.([1,2,3,4,5])                      
-13
iex(29)> B.m(1,2).([3,4,5])
-13
iex(31)> B.m(1,2).([3,4]).(5)
-13
iex(32)> B.m.([1,2]).([3,4]).(5)
-13
iex(33)> B.m(1).(2).([3,4]).(5) 
-13
iex(34)> B.m(1).([2,3]).([4,5])
-13         

Had to re-write Quark.defpartial/2 because I couldn't figure out the way it is creating clauses.


Note to self:

Quark.defpartial/2:

defpartial m(a,b,c), do: a-b-c
      |
      V
def m(), do: fn(a) -> fn(b) -> fn(c) -> a-b-c end end end
def m(a),     do:    m().(a)
def m(a,b),   do:   m(a).(b)
def m(a,b,c), do: m(a,b).(c)
# if I understood that correctly

and defpartialx/2:

def new(),    do: fn(a) -> apply(__MODULE__, :new, [a]          ) end
def new(a),   do: fn(b) -> apply(__MODULE__, :new, [a]    ++ [b]) end
def new(a,b), do: fn(c) -> apply(__MODULE__, :new, [a, b] ++ [c]) end
def new(a, b, c), do: a - b - c

toraritte avatar Mar 22 '19 03:03 toraritte

I'm facing this issue too. Trying to use partial applied function in an Enum.reduce I've got an arity exception.

korsmakolnikov avatar Feb 25 '20 22:02 korsmakolnikov

@korsmakolnikov You can work around it by returning a 2-arg function from your function directly, bit of a hack but it works.

OvermindDL1 avatar Feb 25 '20 22:02 OvermindDL1

I know. The purpose of this macro is to have the strait tool as you could have in a functional ML-like-thing, I think...

korsmakolnikov avatar Apr 08 '20 11:04 korsmakolnikov