Elixir stype

Here are two versions of code I wrote, they both work.
I’d like insight from you guys as to : what is best style between the two, and maybe what would be an even better style ?

I find the version with “with” a bit contrived with all those temprary tuples and atoms I had to create.

First version

world =
  case Msim.Simulator.get_world_db(world_id, true) do
    {:ok, res} -> res
    {:error, _error} -> nil
  end

legit =
  case world do
    nil -> false
    _ -> check_path_to_module(__MODULE__, world.type.front_module)
  end

{:ok,
 socket
 |> assign(
   msim_auth: msim_auth,
   request_path: request_path,
   # specific data after
   world_id: world_id,
   world: world,
   legit_path: legit,
   banks: nil,
   current_user: Msim.Account.fetch_user_from_db(user_id)
 )}

Second version

{world, legit} =
  with {:get, {:ok, world}} <- {:get, Msim.Simulator.get_world_db(world_id, true)},
       {:legit, world, true} <-
         {:legit, world, check_path_to_module(__MODULE__, world.type.front_module)} do
    {world, true}
  else
    {:get, _} -> {nil, false}
    {:legit, world, _} -> {world, false}
  end

{:ok,
 socket
 |> assign(
   msim_auth: msim_auth,
   request_path: request_path,
   # specific data after
   world_id: world_id,
   world: world,
   legit_path: legit,
   banks: nil,
   current_user: Msim.Account.fetch_user_from_db(user_id)
 )}

The first one looks much clearer to me :slight_smile:

You can also merge the cases, because it’s the same condition under the hood…

{world, legit} = case Msim.Simulator.get_world_db(world_id, true) do
  {:ok, res} -> {res, check_path_to_module(__MODULE__, res.type.front_module)}
  {:error, _error} -> {nil, false}
end
4 Likes

Very nice, I did not see that. Thank you.

I’m trying to find a general pattern for such cases. Altough I like your solution, I see that if I now have 3 variables instead of 2 like here, the code will start indenting.
The general problem I’m trying to solve is this : I have n variables and want to decide their values with some constraints : a value depends on function f_a(…), b value depend on function f_b(a,…), c depends on f_c(b,…) and so on.

With your code and 2 variable case, we have your code:

{a, b} =
  case f_a() do
    {:ok, res_a} -> {res_a, f_b(res_a)}
    {:error, _error} -> {nil, false}
  end

If we switch to 3 variables, f_b(res_a) will turn into a new case : case(f_b(res_a)) leading to quite hard to read code.
The less nicer “solution one” scales with more variables and stay readable. Unless there is a trick I have yet to discover.

Seems like a case for Bunch.withl. In your case, it would be something like

withl a: {:ok, res_a} <- f_a(),
      b: {:ok, res_b} <- f_b() do
...
else
  a: {:error, reason} -> # handle a error
  b: {:error, reason} -> # handle b error
end

What you propose does not compile (the atoms a: and b:). That’s why I came up with the second version.
Also, the else part does not cary all the test part variables automatically, that’s why I put world in the second test line left part, so that in case of error, world is available in the else part.

That’s expected given withl is the macro of a package and not part of elixir.

2 Likes

Oh, I did not catch the L at the end of withl. So withl was not a typo.
Thanks a lot, I’ll dig into withl

Thank you for this discovery. withl is my new friends. It makes the code so much clearer. It’s like a bunch of if-then-else only they don’t need to be indented.
And with withl its more obvious to see the regular case and the erroneous cases.