I don’t want to get stuck like this down the line, so I am spending time going through books and documentation, so I know as much as possible.
For instance this was covered in Elixir in Action. Excerpt:
Recall that everything in Elixir is an expression that has a return value. The if expression returns the result of the executed block (that is, of the block’s last expression). If the condition isn’t met and the else clause isn’t specified, the return value is the atom nil:
message = ""
if some_var do
P.S. What I am trying to say is, that it’s overwhelming getting into Elixir. I have to read a lot, so I overcome challenges.
Subqueries and named bindings are also supported. The builder also needs to be SQL-injection-free when properly used.
How to achieve this goal? By using macros, of course. And through building this builder, I learn a whole lot of Elixir metaprogramming.
Make a custom tableau-expression-like syntax parser
I wrote a tokenizer by hand, and a parser using :leex.
Yet another workflow
There are many problems I’ve not solved yet. One of them is how to store arbitrary user-provided JSON-serializable data that embeds system data types at any level. I want to solve this problem with Messagepack with custom extension instead of JSON, but I’m not allowed to do that.
Some of the challenges of Elixir I had to deal with more and more in the last years due to helping (or being in charge) with fairly busy systems where efficiency matters, are:
Taking into account process boundaries and what data gets copied from one process to another. This has led me to discussions during which I completely scrapped a nascent mini library I wrote and replaced it with a very thin layer over the :jobs Erlang library, for example.
Binary copying or referencing. Every now and then we have threads on this forum where people are puzzled with growing memory usage that does not go down and the reason is binaries with too many references pointing at them, 99% of the time. Hence the need to become friends with :binary.copy, or Jason.decode(text, strings: :copy) , or others.
The OTP machinery is amazing, though some things in the library ecosystem remain underdeveloped in terms of convenience and ease of use and terse code – e.g. when you need to monitor a process, or make sure it traps exits and its parent is aware of it, or have a GenServer worker branch outside of itself and its parent supervisor’s children to do a long-running task etc., the resulting code is just not readable, is boilerplate-y, and it reads like incantations We still need a more English-like DSL for OTP! (Though granted, that’s more like a usability complaint but I’ve seen people get confused when shown the current way these tasks are done. That’s important; stuff should be 100% intuitive.)
Even though I have no good ideas on how to improve upon that, I still feel Elixir projects have way too many files.
There are likely more but these are the ones that I got off the top of my head.
The product I work on is a pretty big Rails app. There is no static analysis at all, no interfaces; it’s purely interpreted and your only saving grace is writing tests.
We’re slowly porting over some of our backend to Elixir (and even experimenting with some frontend stuff)… and just having a compiler is a major step up. Then Dialyzer is awesome on top of that! We’ve gotten to the point where we pretty much typespec everything, and the immediate feedback is incredible. I can’t wait to see where the set theoretic typing stuff goes.
We definitely use less DSLs in Elixir, but compile time checking of DSL usage is great!
HEEX validates your HTML, Absinthe validates so much of your GraphQL schema… all at compile time.
I think functional is easier than OO (especially Ruby OO). Tracing through our Ruby code is a nightmare. Objects create objects that create more objects. Classes inherit and mixin several modules, so we basically have multiple inheritance all over the place. There are no interfaces, so we have to traverse tons of files to find methods that raise "implement me". Or worse, modules that expect methods to be implemented, not in the class including it, but some other module that should be mixed into the base class. And lastly (not specific to Ruby), you have to think about which methods mutate state (instance vars). In Elixir, you have arguments and a return value (for the most part). It makes reading and tracing through code 10x easier, imo.
And also, the documentation system is fantastic. I find it both satisfying and fun to write documentation now. For a lot of pure functions, we write doctests. Contrast this to our Ruby codebase where no one likes writing docs… so there are none. Our CI system builds the Elixir docs and uploads them to a static site.
All this stuff adds up to better maintainability. In short: a compiler, type system, static analysis, no OO, and documentation system. All that with the benefits of a dynamically typed language. It’s like having your cake and eating it too!