Two new life saving functions worth knowing | Elixir 1.12 | tap & then

New improvements in Kernel module Elixir 1.12

Blackode
Blackode
Jun 9 · 7 min read

This article is all about knowing the two newly added macros then/2 and tap/2 from Kernel module. Here, we cover some real world situations with simple examples where these functions would be useful.

Actually these are macros, point to be noted.

Let’s check when and where to use these functions.

The Difference between functions

The main difference between the two functions is what they produce as output.

The tap/2 takes two arguments one is data of any(), second parameter is fun() and returns simply data which has been passed as first parameter to the tap function.

So, It don’t care about what the fun() produces.

Likely, the then/2 also takes two arguments data of any(), second parameter is fun() and returns the result of the fun().

So, whatever the fun() produces will be the output of the then function.

Note

Please don’t excuse yourself to not use these functions as we encounter the situations, daily.

Use case(1): Tracking | Logging| Printing | tap/2

The meaning of the tap in verb form is to “exploit or draw a supply from (a resource)”.

Requirement

Suppose, you have a requirement to print or log or track the execution flow of statements before it produce final output. This can be easily achieved using the tap/2.

Demo: Tracking the requested URL

Cosider that we have to hit a http request using the parameters which contains an url and we need to track how many times this url has been requested.

Let’s build this demo.

Here, we need to build two things.
1. flow to make request and
2. other is to save the tracked information.

Since it is a demo, we create fake request and response and we try to track them with an Agent.

Let’s code it.

Agent Tracker

Here, we create an agent and we name it as a tracker. This tracker is started with initial state as an empty map %{}

{:ok, tracker} = Agent.start_link(fn -> %{} end)

Request Flow

defmodule HTTP do  def request(%{url: url}), do: :ok  def track(tracker, url) do
Agent.update(tracker,
fn state -> Map.update(state, url, 1, & &1+1)
end)
end
end

I hope you can easily follow the code for HTTP module. It has two functions request/1 and track/2. The request one simply returns :ok and track/2 function takes the tracker and url to track and it updates the agent(tracker) state

Now let’s check them in iex

Load the HTTP module by copy and pasting in iex shell, after that just run the following lines of code

# Step1 : Load HTTP module Just copy and paste# Step2 Start the Tracker agent
{:ok, tracker} = Agent.start_link(fn -> %{} end)

# Step3 Creating params
params1 = %{url: "https://medium.com"}
params2 = %{url: "https://blackode.in"}

We have now a tracker, params1 and params2 and a module HTTP with request/1 and track/2 functions.

I hope you are following the code. Here, we make some different no of requests by calling HTTP.request passing params1 for some requests and params2 for some other requests and we track no of times each url in params1 and params2 is requested.

Stay in the same iex session where we loaded all the required information. If you accidentally closed the session then you need to repeat the above steps mentioned.

Now let’s make some requests.

Here, we make three(3) requests with params1 and two(2) requests with params2 and see whether the information is tracked or not.

# Step4 Making three params1 requestsparams1 |> tap(&HTTP.track(tracker, &1.url)) |> HTTP.request()
params1 |> tap(&HTTP.track(tracker, &1.url)) |> HTTP.request()
params1 |> tap(&HTTP.track(tracker, &1.url)) |> HTTP.request()
#Step5 Making two params2 requestsparams2 |> tap(&HTTP.track(tracker, &1.url)) |> HTTP.request()
params2 |> tap(&HTTP.track(tracker, &1.url)) |> HTTP.request()

It’s a simple flow. We pass params1 or params2 to tap along with function and it returns either params1 or params2 based on what we pass…after that we are calling request.

Before we hit request function, we add a tap to track the url in params. Here, the track function simply updates the tracker state by count 1 adding to existing count for the specific url in params.

Let’s check the state now

Agent.get(tracker, & &1)%{"https://blackode.in" => 2, "https://medium.com" => 3} #output

Let’s check the another use case of tap which is a similar concept we used above but in a different situation. It’s just another use case of tap.

Use case(2): Debugging Nested parameters | tap/2

data = %{
sensor: %{
name: "temperature",
readings: [1,2,3,4,5,6,7],
device_type: "chip"
}
}

Consider that we have the following flow of functions that data could pass through

data
|> get_sensor()
|> send_readings()
|> insert_device()

Now, if you want to inspect just sensor name before passing to get_sensor() we use the tap like in the following way

data
|> tap(&IO.inspect(&1.sensor.name, label: "sensor name"))
|> get_sensor()
|> send_readings()
|> insert_device()

Though we are inspecting only sensor name here, it still returns the complete data to pass get_sensor().

Use case: Re Positioning Parameters | then/2

The then/2 takes two parameters data and function and returns the result of the function as output.

Situation

We daily see the situations like we need to pass the output of one function to another function as input. If next function accept it as a first parameter then we don’t have a problem. We can simply use the pipe operator |>

What if the function except the output to be passed as second or other?

Before introducing the then/2, we were sending this to some anonymous function and inside the anonymous function we are calling the actual function by positioning the argument passed to anonymous function.

Consider the following example…

defmodule Bank do
def transaction(:credit, fine_amount, balance) do
balance + fine_amount
end
def transaction(:debit, fine_amount, balance) do
balance - fine_amount
end
def calculate_fine(balance, percentage) do
balance * percentage/100
end
end

I hope you can easily understand the module. It prepares the fine and either credit or debit to the balance. It’s just a demo module to understand the then/2 usage.

I purposely added fine_amount as second parameter in transaction to create a situation where we cannot pipe after preparing fine to the transaction function.

Let’s prepare a fine of 10% over the balance 100
The code looks like in the following before then/2 is introduced

balance = 1000final_amount = 
balance
|> Bank.calculate_fine(10)
|> (fn fine_amount ->
Bank.transaction(:credit, fine_amount, balance)

end).()
|> IO.inspect()

Here, we need to send the fine_amount to the anonymous function where eventually calls the Bank.transaction by positioning the fine_amount as a second parameter.

We can avoid this using the then/2 function like in the following lines of code

final_amount = 
balance
|> Bank.calculate_fine(10)
|> then(&Bank.transaction(:credit, &1, balance)
)
|> IO.inspect()

So, using then/2 we can directly pipe and change position of the parameter as needed like in the above example.

So, less type & live more.

I hope you got the idea of where to use these two handy functions tap/2 and then/2 from Kernel module.

Happy Coding!!
Thanks for reading.

Join Our Telegram Channel and support us.Blackoders

Check out the GitHub repository on Killer Elixir Tips

https://github.com/blackode/elixir-tips

Glad if you can contribute with a ★

TQ!

blackode

Coding, thoughts, and ideas.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store