Funny pipe code

Hi all, first post here (also a newbie in elixir/FP).
So, I have next pipe code which works but also looks funny:

case {status_code, body} do
      {200, _} ->
        edges = Poison.Parser.parse!(body)
        |> Map.get("data")
        |> Map.get("repository")
        |> Map.get("issues")
        |> Map.get("edges")
        if Enum.count(edges) == 0 do
          :ok
        else
          edges
          |> Enum.at(-1)
          # get last edge cursor
          |> Map.get("cursor")
          |> IO.inspect
          # recurse to get next page
          |> get(pagenum + 1)
          :ok
        end
      _ ->
        IO.inspect(status_code)
        IO.inspect("[ERROR]")
        :error
    end

2 questions here:

  1. How can I avoid using the temp var edges (i.e. use a continuous pipe)
  2. Is there some sort of tee operator that I could use to siphon out data at a specific pipe point whilst the pipe would continue on?

Thanks!

:wave:

  1. How can I avoid using the temp var edges (i.e. use a continuous pipe)
# ...
|> Map.get("edges")
|> case do
  [] -> :ok
  edges ->
    edges
    |> Enum.at(-1)
    # get last edge cursor
    |> Map.get("cursor")
    |> IO.inspect
    # recurse to get next page
    |> get(pagenum + 1)
    :ok
  end
# ...

But I’d probably want to refactor what you have to something like

def handle_resp(status_code, raw_body)

def handle_resp(200, raw_body) do
  raw_body
  |> Posion.decode!()
  |> extract_edges!()
  |> handle_edges()
end

defp extract_edges!(%{"data" => %{"repository" => %{"issues" => %{"edges" => edges}}}}) do
  edges
end

defp extract_edges!(invalid_body) do
  raise("invalid body: #{inspect(invalid_body)}")
end

defp handle_edges([]), do: :ok
defp handle_edges(edges) when is_list(edges) do
  # not sure what this is supposed to do
  # ...
  :ok
end

# etc
4 Likes

Extract to another function.

edges =
  try
    body
    |> Poison.Parser.parse!()
    |> Map.get("data")
    |> Map.get("repository")
    |> Map.get("issues")
    |> Map.get("edges")
    |> case do
      edges when map_size(edges) == 0 -> throw(:ok)
      edges -> edges
    end
    |> Enum.at(-1)
    |> Map.get("cursor")
    |> IO.inspect
    |> get(pagenum + 1)
    throw(:ok)
  catch
    value -> value
  end

Please note, Kernel.catch/1 is a local return.

But I probably would better extract it into a private function.

1 Like

I’d change that to:

get_in(~w<data repository issues edges>)
2 Likes

C’mon, I blindly copy-pasted it :slight_smile:

You know that if you want a harsh critic you must hire a Slav! :smiley:

Appreciate your help here. Just trying to make you into a Superman. :003:

2 Likes

I’m Russian, I know it better than anybody else :sunglasses:

3 Likes

Thanks a million guys!
Working on your pointers.
Any thoughts about question 2 (tee operator) ?
Something like:

data |> transformation_1 |> transformation_2 T> snapshot_data |> transformation_3
snapshot_data |> transformation_4
def f_1(data) do
  data
  |> transformation_1()
  |> transformation_2()
  |> f_2()
end

# still purely sequential - more like a (sequential) fork/join
# because everything has to be an expression.
#
def f_2(snapshot_data) do
  {transformation_3(snapshot_data),transformation_4(snapshot_data)} # return both results as a tuple
end

functional-programming-patterns-ndc-london-2014-15-638

More often than not the solution to a problem is “functions”.

pie

5 Likes

Typically a “local return” is simply returning a value - which is what all functions naturally do. And even though it says throw you are essentially using it as a goto - i.e. tread carefully.

The primary intent behind a throw is a non-local return of a value as described in Try a try in a tree.

Aside return vs throw vs exit vs raise.

:sweat_smile: it’s gonna take me a while to adjust. Coming from classic OO world (and landing to elixir by accident). FP paradigm feels quite alien to me in a good way though. I feel like I’m unlocking doors in my mind. Thanks a mil!

3 Likes