jgonet

jgonet

Popcorn Core Team

Recursive anonymous functions

What’s the state of recursive anonymous functions now? It was mentioned a while ago, when this feature was being introduced in OTP 17. What’s the syntax for defining those? This isn’t working:

f = fn                      
  (0, acc) -> acc             
  (num, acc) -> f.(num-1, acc+1)
end

Most Liked

Qqwy

Qqwy

TypeCheck Core Team

I do not know the exact state at this time, but I did have a discussion with @sasajuric and @michalmuskala about this while at the Code BEAM Lite Amsterdam.

Some notes:

  1. There is no current syntax in Elixir to define the ‘named anonymous functions’ that were added to Erlang in OTP 17 directly.
  2. However, one can argue that it is not really required. In Erlang, a problem was that you were unable to define a module inside the shell, only being able to define one-off anonymous functions. That really makes it difficult when you want to test out a piece of code that should call itself recursively. (In Elixir’s IEx this is not a problem, as it allows defining new modules in the shell.)
  3. Of course, the ‘classical’ way to do this, the Y-Combinator, is a possibility both in Erlang and Elixir. However, using this is (a) rather slow and (b) beginner-unfriendly, because it is difficult to read. But then again, it’s manually-entered shell-code we are talking about, in which these arguments might not be as strong. When writing modules, why not, instead of a self-recursing anonymous function, define it as a private, named function instead?
  4. It is possible to define an macro that wraps a function in a similar way that the Y-Combinator construction does, in a way that might be more understandable for users (say fn_rec. No alterations to Elixir’s core are necessary to do this. (But this also does not use Erlang’s ‘named anonymous functions’).

An example of how (4) could look when used would be:

fn_rec
  _, 0, acc -> acc
  myfun, num, acc -> myfun.(num - 1, acc + 1)
end

(although when using this syntax, I’d prefer writing it as:

fn_rec myfun, num, acc ->
  case num  do
   0 -> acc
   _ -> myfun.(num - 1, acc + 1)
end

)

or possibly:

fn_rec myfun do
   0, acc -> acc
   num, acc -> myfun.(num - 1, acc + 1)
end

One of the things I do not know, is how performance compares between:

  • using a y-combinator-style approach (where no new names are defined).
  • using an indirectly-threaded approach where the macro defines a new name
  • using the Erlang’s ‘named anonymous functions’ directly.

(I expect the y-combinator to be slow, and directly using the Erlang feature to be the fastest, but I do not know how by how much, or if it is enough to justify adding it as a built-in construct to the Elixir language itself.)

peerreynders

peerreynders

iex(1)> f = fn                      
...(1)>   (_, 0, acc) -> acc             
...(1)>   (g, num, acc) -> g.(g, num-1, acc+1)
...(1)> end
#Function<18.128620087/3 in :erl_eval.expr/5>
iex(2)> 
nil
iex(3)> f.(f,10,10)
20
iex(4)> 

Functions are values …

peerreynders

peerreynders

1> G = fun                  
1>   F (0, Acc) -> Acc;        
1>   F (Num, Acc) -> F(Num - 1, Acc + 1)
1> end.
#Fun<erl_eval.36.128620087>
2> G(10,10).
20
3>

Even though this feature now exists in Erlang the points are:

  1. There already is a solution in Elixir (and Erlang pre-17.0) that works even if it isn’t elegant.
  2. How often does a real need for recursive anonymous functions actually come up, given that recursion is well supported with named functions within actual code (rather than the shell)?

Erlang and by extension Elixir are pragmatic languages.

I contend that anonymous functions are an overused feature which are even more prevalent in Elixir because of the existence of the shorthand notation.

I see the primary value of an anonymous function in it’s closure (i.e. its connection to the scope that created it) - that should be the reason for using it. More often than not they are used simply for notational convenience even though quite often code becomes less readable.

The Erlang named anonymous function example can be quite easily emulated with a mixture of an anonymous function for its closure and a named function to do the real work:

# file: Demo.ex
# Original: https://learnyousomeerlang.com/higher-order-functions#highlighter_833032
#
defmodule Demo do
  def prepare_alarm(room) do
    IO.puts("Alarm set in #{room}")
    fn -> loop(room) end
  end

  defp loop(room) do
    IO.puts("Alarm tripped in #{room}! Call Batman!")
    Process.sleep(500)
    loop(room)
  end
end
iex(1)> c("Demo.ex")
[Demo]
iex(2)>  alarm_ready = Demo.prepare_alarm("bathroom")
Alarm set in bathroom
#Function<0.61175415/0 in Demo.prepare_alarm/1>
iex(3)>  alarm_ready.()
Alarm tripped in bathroom! Call Batman!
Alarm tripped in bathroom! Call Batman!
Alarm tripped in bathroom! Call Batman!

BREAK: (a)bort (c)ontinue (p)roc info (i)nfo (l)oaded
       (v)ersion (k)ill (D)b-tables (d)istribution
a

Last Post!

igsp7

igsp7

An example from Learn Functional Programming with Elixir by Ulisses Almeida :

iex> fact_gen = fn me -> 
  fn
    0 -> 1
    x when x > 0 -> x * me.(me).(x - 1) 
  end
end
iex> factorial = fact_gen.(fact_gen) 
iex> factorial.(5)
120

Where Next?

Popular in Discussions Top

JakeBecker
TL;DR: I’ve just released an implementation of Microsoft’s IDE-independent Language Server Protocol for Elixir. It adds language support ...
1144 54996 245
New
AstonJ
If a newbie asked you about Phoenix Contexts, how would you explain the basics to them? Feel free to be as concise or in-depth as you li...
New
sergio
There’s a new TIOBE index report that came out that shows Elixir is still not in the top 50 used languages. It also goes on to call Elix...
New
AstonJ
I’ve just started the Phoenix part of the utterly brilliant online course by @pragdave. On generating the Phoenix app he uses the --no-ec...
New
PragTob
Hello everyone, I know we had quite some threads (read through lots of them) about background job processing but it remains a hotly deba...
New
mikl
I wanted to capitalize a string, and tried using String.capitalize(). That generally works well, until you try to capitalize a word like...
New
cvkmohan
The upcoming Phoenix 1.6 release looks very interesting. Became a habit to watch the commits - and - what they are bringing in. phx.gen...
New

Other popular topics Top

KronicDeth
Elixir plugin for JetBrain’s IntelliJ Platform (including Rubymine) This is a plugin that adds support for Elixir to JetBrains IntelliJ...
289 36689 110
New
sergio_101
I am VERY much an elixir newbie. I have taken one elixir course and one phoenix course on Udemy. During that course, I saw the instructor...
New
saif
Hello everyone, Long time lurker first time poster here. I’ve recently begun working on Elixir full-time again! :raised_hands: It’s been...
New
dblack
I’ve got an issue with an app and I’ve no idea of how to troubleshoot it. I’m hoping someone here might have seen something similar. I p...
New
sergio
Kind of like when jquery came out, it was super necessary. Existing drag and drop libraries have a bunch of baggage to support old browse...
New
AstonJ
Posting this to see if we can make things easier for people to get into Neovim. If you use Neovim and have a favourite distro please let ...
New

We're in Beta

About us Mission Statement