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.
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.
Arity is fixed. So you have to pass a list to begin with. Often keyword lists are used.
Examples:
Kernel.spawn/3
- the third argument args
is a list of indeterminate length.
Supervisor.start_link/3
where options
is a keyword list.
The List module has some special functions like List.keyfind/4
that can be used with keyword lists.
Map.new/1
converts a keyword list to a map - though when a key is duplicated only one key-value is kept.
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.
I never got an answer that I could use on this, but I’m still very new to Elixir. I was trying to add a sort value to an existing list of maps. I ended up just passing the list down to my client and added it using the javascript function below, but I’d love to know how Elixir handles this?
sortResponders(auction){
return auction.responders.map((r, index) => ({…r, sort: index + 1 }));
}
The BEAM does not support variable arity functions. So you have to use lists or the like
Thanks zkessin…I figured I just need to get stronger on lists, maps, etc. The js workaround will do until I get there.
{obj..., a: 1, b: 2, c: 3}
If the map already has a :sort
atom 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 use Map.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)>
Thanks peerreynders, but like I said, I need to go back through my books/courses and work on lists. I’m assuming I would add that inside my Enum.map function to update my list? My sample data structure is as follows (I want dave to have sort: 1, and pete to have sort: 2) :
iex(1)> r_list = [%{first_name: "dave", last_name: "boo", sort: 9999}, %{first_name: "pete", last_name: "street", sort: 9999}]
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.
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.
Yes, the documentation led me down the Enum.with_index path, but the tuples tripped me up. I ran into time constraints, and went for the workaround. Thanks, and I hope my newbness helps others reading your suggestions.
Yes, now that I’ve had sleep and coffee, map_reduce/3 looks like something I could make work.