1

I can write an anonymous function in the interactive shell directly like below.

iex> total_bottles_milk = fn total -> total * 2 end  
iex> total_bottles_milk.(2)

However, if I write in an external file and run in the interactive shell, it shows a Compile Error.

My file name and directory path is lib/expense.ex

Below is my code

defmodule Expense do

    total_bread_slices = fn total -> (total * 10) / 100 end
    total_bottles_milk = fn total -> total * 2 end
    total_cakes = fn total -> total * 15 end

    def total_expense(bread_slices, bottles_of_milk, cakes) do
        total_bread_slices.(bread_slices) + total_bottles_milk.(bottles_of_milk) + total_cakes.(cakes)
    end

end

When I go into the folder path and run iex -S mix to run my Expense module, terminal shows Compilation error.
I'm wondering only I can run anonymous function directly into the interactive shell and not from external sources and compile it. I want to write my function as a first-class citizen. And if there is a way, how can I do it?

  • 1
    What do you mean by "first-class citizen" here? You can define a named function like `def total_cokes(total), do: total * 15` and then create an anonymous function by doing `&total_cokes/1`. Returning anonymous functions from within normal functions (like your answer) is not idiomatic. – Dogbert Jul 10 '18 at 15:27
  • Ah...I got it now!! Thanks for ur guide. – pyae phyo hlaing Jul 11 '18 at 07:30

4 Answers4

5

You cannot create "variables" like this in elixir (see EDIT 1 and Edit 2 below). The error you're seeing is normal.

You can put your anonymous functions into named functions and call them from there which would give you the same result:

defmodule Expense do

  def total_expense(bread_slices, bottles_of_milk, cakes) do
    total_bread_slices().(bread_slices) + total_bottles_milk().(bottles_of_milk) + total_cakes().(cakes)
  end

  defp total_bread_slices, do: fn total -> (total * 10) / 100 end
  defp total_bottles_milk, do: fn total -> total * 2 end
  defp total_cakes, do: fn total -> total * 15 end
end

This way you're calling the named function which will return the anonymous function which then you pass the arguments to.

EDIT 1

You cannot create variables like that INSIDE modules. This works in iex because it's an interactive environment. However, the x = y syntax in invalid outside of a function in an elixir module.

EDIT 2 Thanks to a correction from @Dogbert. You can actually create variables inside modules and out of functions, but you cannot use them inside def.

Abdullah Esmail
  • 216
  • 2
  • 6
  • Thanks for your answer. But I want to write my function as a First-Class Citizen. So, it means I can only write like that in interactive shell? – pyae phyo hlaing Jul 10 '18 at 07:43
  • 1
    It depends on what you mean by "first-class citizens". The problem is not with defining functions and using them. It's mainly because you cannot create variables like that in elixir (outside of functions). For example, you can have a function return a function and store that into a variable. Please check the following [gist](https://gist.github.com/aesmail/a9b08b596acdd0c0be2d90da75e6cb6b) – Abdullah Esmail Jul 10 '18 at 08:02
  • Ah..now got it. Thanks for your time. – pyae phyo hlaing Jul 10 '18 at 08:19
  • 2
    You can definitely create variables in modules / outside functions. You just can't access them from within a `def`. – Dogbert Jul 10 '18 at 15:25
  • Your comment makes super clear to me. Thanks for ur correction and time. @Dogbert – pyae phyo hlaing Jul 11 '18 at 03:18
  • 1
    @Dogbert thank you. I just knew this was possible. Never used or thought about it and for some reason assumed it was not valid. Updated answer accordingly. – Abdullah Esmail Jul 11 '18 at 04:50
0

Thanks to @Abdullah Esmail, I can write my function like this

defmodule Expense do

    def total_bread_slices do
        fn total -> (total * 10) / 100 end
    end 

    def total_bottles_milk do
        fn total -> total * 2 end
    end 

    def total_cakes do
        fn total -> total * 15 end
    end 

    def total_expense(bread_slices, bottles_of_milk, no_of_cakes) do

        bread = total_bread_slices()
        milk = total_bottles_milk()
        cakes = total_cakes()

        bread.(bread_slices) + milk.(bottles_of_milk) + cakes.(no_of_cakes)
    end

end
0

Thanks to @Dogbert, here is another method if I want to use the function as a value.

Firstly, I define a named function and then using Elixir's function capturing operator &, I can more easily use a named function as a value.

In this way, using the & operator to capture a reference to the function and the = operator to bind to the variable, I can bind the named function to a variable.

defmodule Expense do

    defp total_bread_slices(total) do
        (total * 10) / 100
    end 

    defp total_bottles_milk(total) do
        total * 2 
    end 

    defp total_cakes(total) do
        total * 15 
    end 

    def total_expense(bread_slices, bottles_of_milk, no_of_cakes) do

        bread = &total_bread_slices/1
        milk = &total_bottles_milk/1
        cakes = &total_cakes/1

        bread.(bread_slices) + milk.(bottles_of_milk) + cakes.(no_of_cakes)

    end

end
0

I had the same problem during the running examples from Elixir In Action book, second edition. To avoid retyping lambda function examples in iex, I saved them in a file. I tried to load them in iex, e.g:

iex solutions/ch05.ex

but when I called lambda function:

a_lambda.("something")

I got a compilation error in iex.

Now I copy/paste lambda code from the file directly to Iex, and it works.

Karlo Smid
  • 525
  • 1
  • 5
  • 13