amnu3387

amnu3387

Getting Line Error on stack trace when "using" modules

I had a very large module (for my experience) with around 2k loc. I broke it down for organisational purposes, although I’m just “importing” these new modules into a main one. Now whenever I have an error that occurs in one of the modules being used I get the stack trace with the line where the module is imported, along with the function name and arity.

** (ArgumentError) could not put/update key nil on a nil value
    (elixir) lib/access.ex:371: Access.get_and_update/3
    (elixir) lib/kernel.ex:1880: Kernel.put_in/3
    (AetherWars) lib/aetherwars/duels/duels_processor.ex:12: AetherWars.Duels.Processor.decide_next_player/1
    (AetherWars) lib/aetherwars/duels/duels_processor.ex:11: AetherWars.Duels.Processor.pop_stack/4
    (AetherWars) lib/aetherwars/duels/duels_processor.ex:22: AetherWars.Duels.Processor.entry_point/4
    (AetherWars) web/channels/duel_channel.ex:25: AetherWars.DuelChannel.handle_in/3
    (phoenix) lib/phoenix/channel/server.ex:244: anonymous fn/4 in Phoenix.Channel.Server.handle_info/2
    (AetherWars) lib/aetherwars/endpoint.ex:1: AetherWars.Endpoint.instrument/4
    (stdlib) gen_server.erl:616: :gen_server.try_dispatch/4
    (stdlib) gen_server.erl:686: :gen_server.handle_msg/6
    (stdlib) proc_lib.erl:247: :proc_lib.init_p_do_apply/3

Sometimes this is all I need, but other times I have several different functions with the same arity pattern-matching on the head. Is there any way to be able to see in which line of the original module that has been used it occurs? (I guess since use when compiled basically places the code on the module calling it, it won’t be straightforward but perhaps there’s some easy work-around?)

You can see it becomes a bit problematic:

defmodule AetherWars.Duels.Processor do
  
  use AetherWars.Duels.Create
  use AetherWars.Duels.Utils
  use AetherWars.Duels.Payment
  use AetherWars.Duels.Legal
  use AetherWars.Duels.Messages
  use AetherWars.Duels.Strike
  use AetherWars.Duels.Guard
  use AetherWars.Duels.Stack
  use AetherWars.Duels.Turn
  use AetherWars.Duels.Manipulation
  use AetherWars.Duels.Battle
  alias AetherWars.Duels.Monitor
  ...
end

Any solution? Thanks

Marked As Solved

ericmj

ericmj

Elixir Core Team

Generated code will use the stacktrace location of the caller. This is a good default for most macros since generated code should be kept to a minimum, the fact that you need the location of the generated code in the stacktrace is an indication that you are overusing it. Using use that expands to function definitions with the full implementation for composition is an anti-pattern.

If you do need the line information from the macros add location: :keep to the quoted code: quote location: :keep do ... end.

https://hexdocs.pm/elixir/Kernel.SpecialForms.html#quote/2-stacktrace-information

Also Liked

benwilson512

benwilson512

Author of Craft GraphQL APIs in Elixir with Absinthe

Hey! Large modules can definitely be a pain, but this is not the way to solve them. Find boundaries, break those things out into dedicated modules, then just have them each call each other.

amnu3387

amnu3387

@benwilson512 thanks for your input - and although I will probably end up doing that, I still think the error should be shown from its line on the used module and not the line where the use statement is (not sure how difficult it is to achieve in technical terms…).

amnu3387

amnu3387

Thanks @ericmj - that indeed answers my question. I will nonetheless try organising my code in a different way, following ben’s and your advice - in my head - probably wrong - those functions would belong together in the same module, perhaps except messages and create, since they are all operations on the game state (but I will probably curse the future me about that choice).

ericmj

ericmj

Elixir Core Team

I’m guessing you want a single module as the public API and the module would be too large to have the implementations of all functions? If that’s the case look into using defdelegate Kernel — Elixir v1.16.0.

amnu3387

amnu3387

@ericmj I don’t seem to understand the example provided in the docs,

defmodule MyList do
  defdelegate reverse(list), to: :lists
  defdelegate other_reverse(list), to: :lists, as: :reverse
end

MyList.reverse([1, 2, 3])
#=> [3, 2, 1]

MyList.other_reverse([1, 2, 3])
#=> [3, 2, 1]

But it’s still being called with MyList (the module where they’re defined?) and they “delegate” to :lists, which is?

In my case what I was aiming at was, for instance given:

defmodule AetherWars.Duels.Stack do
def place_in_stack(state, {"gift", player, %{"from" => from, "source" => uuid, "type" => type, "payment" => %{"payment" => payment}} = gift}) do
        with {:ok, g_scroll}  <- get_in_play(state, player, type, uuid, from),
             {:ok, g_gift}    <- legal_gift?(state, player, g_scroll, gift, from),
             {:ok, int_state} <- pay_cost(state, player, g_gift["cost"], payment),
             {:ok, new_state} <- place_gift_in_stack(int_state, player, g_gift, g_scroll, gift, from)
            do
              IO.puts("ready to pass priority after gift stack")
              pass_priority(new_state, player, switch_player(player))
        else
          {:error, msg, _} -> {:error, msg, state}
        end
      end
end

That I would be able to just call this function as place_in_stack from inside AetherWars.Duels.Processor, hence my usage of use.

Itself it calls functions from .Utils and .Legal, but ultimately I see them all as functions from Duels.Processor. Mostly because I’ve decided to always pass the game state, “state”(a json representation of the whole game) is always the first argument to any interaction so mentally I was seeing them as part of the same module, but due to the sheer size I wanted to break them out into different files for easier navigation (and editing).

Now I’m questioning if they should actually be different decoupled modules - but again they only make sense when dealing with a game state that is exactly this structure?

Where Next?

Popular in Questions Top

JorisKok
I have a server on AWS, and was running a load test using artillery. When looking at the Phoenix dashboard I see the Ports going to 100% ...
New
jononomo
I am trying to figure out how Mix knows whether the environment is test, dev, or prod -- where is this set? Thanks.
New
mcarvalho
What is the difference between System.get_env and Application.get_env? For example, what are best practices to use one versus another.
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
qwerescape
Is there a way to get the call stack or stack trace at any point in the code? Not from exceptions, but an expression that returns how the...
New
LegitStack
I’m trying to make a websocket server in Phoenix or raw Elixir. I heard about gun, I think I could use cowboy, but since I’m not that sma...
New
baxterw3b
Hi guys, i’m new in the Elixir world, and i have to say, that i love it! i’m having some problem to understand anonymous functions with ...
New
kostonstyle
Hi all I want to have a unix time, from the current time plus 1 hour. DateTime.now + 1 hour How to get it in elixir? Thanks
New
chewm
Hi guys, nice to meet you to the whole forum, I’m new here, I’m trying to configure visual studio code for elixir, right now the intellis...
New
9mm
I am constructing a JSON object (map) and I need to conditionally set a field. I’m trying to write proper elixir-way code… and I’m at a l...
New

Other popular topics Top

shahryarjb
Hello, I get Persian date from my client and convert it to normal calendar like this: def jalali_string_to_miladi_english_number(persi...
New
srinivasu
How to handle excepions in elixir? Suppose i have A, B, C ,D, E modules. and each module has get() function. A.get() method will call th...
New
Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
New
vac
Hi, I'm quite new in Elixir and I'm trying to format a string to a PEM format. I have the certificate value like MIIDBTCCAe2...... and ...
New
danschultzer
None of the current solutions worked well for me, so I went ahead and built a user management system from scratch. This project took far...
548 27727 240
New
minhajuddin
I have seen a lot of code which picks the first element from a list using Enum.at(0) instead of List.first. Is there a reason why people ...
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
josevalim
Hi everyone, One of the features added to Elixir early on to help integration with Erlang code was the idea of overridable function defi...
New
9mm
I am constructing a JSON object (map) and I need to conditionally set a field. I’m trying to write proper elixir-way code… and I’m at a l...
New
aesmail
Hello guys, I have finally made it. I created an admin interface for a framework. It’s been on my todo list for years and with the curre...
New

We're in Beta

About us Mission Statement