Given this Python code:
def f(k):
n = 2 << k
q = 2
i = 1
while q != n:
i *= q
q <<= 1
return i
The Elixir translation is roughly the following:
def f(k) do
n = 2 <<< k # Remember to add "use Bitwise" at the top of the module
q = 2
i = 1
# (Storing the output into a tuple for reasons that will be explained later)
{i, _, _} = Stream.unfold({q, i}, fn # Initial values: current scope's q and i
{^n, _} -> nil # Loop halt condition: q == n (fixing the current scope's value of n)
{q, i} -> # Loop takes 2 arguments: q, i (inside a tuple)
i = i * q
{ # Return a 2-tuple to the `Stream.unfold` interface:
# 1st element gets yielded to the outer scope:
{i, :fyi, "You can return multiple values here if you want to"},
# 2nd element is fed back in as the argument to the next iteration of the loop:
{q <<< 1, i}
}
end)
|> Enum.take(-1) # Stream.unfold yields all values; only take the last
|> hd # Convert 1-element list into its component element
i
end
Generalizing further, we get:
hd(Enum.take(Stream.unfold({nil, VARS}, fn
{:halt, _} -> nil
{_, VARS} ->
WHILE_BODY(VARS) # _do_ modify VARS within this
{RETVAL(VARS), {if(EXIT_PREDICATE(VARS), do: :halt), VARS}}
end), -1))
(where VARS
may be either a bare variable or a tuple of them)
TODO: translate that last codeblock into defmacro while…
Of course, the above code is a maximalist, generalizable example—it is a template that you can take and fill with the most convoluted looped code you care to write (so long as you enumerate the variables that get changed each loop and shunt them away into the return value); in reality, computing the above function per se would be very simple since it's inherently a kind of accumulation; something like the following would do:
def f(k) do
q = 2
Enum.product(Stream.map(0..(k-1), fn k -> (q <<< k) end))
end