Or a small rant against Elixir.
When you first start learning about Elixir, you learn that states and side effects are evil, that every function should be “pure”, taking strictly an input and returning strictly an output without mutating anything on the process. Then, you learn this:
iex(20)> IO.puts "the very first lie"
the very first lie
IO.puts/1 takes a string and returns an atom
:ok with an obvious big side effect, writing to
stdout. Why is this okay in this case? We don’t know. Why not being strictly functional and having
IO.puts take 2 arguments, a representation of the terminal input/output state and the string we want to print deferring the actual printing when we actually run the program? That will be more functional no matter how impractical it is. But, why not? If functional is useful in other cases, it should be also useful here to work like this. If it’s impractical, how we define what should be purely functional and what should have a more pragmatic approach? The absence of guidelines on this particular topic doesn’t help to understand where to draw the line. I think this absence just shows how clueless we can be about functional programming.
So, modern functional programming motto says it’s okay to not be functional for anything IO related, file related, display related, or DB related, but we’ll keep the internals of your program functional. Apart for GenServers. That’s doable. However, as we write program to interact with at least one of those, it means most of your program won’t be functional. And, we want these side effects or our program is useless. The alternative: having, logs, stdout, or db variables to be passed around with every functions to avoid unclear side effects is not something that we want as it’s just hell.
Take Phoenix, an elixir based web framework. They are trying the latter approach. They pass an object
conn to every single functions. To avoid having global states at all costs.
conn contains all request data and all response data. So, you’ll have stuff like this:
def index(conn, _params) do
categories = Repo.all(Category)
render(conn, "index.html", categories: categories)
Please note the database layer is not passed to the function as well as the file system and the logger data. Why not having something like this:
def index(conn, db, fs, logger, _params) do
categories = Repo.all(db, Category)
render(conn, db, fs, logger, "index.html", categories: categories)
Maybe because it’s not pragmatic or reasonable. Maybe global states are a necessary evil. We don’t want to pass a multitude of variables around just for the sake of not having side effects.
Functional programing, object oriented programing, imperative and declarative programing, are all interesting by themselves and bring to the table well-thought concepts that make us better programmers. Here the big secret. Good programers will produce good code anyway. Bad programers will produce unreadable code anyway. Trying to make bad programmers better by imposing to everyone some magic programming paradigm is a mirage. It never worked and is the biggest lie in our industry.
Hacker Noon is how hackers start their afternoons. We’re a part of the @AMIfamily. We are now accepting submissions and happy to discuss advertising & sponsorship opportunities.
To learn more, read our about page, like/message us on Facebook, or simply, tweet/DM @HackerNoon.
If you enjoyed this story, we recommend reading our latest tech stories and trending tech stories. Until next time, don’t take the realities of the world for granted!