mschwartau

mschwartau

Beginner: Ok or not ok, cyclic function calls?

The following is my solution to one of the exercises of “Programming Elixir 1.2” and I wondered if it is considered an anitpattern to have two functions (upcase_first_char and downcase_till_point) which call each other - or if it is ok if there are not too many functions which call each other. Other solutions use a parameter (true/false) to know if they have to upcase or downcase a character but I think the code is better readable if I use different method names instead.

The exercise was:

Write a function to capitalize the sentences in a string. Each sentence is terminated by a period and a space. Right now, the case of the characters in the string is random.

 iex> capitalize_sentences("oh. a DOG. woof. ")
  "Oh. A dog. Woof. "

My solution is:

defmodule MyStrings do

    def capitalize_sentences(string), do: upcase_first_char(string)

    defp upcase_first_char(<< " ", tail :: binary >>), do: " " <> upcase_first_char(tail)

    defp upcase_first_char(<< head :: utf8, tail :: binary >>), do: String.upcase(<<head>>) <> downcase_till_point(tail)

    defp upcase_first_char(<<>>), do: ""

    defp downcase_till_point(<< ".", tail :: binary >>), do: "." <> upcase_first_char(tail)

    defp downcase_till_point(<< head :: utf8, tail :: binary >>), do: String.downcase(<<head>>) <> downcase_till_point(tail)

    defp downcase_till_point(<<>>), do: ""

end

I know that it’s easier to do it using String.split und Enum.join but I wanted to get familiar with binary pattern matching.

Ok or not ok?

Regards
Meinert

Most Liked

rvirding

rvirding

Creator of Erlang

Erlang, and elixir, supports not only tail-recursion but also tail-call optimisation, or sometimes called last call optimisation. This means that not only recursive tail calls but ALL tail calls are optimised away into “jumps”. So in this case have a number of functions mutually calling each other will get the same optimisation as one being tail-recursive.

The compiler happily does this for you, the general tail-call optimisation creates no extra work for it. It would actually be more work to explicitly handle tail-recursion.

This means that there is no support for trampolines. There is no need for it. Trampoline is just a hack :slight_smile: you need when you don’t have TCO.

Strangely enough you can do TCO on the JVM, but scala and clojure have chosen not to do it. There is an implementation of erlang running on the JVM, erjang, which supports full erlang with TCO. It’s quite an impressive system, and fast.

The only thing you have to be careful about is to make sure that you actually are doing proper tail-calls. It is not enough that they are syntactically last, they have to be operationally last for the effect to kick-in. @Qqwy discusses this and gives some tips on how you can make your functions have proper tail-calls.

As a final point I just want to say that while TCO is nice there are only a few places where you need to make sure you have them. Most common is the top-loop of a process which sits and waits for input messages, processes the input, then calls itself recursively to wait for the next message. This has to be proper tail calls otherwise stack will build up and you will crash the system. This also means that stacks seldom get deep in erlang/elixir processes, at least not when you get back to the top function.

Having TCO is common in functional languages so it is nothing we invented here.

Robert

Qqwy

Qqwy

TypeCheck Core Team

In general, it is completely fine to have pairs of functions that call each other, or have functions that call themselves. This is called recursion and it is one of the fundamental principles of functional programming: Dividing a problem in simple steps that are trivially to solve individually.

One thing to keep in mind is the difference of ‘normal’ recursion and tail recursion: With normal recursion, you might at some point get a stack-overflow error, as none of the functions can return until the functions they call internally are finished.

tail recursion is what happens if you call, at the end of your function, a single function recursively. This enables languages that support tail recursion (and most functional ones, including Elixir do) to simply ‘replace’ the original function on the function-call-stack with the new one, which makes it work even for very large inputs.

Tail recursion can often be obtained by using a so-called accumulator to keep track of the results you’ve obtained so far. This lets you flip the order of operations, which allows you to keep the recursive function as the final operation in a function.
In this case, you call the <> concatenation operator in every function, which is the last step of the function. To make your function heads tail recursive, you could do something like:

defmodule MyStrings do

    def capitalize_sentences(string), do: upcase_first_char(string, "")

    defp upcase_first_char(<< " ", tail :: binary >>, acc), do: upcase_first_char(tail, acc <> " ")

    defp upcase_first_char(<< head :: utf8, tail :: binary >>, acc), do: downcase_till_point(tail, acc <> String.upcase(<<head>>) )

    defp upcase_first_char(<<>>, acc), do: acc

    defp downcase_till_point(<< ".", tail :: binary >>, acc), do: upcase_first_char(tail, acc <> ".")

    defp downcase_till_point(<< head :: utf8, tail :: binary >>, acc), do: downcase_till_point(tail, acc <>  String.downcase(<<head>>))

    defp downcase_till_point(<<>>, acc), do: acc

end
peerreynders

peerreynders

Given that your motivation was, as an exercise, to “get familiar with binary pattern matching” your use of recursion is justifiable. However when looking for “the right tool for the job” I don’t believe that “easier to do” was the sole motivation of using String.split, Enum.map, and Enum.join in Dave Thomas’ possible solution (though it certainly is a valuable bonus).

I personally suspect that it has something to do with what Michael Fogus discusses in his blog post Recursion is a low-level operation - the essence of which seems to be “Prefer use of higher order functions over recursion”. Now granted the blog is in the context of Clojure and the JVM but it seems to align with the general notion that higher level abstractions tend to lead to better solutions. So while it is certainly important to know how to effectively use recursion, it is just as important to know when not to use it. Don’t let recursion become your golden hammer; attempt to seek out better options whenever possible.

Where Next?

Popular in Questions Top

lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
gshaw
What is the idiomatic way of matching for not nil in Elixir? E.g., First way: defp halt_if_not_signed_in(conn, signed_in_account) when...
New
LegitStack
I’m trying to make a websocket server in Phoenix or raw Elixir. I heard about gun, I think I could use cowboy, but since I’m not that sma...
New
Emily
I have VueJS GUIs with the project generated using Webpack. I have Elixir modules that will need to be used by the VueJS GUIs. I forese...
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
shijith.k
I am trying to start a new phoenix project with elixir 1.9, but mix phx.new does not work. It says that ** (Mix) The task "phx.new" could...
New
dotdotdotPaul
Okay, I’m having a heck of a time trying to figure out how to best handle the validation of belongs_to associations in Ecto. I’m sure I’...
New
hariharasudhan94
I would like to know what is the best IDE for elixir development?
New
hariharasudhan94
Lets say I have map like this fetching from my database %{"_id" =&gt; #BSON.ObjectId&lt;58eb1a7a9ad169198c3dXXXX&gt;, "email" =&gt; ...
New
dokuzbir
I want to highlight html closing tags when i click a html tag. That works in .html files but doesnt work for html.eex templates. How can...
New

Other popular topics Top

Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
New
marius95
Hello everyone, I try to use an Javascript Event Handler in my root.html.leex file. Therefore I created a function in the app.js file: ...
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID&lt;0.412.0&gt; terminating ** (Postgrex.Error) FATAL...
New
shahryarjb
Hello, I have map which I want to convert it to string like this: the map: %{last_name: "tavakkoli", name: "shahryar"} the string I ne...
New
johnnyicon
Hi all, I’ve just started learning Elixir and Phoenix Framework, so please pardon my n00bness at this stage. I’m trying to use Postgres...
New
aalberti333
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this: ...
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
Qqwy
Update: How to use the Blogs &amp; Podcasts section You can post links to your blog posts or podcasts either in one of the Official Blog...
3271 127089 1222
New
JorisKok
I have a server on AWS, and was running a load test using artillery. When looking at the Phoenix dashboard I see the Ports going to 100% ...
New
dokuzbir
I want to highlight html closing tags when i click a html tag. That works in .html files but doesnt work for html.eex templates. How can...
New

We're in Beta

About us Mission Statement