Pipe-chaining functions that don't work with nil args?

Hi, I’m having some trouble when “pipe-chaining” multiple functions that would fail with an exception if the first argument is nil. All I want in that case is to just return nil at the end if any of the functions returns a nil. I have come with a simple solution; however, I believe there should be a more elegant one.

Here it is the code that needs to be refactored:

body
|> Floki.find(".description div")
|> Enum.at(2)
|> Floki.text
|> String.slice(0, 100)

All functions would fail with an exception if the “description” is not found on line 2 above.

My solution (please, have in mind that this is my third week working with Elixir):

defmodule OKWrapper do
  def ok(data) when is_nil(data), do: {:error, "Blank Data"}
  def ok(data), do: {:ok, data}
end
...
with {:ok, desc_div} <- ok(Floki.find(body, ".description div")),
     {:ok, div3} <- ok(Enum.at(desc_div, 2)),
     {:ok, text} <- ok(Floki.text(div3)),
  do: String.slice(text, 0, Job.description_max_length),
else: nil

How would you solve the same problem in a more elegant way?

Also, I was thinking that I would be happy to use something like a “safe-pipe” operator (e.g. ||>) that would return a nil if it’s passed a nil. Then, my code could look like:

body
||> Floki.find(".description div")
||> Enum.at(2)
||> Floki.text
||> String.slice(0, 100)

I believe that could be achieved by some black magic meta-programming :)?

There are a couple of macros I’ve made that could be of help, depending on what you prefer

Using the Pit macro if you need to decide at each pipe step

body
|> Floki.find(".description div")
|> pit(not nil, do_pipe: Enum.at(2))
|> pit(not nil, do_pipe: Floki.text)
|> pit(not nil, do_pipe: String.slice(0, 100))

Or using OkJose you can define a custom pipe that will work only on non-nil values:

defmodule NonNilPipe do
  use OkJose.Pipe
  defpipe non_nil do
    x when not is_nil(x) -> x
  end
end

## later on:
body
|> Floki.find(".description div")
|> Enum.at(2)
|> Floki.text
|> String.slice(0, 100)
|> NonNilPipe.non_nil # will only pipe non-nil values
3 Likes

Vic’s libraries are awesome, and for note in his NonNilPipe.non_nil example it affects the entire |> pipe chain, so it ‘just works’, and you can have multiple kinds too. :slight_smile:

3 Likes

Thank you, mates! NonNilPipe.non_nil seems to be exactly what I need.

1 Like