Hello,
I would like to propose the addition of a new feature to Elixir.
Suppose a team created the following module
defmodule A do
defmacro __using__(_opts) do
quote do
def task(:coffee), do: "make coffee"
def task(:cookies), do: "make cookies"
end
end
end
Now, a developer in some other team creates the following module
defmodule MyModule do
use A
end
This module allows to make coffe or cookies, and this is a good thing.
Now suppose that after some time, someone else makes finds the next module useful
defmodule B do
defmacro nice_macro() do
quote do
def task(x), do: "something good is done, but sometimes there are hidden missiles being launched"
end
end
end
and modifies `MyModule` like this
import B
defmodule MyModule do
use A
B.nice_macro()
end
Oops!!! Very bad things can happen now if `MyModule.bar/1` is called with anything but `:coffee` or `:cookies`.
This situation can be called an "unexpected clause problem" because it makes an existing
function to be inadvertently redefined by code defined sometime and somewhere else.
The proposed solution to this problem is to introduce the `defnotoverridable` clause, like this
defmodule A do
defmacro __using__(_opts) do
quote do
def task(:coffee), do: "make coffee"
def task(:cookies), do: "make cookies"
defnotoverridable [task: 1]
end
end
end
or like this, by declaring all existing functions at some point to be not overridable
defmodule A do
defmacro __using__(_opts) do
quote do
def task(:coffee), do: "make coffee"
def task(:cookies), do: "make cookies"
defnotoverridable :all
end
end
end
Note that a `defnotoverridable :all` can also be a strong guarantee against a missing `defnotoverridable`, like by doing
defmodule B do
defmacro __using__(_opts) do
quote do
defnotoverridable :all # defend just in case task/1 was previously defined at the macro call site
def task(x), do: "something good is done, but sometimes there are hidden missiles being launched"
end
end
end
Furthermore:
1) a `defnotoverridable` cannot be undone by a subsequent `defoverridable`, but a `defoverridable` can be undone by a subsequent `defnotoverridable` (the point is, `defoverridable` indicates a possibility, whereas `defnotoverridable` indicates an impossibility)
2) a `defnotoverridable [foo: 1]` requires function `foo/1` to have been defined before
I hope you find this addition useful.
Thanks,
Mário Guimarães