kccarter

kccarter

When should you use function overloading vs control structures?

While browsing through the source code to Plug, I can across this block of code:

defp weekday_name(1), do: "Mon"
defp weekday_name(2), do: "Tue"
defp weekday_name(3), do: "Wed"
defp weekday_name(4), do: "Thu"
... snip ...

This pattern would raise a lot of red flags in most languages but given the popularity of Plug, I assume this is a reasonable pattern in Elixir.

Is this pattern preferred over using more “traditional” control structures like cond and case? If so, why and what benefits does it provide?

In Plug, there is a function for each weekday and each month. How far should you take such a design pattern when matching on simple values?

Most Liked

dimitarvp

dimitarvp

This is not function overloading at all. You can liken it to a multimethod maybe.

Using pattern matching in function heads does get compiled to case. It’s a readability improvement pattern that many have found useful because you can more cleanly isolate logic for separate input values.

There’s never an universal answer. That depends on whether you are reaping any readability benefits and/or if your team is happier with the code.

derpycoder

derpycoder

It’s the best pattern I came across and I love it.

Here are some of the benefits:

  1. No pyramid of doom.
  2. Each aspect belongs to its own function.
  3. Functions can be pipelined because of this approach and we can easily debug the things happening in a pipeline using dbg(), rather than putting print statements everywhere.
  4. Pattern Matching…

Here’s an example code I came across today, that is just awesome:


Here’s how I use it:

defmodule DerpyCoder.Photos.Policy do
  @moduledoc """
  Policy: Used to authorize user access
  """
  alias DerpyCoder.Accounts.User
  alias DerpyCoder.Photos.Photo

  @type entity :: struct()
  @type action :: :new | :index | :edit | :show | :delete

  @spec can?(User, action(), entity()) :: boolean()
  def can?(user, action, entity)

  # ==============================================================================
  # Super Admin - Can do anything
  # ==============================================================================
  def can?(%User{id: id, role: :super_admin}, _, _), do: DerpyCoder.Accounts.is_super_admin?(id)

  # ==============================================================================
  # Admin
  # ==============================================================================
  def can?(%User{role: :admin} = user, :new, _),
    do: FunWithFlags.enabled?(:new_photos, for: user)           # Admins must have the feature enabled

  def can?(%User{role: :admin} = user, :edit, _),
    do: FunWithFlags.enabled?(:edit_photos, for: user)          # By being part of photography group perhaps

  def can?(%User{role: :admin} = user, _, Photo), do: FunWithFlags.Group.in?(user, :photography)
  def can?(%User{role: :admin}, _, _), do: true

  # ==============================================================================
  # User
  # ==============================================================================
  def can?(%User{} = user, :new, Photo), do: FunWithFlags.enabled?(:new_photos, for: user)

  def can?(%User{id: id} = user, :edit, %Photo{user_id: id}),      # Can user edit photo? Yes, only if it's theirs
    do: FunWithFlags.enabled?(:edit_photos, for: user)             # And if feature is enabled

  def can?(%User{id: id} = user, :delete, %Photo{user_id: id}),    # Can user delete photo? Yes, only if it's their own
    do: FunWithFlags.enabled?(:delete_photos, for: user)           # And if the feature is enabled for the user

  def can?(_, _, _), do: false
end

defmodule DerpyCoder.Photos do
  @moduledoc """
  The Photos context.
  """

  defdelegate can?(user, action, entity), to: DerpyCoder.Photos.Policy
  ...
end

Seeing the above, can you tell what’s happening?

Is it more readable, perhaps not, but once you get used to it, it’s much more readable than an if-else-infested page.

And here’s the elegant usage of the above policy.

Photos.can?(@current_user, :new, Photo)      # Show new button
Photos.can?(@current_user, :delete, photo)   # Show delete button
Photos.can?(@current_user, :edit, photo)     # Show edit button

It’s because of this feature, handle_event, handle_info, plugs, and pipelines work the way they work.

D4no0

D4no0

Exactly, and the great thing is that these principles can be applied to other languages too, in some easier and in some you might require some libraries.

Golang is one of the languages that also embraces this principle, errors as data, handle the errors you want and ignore the rest, the only sad thing about this in golang is the lack of support structure for this style of programming, people tend to write coroutines that can handle/ignore almost all errors as to avoid crashing the entire application, witch in turn creates defensive programming, that is brittle, hard to test, hard to debug.

In my short professional career with golang, I never saw anyone not writing defensive code, thousands of lines for null pointers check, this was one of the reasons at that point I decided to leave that company and golang behind and focus on elixir.

zachallaun

zachallaun

I think it’s a really fair question and in this case, I’d say either is okay. I personally think the way Plug has done it is very readable – while there’s a bit of duplication, it’s easy to skim past it and see where the match is occurring.

I don’t think this matters too much, but it also saves a few lines over case:

defp weekday_name(1), do: "Mon"
defp weekday_name(2), do: "Tue"
defp weekday_name(3), do: "Wed"
defp weekday_name(4), do: "Thu"
... snip ...

# vs

defp weekday_name(day_of_week) do
  case day_of_week do
    1 -> "Mon"
    2 -> "Tue"
    3 -> "Wed"
    4 -> "Thu"
    ... snip ...
  end
end

Edited to add:

As for other ways of defining the mapping – I think that makes the most sense when you have more metadata and it’s nice to keep it all together in one place. For instance:

@days_of_week [
  mon: [short_name: "Mon", long_name: "Monday"],
  tue: [short_name: "Tue", long_name: "Tuesday"],
  ...
]

and then you might munge that data structure to create various lookup functions by atom, or index, or whatever.

JohnnyCurran

JohnnyCurran

I’ll expand on this a little bit. One of my favorite parts of Elixir and functional programming is the ability to “say what you want - not what you don’t”

In other words, you write much less “defensive” code in Elixir. A typical imperative function with some super simple validation might look like:

if (!form.IsValid)
{ return; }

// now okay, continue with business logic

Elixir

def submit({is_valid: true}) do
  // now we know we’re good to go 
end

def submit(form) do
  // form is invalid, log, return error, etc;
end

The difference in this contrived example is admittedly small. However the principle is important. Each function head allows us to deal with exactly what it needs to be: we know that the first function head has a valid form and we don’t need to waste mental processing time worrying about what could go wrong. Likewise, in the second, we know the form isn’t valid and we can log / return an error / do whatever to tell the client the request needs to be retried

Where Next?

Popular in Questions Top

Brian
What is the proper way to load a module from a file in to IEX? In the python world, doing something like this pretty standard: from ....
New
logicmason
Hi there, I'm working through my first release with elixir/phoenix. I've built a release with distillery and found that it crashes when I...
New
fireproofsocks
I’m working on defining a simple Ecto schema for a table (in PostGres), but I don’t see where I can define a column as NOT NULL. Conside...
New
dokuzbir
Hello, I am trying to convert my lists to string without losing brackets.For start i have 3 map. They look like these buyer = %{ id: ...
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New
Phillipp
Hey, I have a NanoPi-M3 and try to install Elixir on their Ubuntu image. I followed the Raspberry Pi installation instructions from the ...
New
aadeshere1
I have a another noob question about loop. Since elixir is immutable, while loop is not directly possible. total = 10 while total != 0 ...
New
Mooodi
Given a string, how can I get access to its character by index? Enum.at("my_string", 2) doesn't work. Or rather, not char, but a substr...
New
skosch
To my knowledge, put_in, Map.update etc. all have the one limitation of not automatically creating intermediate keys when needed (for exa...
New
jc00ke
Expanding on this topic: https://forum.elixirforum.net/t/map-typespec-question/19217 Let’s say I have a map with required and optional k...
New

Other popular topics Top

albydarned
Hello all! I am typing this post from my new MacBook Pro with the M1 chip. I’m loving it so far, and will probably use it as my daily dr...
New
jerry
Good day to you all. I have been struggling to get a query involving like and ilike to work. Can anyone assist me on this, please? pro...
New
axelson
This post is a wiki (feel free to hit the edit button near the bottom right of this post to add your own changes!) This post collects co...
239 45766 226
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
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
electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
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
lucidguppy
I have a super simple question about elixir - how would I take a file like this foo bar baz and output a new file that enumerates th...
New
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
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