I like callsites to decide what they want, call a function, and get the expected value. That’s how I normally code. When I see logic inside a function alter what it will do, that seems dishonest to me.

Honest Code

function add100(x) {
  return x + 100
}

add100(1) // 101
add100('potato') // 'potato100'

That is a dependable function. It does exactly what it should. The problem here is the callsite called the wrong function. People will usually try and fix this by shifting responsibility from the caller to the function (as if it can anticipate every stupid thing a callsite may do).

Dishonest Code

function add100(x) {
  if (typeof x === 'number') {
    return x + 100
  }
}

add100(1) // 101
add100('potato') // undefined

Now this prevents the 'potato100' bug, but a new undefined bug is fast on its heels. Why in the world is it the function’s problem if you use it incorrectly?

Idiot Warning

Suspending Disbelief

Now that I’ve gotten that off my chest, let’s talk about how we solve this problem in Elixir. The examples I run across encourage you to use function overloading (different arity), multi-clause functions (same arity), or guards to move the decision making from the callsite down to the function.

defmodule Foo do
  def add100(x) when is_integer(x) do
    x + 100
  end
end

Foo.add100(1) # 101
Foo.add100('potato') # (FunctionClauseError) no function clause matching in Foo.add100/1

This feels really weird to me, but I’m comforted by the error. It’s an improvement over the undefined bug our JS implementation created.

Acceptance

Part of learning a new language is being open to new ways of thinking. As such, it seems that the Elixir way is to ensure a function knows how to handle all the different patterns that may be thrown at it. If a new pattern is used, it raises an error and you are forced to expand the repertoire (or fix the callsite). That seems reasonable.

The way I’ve done it in the past suggests that, if the callsite has a new way to use a function, it would need to call a different function. It would technically be a different function, but essentially the same. The onus is then placed on the caller to understand the variations of a function and choose between them.

Put Down that Koolaide

Before I get all weak-kneed for Elixir and its function overloading, there’s just one more thing I can’t come to terms with.

defmodule Foo do
  def bar(x) when x < 100 do
    x + 100
  end

  def bar(x) do
    x + 100000
  end

  def bar do
    ["a", "b", "c"]
  end
end

Foo.bar(1) # 101
Foo.bar(100) # 100100
Foo.bar() # ["a", "b", "c"]

The variations of a function do not need to return the same data structure! That doesn’t sit right with me. If I’m calling bar and then playing with the result, what the hell happens when someone changes a guard for bar and suddenly I start getting back a different type of data?

Is this a real concern? Would this ever happen in real life? Will the compiler guide me right to it? Would it be easier to add the variation to all the callsites out in codeland?

I’m not sure right now. It feels weird to me, but I’ll go along for now and hopefully it will make more sense soon.

Refs

https://stackoverflow.com/questions/28377135/how-do-you-check-for-the-type-of-variable-in-elixir https://learnyousomeerlang.com/types-or-lack-thereof