# Tuple Calls

BEAM is a register-based virtual machine, not a stack-based one. There are X and Y registers (1024 of both). X registers are where regular operations happen. Y registers are slots on the BEAM stack. Arguments to a function are passed in X0-X(n-1) registers. The return value is always in the X0 register. The X registers are caller-saved.

EDIT: registers are 0-indexed, not 1-indexed.

3 Likes

Given the following code:

``````defmodule Test do
def test(x, y) do
x + y
end

def test_first do
x1 = test(1, 1)
x2 = test(x1, 1)
test(x2, 1)
end

def test_last do
x1 = test(1, 1)
x2 = test(1, x1)
test(1, x2)
end
end
``````

We get the following assembly (stripped down for brevity)

``````{function, test_first, 0, 9}.
{label,8}.
{func_info,{atom,'Elixir.Test'},{atom,test_first},0}.
{label,9}.
{allocate,0,0}.
{move,{integer,1},{x,1}}.
{move,{integer,1},{x,0}}.
{call,2,{f,7}}.
{move,{integer,1},{x,1}}.
{call,2,{f,7}}.
{move,{integer,1},{x,1}}.
{call_last,2,{f,7},0}.

{function, test_last, 0, 11}.
{label,10}.
{func_info,{atom,'Elixir.Test'},{atom,test_last},0}.
{label,11}.
{allocate,0,0}.
{move,{integer,1},{x,1}}.
{move,{integer,1},{x,0}}.
{call,2,{f,7}}.
{move,{x,0},{x,1}}.
{move,{integer,1},{x,0}}.
{call,2,{f,7}}.
{move,{x,0},{x,1}}.
{move,{integer,1},{x,0}}.
{call_last,2,{f,7},0}.
``````

The last-argument-chaining version has the extra move instruction to move from the return position in X0 to the last argument position in X1. But as I said before, this level of difference, probably wonât matter in real-life programs, since there are additional instruction-fusions and optimisations going on in the loader.

2 Likes

Exactly my point.

It only really matters on a tight looping/recursive function, where it did provide a sizable performance difference when I tested it long ago.

(Sent too soon, so edit:)
And I know the BEAM is register based, but when it executes it then it ran it as a stack (or the JIT did, one or the otherâŠ).

2 Likes

The main drawback of piping with the first argument is that it conflicts with partial function application.

Partial function application is a very powerful tool, and should be more well-known within the Elixir community:

``````iex> [1,2,3] |> FunLand.map(Currying.curry(&+/2)) |> FunLand.apply_with([10, 11, 12])
[14, 15, 16, 15, 16, 17, 16, 17, 18, -6, -7, -8, -5, -6, -7, -4, -5, -6, 40, 44,
48, 50, 55, 60, 60, 66, 72]
``````
``````iex> import Currying
iex> [curry(&+/2), curry(&-/2), curry(&*/2)] |> FunLand.apply_with([4,5,6]) |> FunLand.apply_with([10,11,12])
[14, 15, 16, 15, 16, 17, 16, 17, 18, -6, -7, -8, -5, -6, -7, -4, -5, -6, 40, 44,
48, 50, 55, 60, 60, 66, 72]
``````
``````iex> maybe_num1 = FunLandic.Maybe.just(10)
iex> maybe_num2 = FunLandic.Maybe.just(20)
iex> FunLand.map(maybe_num1, Currying.curry(&+/2)) |> FunLand.apply_with(maybe_num2)
FunLandic.Maybe.just(30)
iex> maybe_num2 = FunLandic.Maybe.nothing()
iex> FunLand.map(maybe_num1, Currying.curry(&+/2)) |> FunLand.apply_with(maybe_num2)
FunLandic.Maybe.nothing()
``````

Partial function application (and its flip-side, currying) are very powerful functional programming techniques, but they have been unsupported by Erlang (probably because of its basis in Prolog, which is also where it obtained its âfunctions with different arities are different functionsâ notion that is definitely related to this, from), the only possibility being to build a library-level wrapper around it that will not really be able to be optimized by the compiler.

(I know of two approaches:

1. Create a macro that defines function clauses with less parameters given than required, that returns clauses to the higher-level function. This is what the curry library does. Main drawback: Impossible to use for functions with multiple clauses.
2. Check the actual arity of the called function, and if the amount of supplied arguments is less, create a new anonymous function where the rest of the parameters could be passed in. This is what the Currying library does. (disclaimer: I wrote Currying)

)
Neither approach is probably very performant, as they are library-level constructs, rather than language-level.

That being said, Elixirâs `&` shorthand function syntax is a good alternative for most situations. (I donât remember where, but JosĂ© mentioned it as âElixirâs best featureâ somewhere. If he was jesting or serious, I do not know though ).

5 Likes

I donât remember saying that but probably jesting as I donât consider it the best feature.

In any case, currying was thrown out of the window when we decided to be fully compatible with Erlang (i.e. name/arity) and also due to its performance costs. Dynamic loading and lack of a static type system means it is hard to avoid creating intermediate lambdas - which makes currying expensive. For better or worse, it is an idiom that is unlikely to ever be first-class in Elixir.

5 Likes

I still think there could be an Elixir-with-typing for that. ^.^

2 Likes

Iâve kind of wondered, wouldnât it be possible to have another operator, maybe â<|â that pipes in from the right? Just sayinââŠ

2 Likes

That is common in other languages, especially ML/Haskellây languages, like:

``````42
|> someFunc 6.28 <| anotherFunc "bloop"
|> moreFunc
``````

Would be this in elixirâise:

``````42
|> someFunc(6.28, anotherFunc("bloop"))
|> moreFunc()
``````

Although in the MLây/Haskellây languages the `<|` expands to basically this:

``````42
|> someFunc 6.28 (anotherFunc "bloop")
|> moreFunc
``````

In this short example it doesnât really show it off, but it is most used in basically preventing having to use (usually a lot more than what is shown here) of parenthesis.

But yes, you could make something like that in Elixir too, but eh.

3 Likes

When tuple calls are that useful for some and accidents for others, is it potentially senseful to default for errors and make it optionally possible via a compiler flag or so.

I think this thread is worth closing. A lot of good discussion happened initially, but itâs been resurrected twice now months after the fact.

Substantive efforts to develop the conversation around tuple calls can, if desired, happen in a new thread dedicated to that direction.

3 Likes

Yes. Tuple calls are already disabled by default on Erlang/OTP 21 and they explicitly require a `@compile` option on every module that needs them.

Therefore even more reason to close this thread.

5 Likes