June 28th, 2024

Elixir Anti-Patterns

The document discusses code-related anti-patterns in Elixir v1.18.0-dev, highlighting issues like comments overuse, complex 'with' expressions, dynamic atom creation, long parameter lists, and namespace conventions. It provides examples and refactoring suggestions to improve code quality and maintainability.

Read original articleLink Icon
Elixir Anti-Patterns

The document discusses code-related anti-patterns in Elixir v1.18.0-dev. It highlights issues like comments overuse, complex else clauses in 'with' expressions, complex extractions in clauses, dynamic atom creation, long parameter lists, and namespace trespassing. Examples and refactoring suggestions are provided for each anti-pattern to improve code readability and maintainability. For instance, it advises against creating atoms dynamically due to potential memory issues and suggests using explicit conversions or existing atoms. Similarly, it recommends grouping related arguments using key-value data structures to address the problem of long parameter lists. The document emphasizes the importance of adhering to namespace conventions to prevent module name clashes in libraries. Overall, it provides insights into common pitfalls in Elixir coding practices and offers solutions to enhance code quality.

Related

Orb: Write WebAssembly with Elixir

Orb: Write WebAssembly with Elixir

Orb leverages Elixir's ecosystem to simplify WebAssembly writing, offering features like composable modules, Hex package manager, ExUnit testing, macros, and syntax highlighting. It enables Elixir code compilation to .wasm, supports reusable modules, and integrates existing Elixir libraries for MIME tasks, showcasing flexibility in WebAssembly development.

Avoiding Emacs Bankruptcy

Avoiding Emacs Bankruptcy

Avoid "Emacs bankruptcy" by choosing efficient packages, deleting unnecessary configurations, and focusing on Emacs's core benefits. Prioritize power-to-weight ratio to prevent slowdowns and maintenance issues. Regularly reassess for a streamlined setup.

Elixir Gotchas

Elixir Gotchas

The article highlights common pitfalls in Elixir programming, including confusion between charlists and strings, differences in pattern matching, struct behavior, accessing struct fields, handling keyword lists, and unique data type comparisons.

Elixir for Python Developers

Elixir for Python Developers

This comparison delves into Python and Elixir, emphasizing syntax disparities and distinctive traits. Elixir prioritizes immutability and pattern matching, while Python focuses on object-oriented elements. Both share list comprehensions and parallel processing, yet Elixir's functional approach and pattern matching distinguish it. Elixir's Stream module supports lazy operations for big data, contrasting Python's I/O streams. Python developers can benefit from exploring Elixir.

Elixir for Humans Who Know Python

Elixir for Humans Who Know Python

The article explores transitioning from Python to Elixir, emphasizing Elixir's concurrency, Phoenix framework, LiveView feature, immutability, and pattern matching. It compares Elixir's functionalism and control flow to Python, showcasing Elixir's efficiency for web development.

Link Icon 6 comments
By @Terr_ - 5 months
> Complex else clauses in with

This has proven controversial, but sometimes the Else behavior is very sensitive to which of the With expressions failed (especially for better logging) and I maintain that the least terrible approach involves augmenting the With clauses using atoms to identify each expression when it fails, ex:

For example:

    with {_, {:ok, key} } <- {:phase_key,    get_key()         },
         {_, {:ok, door}} <- {:phase_unlock, unlock(door)      },
         {_, %Thingy{}  } <- {:phase_thingy, fetch_thingy(door)},
         {_, %Widget{}  } <- {:phase_widget, fetch_widget(door)} do
    else
         {:phase_key,    {:error, _msg} = err} -> # I can tell the key was missing, rather than the lock broke
         {:phase_unlock, {:error, _msg} = err} -> # I can tell the lock broke, rather than missing a key
         {:phase_thingy, nil                 } -> # I can tell this nil value is a missing thingy, not a widget
         {:phase_widget, nil                 } -> # I can tell this nil value is a missing widget, not a thingy
    end
As a big bonus, when something crazy-unexpected is emitted to cause a WithClauseError--like "hello world"--you have a much better chance of quickly diagnosing what happened from log messages, instead of inspecting a dozen different logical branching or trying to blindly repro it.

Pre-buttals:

"Just rewrite those 4 functions to return better somehow" -> No, that's not always practical or even possible. In the above example, the signatures of some of these functions are already perfectly fine on their own. The ambiguity comes when they get used near others in different ways, and trying to assign globally unique features to match on is a bad idea.

"Create 4 private functions to wrap things" -> Ugh, now the person reading the code has an even harder time, since they need to refer to functions elsewhere in the file, any of which might be hiding unexpected bonus behavior. Those 4 private functions might not even be useful in other with-statements in the same module, causing you to make even more private functions... and all of it still falls apart the moment your Else logging/cleanup needs to record or revert something from a prior (successful) step.

By @Terr_ - 5 months
Anti-pattern: Overuse and misuse of macros. (Or perhaps not a "pattern", since the manifestations are so varied?)

For non-Elixir folks, imagine something that looks like a regular function-call where expressions are evaluated and the results are passed in... but in reality all arguments are symbolic inputs to something that emits new code.

For example, you might see this unassuming line in your editor:

    describe_combo(a(foo),b(bar))
But what might actually be compiled is:

    # What might actually get compiled
    # Runtime results like "a(1)+b(2)=3"
    "a(" <> to_string(foo) <> ")+b(" <> to_string(bar) <> ")=" to_string(a(foo)+b(bar))
And if someone in your codebase is an evil misanthrope:

    foo = "haha I changed your local variable"
    IO.puts("surprise, suckers") # Prints to console
    kick_puppies()
Macros are actually part of the core DNA of the language and underpin how some of its syntax works... but with great power comes great potential for WTF.
By @mervz - 5 months
Elixir is one language I REALLY want to use as the solo developer working on internal infrastructure tools; but I'm very hesitant as I don't want to be cruel to the person who eventually replaces me and has to maintain these applications written in this seemingly (awesome) niche language.

Instead, I'm stuck writing applications in Go which is a language I absolutely despise but can't deny it's ability to get shit done regardless of the insane amount of boilerplate code I have to write.

Any predictions on whether Elixir will eventually gain more traction? The language and platform are rock solid and it's the best developer experience I've encountered. Phoenix is by far my favorite web framework and just feels very intuitive and complete. The lack of a good IDE is one huge pain point that I can't seem to overcome (I don't care how much people praise it, I know it's the best but VSCode is awful for Elixir development).

I just wish this language was more popular because it deserves to be!

By @fouc - 5 months
Jose Valim gave an overview of this in last month's ElixirEU Conf https://www.youtube.com/watch?v=agkXUp0hCW8
By @mjoin - 5 months
Unpopular opinion given all the craze around Elixir these days. I find the syntax extremely verbose and hard to read compared to e.g. Python. I was told great things about it by engineers I really respect. But each time I start reading Elixir code bases to get a good look at what an actual Elixir project looks like, I find the code hard to read and understand. Is it just me?