Reputation: 782
Probably a very basic Elixir question,
I want to sum the even number from a 1 to 10 and output it to IO.puts
First I try doing it this way:
1..10
|> Enum.filter(fn (x) -> rem(x, 2) == 0 end)
|> Enum.sum
|> IO.puts
Which works as expected.
Then I try defining that function inside a module as such:
defmodule Test do
def is_even(x) do
rem(x, 2) == 0
end
end
1..10
|> Enum.filter(Test.is_even)
|> Enum.sum
|> IO.puts
But this gives me the following error on compilation:
** (UndefinedFunctionError) undefined function: Test.is_even/0
Test.is_even()
tmp/src.exs:8: (file)
(elixir) src/elixir_lexical.erl:17: :elixir_lexical.run/3
(elixir) lib/code.ex:316: Code.require_file/2
Why is it looking for is_even/0 when it should( in my intention) be looking for is_even/1?
I don't understand why this is, especially since doing this:
defmodule Test do
def hello(x) do
IO.puts(x)
end
end
Test.hello("Hello World!")
Works completely fine.
I also just found out this works:
defmodule Test do
def is_even() do
fn (x) -> rem(x, 2) == 0 end
end
end
1..10
|> Enum.filter(Test.is_even)
|> Enum.sum
|> IO.puts
Why is it using the return of the function to use as a function instead of using the function itself?
Is there a way to make this work without having to return anonymous functions inside functions?
Upvotes: 2
Views: 359
Reputation: 4906
Yes, what you want to do is possible and easy. You are trying to pass a function around as you would other values in Elixir, which is possible. For example:
f = &IO.puts/1
This assigns IO.puts/1
to the variable f
, which can be used like other values. Whenever referencing a function like this you must include the arity of the function, which in your case is 1
because your function takes one argument. When calling functions the arity is implicit because you are passing in a number of arguments, but when you are referencing a function this way you must specify the arity manually because there could be multiple versions of your is_even
function with different arities and the VM won't know which one you want.
You code should look like this:
defmodule Test do
def is_even(x) do
rem(x, 2) == 0
end
end
1..10
|> Enum.filter(&Test.is_even/1)
|> Enum.sum
|> IO.puts
Upvotes: 1
Reputation: 206
defmodule Test do
def is_even(x) do
rem(x, 2) == 0
end
end
1..10
|> Enum.filter(Test.is_even)
|> Enum.sum
|> IO.puts
|> Enum.filter(Test.is_even)
is the line that doesn't work because of how the pipe operator works. The pipe operator will put the result of the expression before it (1..10
) as the first argument for Enum.filter/2
, but it won't put it as an argument for Test.is_even/1
since it is inside filter
.
That line is like writing: Enum.filter 1..10, Test.is_even()
, no argument is passed for Test.is_even
, so it looks for a 0-arity function but it couldn't find one.
The is_even/0
on your last example works because that function returns an anonymous function which is the same as the first example you gave.
Upvotes: 1
Reputation: 10051
Unlike some common languages, Elixir (and Erlang and probably all BEAM based languages) allow you to define multiple functions with the same name. Elixir looks to try and match the function call with a definition that has the same arity that you called it with.
Arity is the number of arguments a function takes. For example, the following function has an arity of 0
def foo() do
IO.puts "foo"
end
while this function
def bar(x) do
IO.puts "bar"
end
has an arity of 1, even though the argument goes unused.
With your module
defmodule Test do
def is_even(x) do
rem(x, 2) == 0
end
end
You have defined the function is_even
with an arity of 1.
When you tried to call your function Test.is_even
in your chain, you called it as if it had an arity of 0. If you want to specify that your function is expected to be of arity 1, you can use the following notation, &Test.is_even/1
. So your example would become
1..10
|> Enum.filter(&Test.is_even/1)
|> Enum.sum
|> IO.puts
Upvotes: 3