ibgib

ibgib

First off, I have this “working” with happy_path (It seems to execute properly, but it’s difficult to exercise the error paths). I am creating this topic for a couple main reasons:

  1. Is there a “better” way?
  2. Why isn’t my with statement compiling?
  3. Is there a way with with statements to have multi-line clauses.
  4. Are nested with statements allowed/possible?

So I’ll come back to these questions after the code:

Pyramid of doom with branching case statement in the middle

def start_or_resume_session(session_id) when is_bitstring(session_id) do
  case IbGib.Expression.Supervisor.start_expression({"session", "gib"}) do
    {:ok, root_session} ->
      case get_session_ib(session_id) do
        {:ok, session_ib} ->
          case get_latest_session_ib_gib(session_id, root_session) do
            # I want to do stuff if it's nil
            {:ok, nil} ->
              case (root_session |> instance(session_ib)) do
                {:ok, {_, session}} ->
                  case session |> get_info do
                    {:ok, session_info} ->
                      case get_ib_gib(session_info) do
                        {:ok, session_ib_gib} -> {:ok, session_ib_gib}
                        {:error, reason} -> {:error, reason}
                      end
                    {:error, reason} -> {:error, reason}
                  end
                {:error, reason} -> {:error, reason}
              end

            # Return it if not nil
            {:ok, existing_session_ib_gib} -> {:ok, existing_session_ib_gib}

            {:error, reason} -> {:error, reason}
          end
        {:error, reason} -> {:error, reason}
      end
    {:error, reason} -> {:error, reason}
  end
end

Attempt with nesting with (doesn’t compile)

def start_or_resume_session(session_id) when is_bitstring(session_id) do
  with {:ok, root_session} <- IbGib.Expression.Supervisor.start_expression({"session", "gib"}),
    {:ok, session_ib} <- get_session_ib(session_id),
    {:ok, session_ib_gib} <-
      case get_latest_session_ib_gib(session_id, root_session) do
        {:ok, nil} ->
          with {:ok, {_, session}} <- root_session |> instance(session_ib),
            {:ok, session_info} <- session |> get_info,
            {:ok, session_ib_gib} <- get_ib_gib(session_info) do
            {:ok, session_ib_gib}
          else
            {:error, reason} -> {:error, reason}
          end

        {:ok, existing_session_ib_gib} -> {:ok, existing_session_ib_gib}

        {:error, reason} -> {:error, reason}
      end,
      do: {:ok, session_ib_gib}
  end
end

Attempt with nesting happy_path (works, I think)

def start_or_resume_session(session_id) when is_bitstring(session_id) do
  happy_path do
    {:ok, root_session} = IbGib.Expression.Supervisor.start_expression({"session", "gib"})
    {:ok, session_ib} = get_session_ib(session_id)
    {:ok, existing_session_ib_gib} = get_latest_session_ib_gib(session_id, root_session)
    {:ok, session_ib_gib} =
      if (existing_session_ib_gib == nil) do
        Logger.debug "nil case...are we still on happy path?"
        # it's nil, so create a new one and return that
        # Are we still on the happy_path?  No. So nest another path?
        happy_path do
          {:ok, {_, session}} = root_session |> instance(session_ib)
          {:ok, session_info} = session |> get_info
          {:ok, session_ib_gib} = get_ib_gib(session_info)
          {:ok, session_ib_gib}
        end
      else
        {:ok, existing_session_ib_gib}
      end
    {:ok, session_ib_gib}
  end
end

Questions

Is there a “better” way?

Overall, could/should I refactor something out? I’m not a huge fan of do_x_or_y functions, but this seems pretty straight-forward to me, and the long-ish name is more of being precise.

Or, is there a more concise/appropriate way of nesting happy_path statements?

Why isn’t my with statement compiling?

The error changes, depending on which permutation of , do:, or do end block in the with statement I use. It’s either there is an unexpected end later on in the file, or that there is an illegal character before a comma (,) on the with block. I think it’s balking on the case statement line. Are case statements, or rather full do..end blocks not allowed with with clauses?

Is there a way with with statements to have multi-line clauses.

This is probably related to the previous question.

Everything I’ve seen/read on with has a single line clause, with each line ending with a comma before the do block. Is there a way to have a multi-line clause?

Are nested with statements allowed/possible?

I can’t seem to figure out how to nest with statements, and I just think that I should. I’m still a little fuzzy on some of the , do: vs do...end blocks thing (I didn’t come from a Rails background).

Showing Posts 1 to 8

andre1sk

andre1sk

Could do something like

def start_or_resume_session(session_id) when is_bitstring(session_id) do
  with {:ok, root_session} <- IbGib.Expression.Supervisor.start_expression({"session", "gib"}),
       {:ok, session_ib} <- get_session_ib(session_id),
       {:ok, existing_session_ib_gib} <- latest(session_id, root_session) do
    {:ok, existing_session_ib_gib}
  else
    {:error, reason} ->  {:error, reason}
  end
end

def latest(session_id, root_session) do
  get_latest_session_ib_gib(session_id, root_session)
  |>handle_latest_session_ib_gib(root_session,session_ib)
end

def handle_latest_session_ib_gib({:ok, nil},root_session,session_ib) do
  with  {:ok, {_, session}} <- instance(root_session, session_ib),
        {:ok, session_info} <- get_info(session),
        {:ok, session_ib_gib} <- get_ib_gib(session_info) do
    {:ok, session_ib_gib}
  else
    {:error, reason} ->  {:error, reason}
  end
end

def handle_latest_session_ib_gib(res,_,_), do: res
ibgib

ibgib OP

It looks like you had the gut feeling of refactoring out into another function (or two as in this case)…which, I just have to say that it compiled with only one slight tweak (including session_ib in the latest function signature): That is pretty darn impressive considering the esoteric domain language. :smile:

So this refactoring or a similar one would be how to make it better, and that is kind of what I also am leaning towards. Perhaps this is an indication that a nested with or happy_path statement would be in general a code smell? In which case, it would make the other questions moot, and condensing any possible multi-line statements into single-line functions could indicate a cleaner overall design?

ibgib

ibgib OP

@andre1sk Here is what I’ve come up with based on your refactoring. The key I think is moving the case statement pattern matching to function pattern matching as you showed in your response. It definitely looks a lot cleaner :smiley:

Thanks a lot for your help!

  def start_or_resume_session(session_id) when is_bitstring(session_id) do
    with {:ok, root_session} <- IbGib.Expression.Supervisor.start_expression({"session", "gib"}),
      {:ok, session_ib} <- get_session_ib(session_id),
      {:ok, latest} <- get_latest_session_ib_gib(session_id, root_session),
      {:ok, session_ib_gib} <- create_session_if_needed(latest, root_session, session_ib) do
      {:ok, session_ib_gib}
    else
      {:error, reason} -> {:error, reason}
    end
  end
  def start_or_resume_session(unknown_arg) do
    {:error, emsg_invalid_arg(unknown_arg)}
  end

  defp create_session_if_needed(existing, root_session, session_ib)
    when is_nil(existing) do
    with {:ok, {_, session}} <- root_session |> instance(session_ib),
      {:ok, session_info} <- session |> get_info,
      {:ok, session_ib_gib} <- get_ib_gib(session_info) do
      {:ok, session_ib_gib}
    else
      {:error, reason} -> {:error, reason}
    end
  end
  defp create_session_if_needed(existing, _, _) do
    {:ok, existing}
  end
andre1sk

andre1sk

Looks really clean to me :slight_smile:
In Elixir composing small easy to read functions is the favored approach, so having deep nesting control flow structures would prob be considered an anti-pattern

josevalim

josevalim

Creator of Elixir

Btw, the reason why your first example using with failed is because you had do blocks inside do blocks which caused a precedence issue. The suggested refactorings are definitely the way to go though, there is no reason to have such a large function as the one initially posted. Breaking the code into smaller functions will be helpful regardless if you are using case, with or happy_path.

ibgib

ibgib OP

Thanks for this comment! I’m still scratching my head though, as I don’t come from a Ruby background and some of the nuances of blocks still evade me. In the above example, do you mean the implicit do before the case statement?

I’ve made a simplified snippet module:

defmodule DoBlocks do
  def foo do
    with :ok <- bar(),
      :ok <- case baz do
          :ok -> :ok
          :error -> :error
        # end, do: IO.puts "ok yo" # doesn't compile
        end do
      IO.puts "ok yo" # doesn't compile either
    end
  end

  def bar, do: IO.puts "bar"
  def baz, do: :ok
end

This particular example is relatively complicated since it includes both a with and a case. Could you give me a simplified nested do problem without the with/case statement in code for me? Also, is this related to why with clauses have only one line?

OvermindDL1

OvermindDL1

I use happy_path (excessively in phoenix…) but anytime I would ever think of having a nested one is when I would break that out into a function and happy_path in it with the head of the function matching on what is needed (with a default path to send an error to the parent happy_path to send to the webpage instead of 500’ing).

ibgib

ibgib OP

Thanks for your input. It sounds like everyone is agreed that a nested with/happy_path is a code smell: :no_entry_sign: :nose:

— All posts loaded —

Where Next? Top

Trending in Discussions Top

AstonJ
As the title says, please share what you’ve been up to with Elixir. Whether that’s been learning it, looking into it, making stuff with i...
2977 92995 915
New
caslu
I want to open this thread for you all to discuss and help those who really like Ash but are still hesitant to use it in a real project. ...
New
mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
Herve37
We’re evaluating API mocking tools for OpenAPI-based projects and would love to hear what other teams are using. We’re particularly inte...
New
GES233
I’m posting this in response to Jose’s recent tweet (Cr. link) : People are sleeping on Elixir for a coding harness: Hot-code swappi...
New
_mfierro
Hello, I wrote Stop My Hand, a Scattergories-like web application using Phoenix/LiveView as my learning project for Elixir (after readin...
New
nseaSeb
AcmeScript — Writing JS hooks as if I were still using Elixir I’ve been having fun building a little something over the last few days: Ac...
New

Other Trending Topics Top

garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge &amp; Solve. They are GUI (Emerge) and State management (S...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
wintermeyer
There are three potential reasons for members of this forum to have a look at https://vutuv.de You are tired or annoyed of LinkedIn. Yo...
New
webofbits
Aludel - LLM Evaluation Workbench Aludel is an embeddable Phoenix LiveView dashboard for evaluating and comparing LLM prompts across mult...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews