cpackingham
Elixir equivalent of the spread operator?
How do I use an arbitrary number of arguments in an anonymous function?
Ex. in JavaScript
…args
Ex. in Python
*args
I need to be able to take any number of arguments in and convert them into a list with an anonymous function.
Most Liked
OvermindDL1
To ‘spread’ you have to call apply:
╰─➤ iex
Erlang/OTP 20 [erts-9.1] [source] [64-bit] [smp:2:2] [ds:2:2:10] [async-threads:10] [hipe] [kernel-poll:false]
Interactive Elixir (1.6.0-dev) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> blah = fn(a, b, c, d) -> a+b+c+d end
#Function<4.99386804/4 in :erl_eval.expr/5>
iex(2)> args = [1,2,3,4]
[1, 2, 3, 4]
iex(3)> apply(blah, args)
10
iex(4)> defmodule Bloop do def bleep(a, b, c, d), do: a+b+c+d end
{:module, Bloop,
<<70, 79, 82, 49, 0, 0, 4, 32, 66, 69, 65, 77, 65, 116, 85, 56, 0, 0, 0, 116,
0, 0, 0, 13, 12, 69, 108, 105, 120, 105, 114, 46, 66, 108, 111, 111, 112, 8,
95, 95, 105, 110, 102, 111, 95, 95, 9, ...>>, {:bleep, 4}}
iex(5)> apply(Bloop, :bleep, args)
10
iex(6)> apply(Bloop, :bleep, [0 | args])
** (UndefinedFunctionError) function Bloop.bleep/5 is undefined or private. Did you mean one of:
* bleep/4
Bloop.bleep(0, 1, 2, 3, 4)
Do note, this is an indirect call so although still cheap enough, don’t call it in a tight loop where performance is a top necessity, but otherwise it’s perfectly fine to use. ![]()
For that you’ll need a macro. Functions on the BEAM are like functions in C++ or so, they are defined by a name and arity, thus the arity has to match to be called. You can generate many functions that take each count of args and return that, but a macro can do it inline, however you cannot make that anonymous for obvious reasons (ran at compile-time, not run-time). ^.^
To take an arbitrary number of arguments that are not in the ‘arity’ you should pass in a list, or map, or whatever structure is appropriate. ![]()
pawaclawczyk
In your code {...r, sort: index + 1} is equivalent to Map.put(r, :sort, index + 1).
In Elixir you don’t need the spread operator to make copy, because variables are immutable.
You can use the mentioned construct of %{r | sort: index + 1} if you are sure that key sort exists in r. Map.put will create a key if it does not exist.
However, the outer map of responders.map((r, index) => ({…r, sort: index + 1 })) will not work without a little help. If you use Enum.map you pass a function of single argument into it. It won’t receive the index of item. You must zip the values with indices first i.e. using Enum.with_index, but remember that since now you will have list of two element tuples - {value, index}.
So
responders.map((r, index) => ({…r, sort: index + 1 }))
can be written as
responders
|> Enum.with_index(1)
|> Enum.map(fn {r, index} -> Map.put(r, :sort, index) end)
More info and great examples you can find in Enum documentation.
zkessin
The BEAM does not support variable arity functions. So you have to use lists or the like
peerreynders
{obj..., a: 1, b: 2, c: 3}
-
If the map already has a
:sortatom key you could simply use the%{r | sort: index + 1}update syntax sugar; that syntax can accomodate multiple key updates%{map | a: 1, b: 2, c: 3} -
If the key may not exist use
Map.put/2-Map.put(map, :b, 2), for multiple keys you can useMap.merge/2-Map.merge(map,%{a: 1, b: 2, c: 3})
const {a, b, c, ...rest} = obj
iex(1)> map = %{a: 1, b: 2, c: 3, d: 4}
%{a: 1, b: 2, c: 3, d: 4}
iex(2)> isolate = [:a, :b, :c]
[:a, :b, :c]
iex(3)> {%{a: a, b: b, c: c}, rest} = Map.split(map, isolate)
{%{a: 1, b: 2, c: 3}, %{d: 4}}
iex(4)> IO.puts("#{inspect a} #{inspect b} #{inspect c} #{inspect rest}")
1 2 3 %{d: 4}
:ok
iex(5)> taken = Map.take(map, isolate)
%{a: 1, b: 2, c: 3}
iex(6)> IO.puts("#{inspect taken}")
%{a: 1, b: 2, c: 3}
:ok
iex(7)> other = Map.delete(map, :a)
%{b: 2, c: 3, d: 4}
iex(8)> IO.puts("#{inspect other}")
%{b: 2, c: 3, d: 4}
:ok
iex(9)> left = Map.drop(map, isolate)
%{d: 4}
iex(10)> IO.puts("#{inspect left}")
%{d: 4}
:ok
iex(11)>
peerreynders
Enum.map/1 doesn’t supply an index - that is a JavaScript Array thing.
defmodule Demo do
#
# version using comprehension
# https://elixir-lang.org/getting-started/comprehensions.html
def adjust_sort(list, offset \\ 0),
do: for({m, index} <- Enum.with_index(list, offset), do: %{m | sort: index})
#
# version using recursion
def adjust_sort2(list, offset \\ 0),
do: adjust_sort(list, offset, [])
# base/terminal case. Processed entire list. Reverse it to get original order
defp adjust_sort([], _, list),
do: :lists.reverse(list)
# http://erlang.org/doc/man/lists.html#reverse-1
# recursive case. Update sort value on this element, recurse on next element
defp adjust_sort([h | t], index, rest),
do: adjust_sort(t, index + 1, [%{h | sort: index} | rest])
#
# version using reduce/foldl
def adjust_sort3(list, offset \\ 0) do
{result, _} = List.foldl(list, {[], offset}, &reducer/2)
:lists.reverse(result)
end
defp reducer(m, {rest, index}),
do: {[%{m | sort: index} | rest], index + 1}
#
# version using Enum.map_reduce
def adjust_sort4(list, offset \\ 0) do
{result, _} = Enum.map_reduce(list, offset, &map_reducer/2)
result
end
defp map_reducer(m, index),
do: {%{m | sort: index}, index + 1}
end
r_list = [
%{first_name: "dave", last_name: "boo", sort: 9999},
%{first_name: "pete", last_name: "street", sort: 9999}
]
IO.inspect(Demo.adjust_sort(r_list, 1))
IO.inspect(Demo.adjust_sort2(r_list, 1))
IO.inspect(Demo.adjust_sort3(r_list, 1))
IO.inspect(Demo.adjust_sort4(r_list, 1))
$ elixir demo.exs
[
%{first_name: "dave", last_name: "boo", sort: 1},
%{first_name: "pete", last_name: "street", sort: 2}
]
[
%{first_name: "dave", last_name: "boo", sort: 1},
%{first_name: "pete", last_name: "street", sort: 2}
]
[
%{first_name: "dave", last_name: "boo", sort: 1},
%{first_name: "pete", last_name: "street", sort: 2}
]
[
%{first_name: "dave", last_name: "boo", sort: 1},
%{first_name: "pete", last_name: "street", sort: 2}
]
$
I think the Enum.map_reduce/3 version is the closest in spirit for what you are looking for.







