garrison

garrison

A library I’m working on has a lot of functionality related to string prefixes (technically binaries, but they’re mostly strings). I have to add prefixes to keys a lot, and I have to pattern match them back off a lot.

Like:

defp prefix(key), do: "foo/" <> key
defp is_foo("foo/" <> key), do: true
defp is_foo(_key), do: false

And so on. But a lot of this functionality is embedded in application code, in anonymous functions, in Enum.maps, variable assignments, and so on. It’s everywhere.

I want to standardize these prefixes across the codebase going forward. They are already standardized, of course, but I have module attributes scattered across a couple dozen modules when I want them to be in one place.

There is, essentially, a lot of code like this, with the attributes duplicated across many modules:

@prefix "foo"
defp something(@prefix <> rest), do: rest

The docs recommend using public functions as constants instead of attributes, but this doesn’t work because I also need to use them for pattern matching everywhere.

I can think of two approaches off the top of my head:

First, use a module with a __using__ macro to inject the same set of attributes into every module. This would be acceptable for my use case, but it seems kinda cursed.

Second, replace the attributes with macros that inject the constants into the expressions. Essentially, the function-as-constant approach but with a macro instead. This sounds more sane to me, and is what I’ll probably do, but I figured I should seek some guidance in case I’ve overlooked something. This approach is conspicuously absent from the docs I linked.

Showing Posts 1 to 9

dimitarvp

dimitarvp

Just to make sure: those prefixes are not changing during runtime, correct?

garrison

garrison OP

That’s correct, they are essentially permanent. I just hate having to redefine them everywhere: it’s a mess, and a typo would be easy to make and very bad.

dimitarvp

dimitarvp

Then I would go for the __using__ approach but would not hard-code the values there; I’d make them part of the application’s config and just use Application.compile_env in the code that’s injected via the macro. That would help with the single source of truth problem.

garrison

garrison OP

This is contextual enough that I would not expect you to understand, but in this particular case the values are “implementation details” in such a way that using config seems like the wrong choice. It would be like defining magic numbers for a file format using config options - not quite right. These values are not going to change.

Anyway, I don’t love the __using__ approach because I feel like it’s less discoverable. At some point someone is going to read my code and wonder where @prefix came from, and it’s not going to be defined. I don’t like that.

If I use individual macros at least they will have a clear definition somewhere discoverable.

dimitarvp

dimitarvp

In that case and with that context I’d agree with you. Though it has to be said I find both suboptimal in different ways but I don’t see a [much] better way myself.

cmo

cmo

I don’t hate the __using__ method for those cases that are truly global and/or want to be used in function heads.

It is a little less discoverable but it doesn’t take me too long to see the use MyModuleAttrubuteConstants at the top of the module and remember what I’ve done.

mudasobwa

mudasobwa

Creator of Cure

Use global public macros as constants then.

defmodule Prefixes do
  defmacro prefix(prefix \\ "foo", rest) do
    case __CALLER__.context do
      :match -> 
        quote generated: true, do: unquote(prefix) <> unquote(rest)
    end
  end
end

defmodule Usage do
  import Prefixes, only: [prefix: 1]

  def something(prefix(key)), do: key
end

Resulting in:

iex(2)> Usage.something "ggg"
** (FunctionClauseError) no function clause matching in Usage.something/1    
    
    The following arguments were given to Usage.something/1:
    
        # 1
        "ggg"
    
    iex:6: Usage.something/1
    iex:5: (file)
iex(5)> Usage.something "fooggg"
"ggg"
garrison

garrison OP

This is clever, thanks for the reply.

What I had in mind (my second idea) was something slightly simpler:

defmodule Prefixes do
  defmacro prefix, do: "foo"
end

defmodule Usage do
  import Prefixes
  def something(prefix() <> rest), do: rest
end

I like this a bit more because I think the syntax is less surprising. It looks almost exactly like a module attribute except it can be used anywhere, plus it’s discoverable.

One thing I was wondering is: does anyone know whether this would have any performance impact (at least at compile time)? I would think it would be insignificant.

mudasobwa

mudasobwa

Creator of Cure

Well, Elixir macros are being expanded during compile-time and the AST they return is being directly injected into a resulting beam code. That said, the runtime would have zero impact compared to something("foo" <> rest) because _this is exactly the code the VM machine would see.

Duting the compilation time, yeah, it’ll gorge a couple of processor ticks, but you’d never notice this.

— All posts loaded —

Where Next? Top

Trending in Questions Top

stjefim
Hello! Suppose you are building workflow (order / task / payment) processing system with the following requirements: Each workflow con...
New
water
I’m aware of 2 such projects, are there others? git: GitHub - team-alembic/realworld: A fullstack Phoenix LiveView application with back...
New
itzmidinesh
Hi all, I’m working on a Phoenix project where I need to make external HTTP requests and I’m trying to choose between Req and HTTPoison....
New
peck
Hi everyone. My team and I have been working on a fairly modest app based around video streaming and chat, but we’ve landed a customer t...
New
leoschweizer
Hi, we run different LLM models across a cluster, with each node loading a configurable subset. Model-specific queues let nodes consume ...
New
csmr
Hi all, I’m new here and would like to hear peoples views on my Elixir/Phoenix based game-engine! :smiley: Mutonex gamedesign was finish...
New
Stef-Verniers
Hello I’ve been coding in Elixir for almost two years now because of my current job where we implemented Elixir in our application. But ...
New

Other Trending Topics Top

CodeSync
That’s a wrap on Code BEAM Europe 2026! Thanks to everyone who came to Haarlem or joined online, and to our speakers, trainers and sponso...
New
dominicletz
Hey there, I haven’t seen a list of useful Elixir specific skills for the popular AI tools yet so I’m starting one here with my first. ...
New
raa-ziq
Hi everyone :waving_hand: I wanted to share something I’ve been building — NowBlind, a random chat app designed to help people discover ...
New
mudasobwa
I fully migrated to my own harness from Anthropic/Gemini and I think it’s time to share it. Welcome DSH, the DeepSeek Harness, fully writ...
New
KristerV
Hey. Is there anyone here who creates agents in their apps? Not talking about using agents, but creating them. I’m finding it pretty diff...
New
OndrejValenta
(I just needed to vent somewhere and LinkedIn is full of hope, or hype, I’m not sure which exactly) AI dream has many faces, but general...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews