DaAnalyst
Fallback to nearest prior imported library module and not necessarily Kernel when overriding an operator?
I may be wrong here, but I really don’t see a proper way to override an operator in one’s own library while allowing for a fallback to whatever library module the user imported before it that also overrides the same operator (or any other function or a macro), e.g.:
use OtherLibrary # imports its `*` override for maps
use MyLibrary # imports its `*`override for lists
2 * %{ a: 1}
# => MyLibrary passes it to the whatever prior library overriding `*` i.e. OtherLibrary and OtherLibrary does its job
2 * [ a: 1]
# => MyLibrary does its job
2 * 2
# => 4 (MyLibrary passes it to OtherLibrary which passes it to Kernel)
The idea is to fetch the last prior import by relying on Macro.Env.lookup_import/2, and it works, but I have another problem. All I can do with the import prior to mine in MyLibrary.__using__/1 is import unquote( prior_import), except: [ *: 2] and that’s not exactly what I need. The reason is I cannot afford to assume that the caller is ok with my library importing all other macros and functions from the OtherLibrary module (otherwise unknown to me), and I don’t know of any other way to un-import just this particular function or macro.
Any ideas?
Most Liked
benwilson512
I mean as a general rule, I would simply not try to do this at the module level. If a library wants to override an operator that should be lexically scoped inside one function at a time.
al2o3cr
I’m less-opposed to custom operators than most, but IMO if you’ve got different overrides for the same operator overriding each other you may want to consider a different design.
Setting the specifics of operators aside, it sounds like what you want is something where you can import heads for one function from multiple modules:
# HYPOTHETiCAL: THIS CODE DOES NOT WORK - SEE NOTES BELOW
defmodule ImplOne do
def some_fun(x) when is_list(x), do: IO.puts("list!")
end
defmodule ImplTwo do
def some_fun(x) when is_map(x), do: IO.puts("map!")
end
defmodule Demo do
import ImplOne
import ImplTwo
def do_stuff do
some_fun([1,2,3])
some_fun(%{a: 1})
end
end
This currently complains:
** (CompileError) iex:11: function some_fun/1 imported from both ImplTwo and ImplOne, call is ambiguous
(elixir 1.14.0) src/elixir_dispatch.erl:149: :elixir_dispatch.expand_import/7
(elixir 1.14.0) src/elixir_dispatch.erl:119: :elixir_dispatch.dispatch_import/6
(elixir 1.14.0) src/elixir_expand.erl:567: :elixir_expand.expand_block/5
(elixir 1.14.0) src/elixir_expand.erl:40: :elixir_expand.expand/3
(elixir 1.14.0) src/elixir_clauses.erl:37: :elixir_clauses.def/3
(elixir 1.14.0) src/elixir_def.erl:197: :elixir_def."-store_definition/10-lc$^0/1-0-"/3
iex:6: (file)
TBH this style of importing reminds me of the worst parts of OO, where you end up playing “your method is in another castle” through twelve classes all named Base.
If you’re doing something like this codebase-wide, I’d recommend combining all the relevant plumbing into one module that gets used consistently. That module may not be pretty, but it will be centralized - the thing you want to avoid is having your codebase littered with slightly-different sets of operators etc in different modules.
Eiji
The question was what unary operator could be added - overriding is not adding.
That’s important to say as you only repeat after me one character $, so in fact you confirm there is not much to add here and definitely not many things.
You need to understand that any overrides and multiple meaning of same operators is more confusing than helping especially when we are talking about % as every new developer would think about maps in first place. Also SpecialForms naming is not random. If you really want to write for example your own for special form then you would need to go outside Elixir.
But then those would no longer be operators. ![]()
When starting with programming and Elixir I have also expressed many things and found myself that many of them does not makes sense at all only because I did not understand something.
Let’s go back to your question … If you really want to go this way then consider splitting your macros.
defmodule Example do
use First
use Second
import First, only: [-: 1]
# or
# import Second, only: [-: 1]
end
This is simplest way without going around. The developer could decide which function/macro should be imported. Also it’s highly recommended to prefer import in favor of use when everything you do is to import functions/macros.
Eiji
Sorry, but I do not see this good. In same way someone may challenging Ruby-like syntax (do … end notation) or functional programming and what the community should reply for such a message? Well … you are free to use other languages or write your own?
Ok, so … you are using naming without understanding what it means? As before non-unary operators have 2 arity. different arity are your words. Now see the definition of arity. As in wikipedia the arity is number of arguments, but somehow you changed it to freedom in overriding. What’s more interesting you have this freedom as you are overriding operator, but instead you have conflict between two libraries. ![]()
In fact it is and this is terribly important in programming. Before next major version (2.x) of Elixir we can expect new features and not changing behaviour of current ones. If that would not be a case upgrading between every Elixir release would be like a hell as every time you would need to update all of your projects (from pet project up to production ones) in order to make them work in new version.
There is even no schedule plans for 2.x version of Elixir and to replace language basics you would need to convince not just maintainers, but also the whole community. So far you have no likes or replies like I also have such problem and therefore for now it looks like that would be a change for just one person for just one use case in which you have other ways to solve conflict problem.
It’s not like that community does not consider changes in language, but just for example say … When last time you have used static types in Elixir? Even if community is willing to consider adding this the topic have many years. Every change of behaviour would affect whole community and not only you. It’s not likely such changes would be delivered quickly if any.
Our community is rather used that some library could override something from Kernel (like def in components), but I do not remember a case when one library changed other one. Of course there are libraries that extends other libraries in many ways, but they are not overriding their functions/macros. For you it’s a topic like anyone else, but for others it’s rather something new. If it would not be new your posts would be merged with existing topic or this topic would be closed mentioning that there is a similar one or somebody would link to existing solutions working already in production.
Never heard about such case. It’s rather use B instead of A and not override A to make B work. If you have shared code simply use it in both A and B and let them define their own function/macros. I completely do not see a use case for what you are looking for.
defmodule Shared do
defmacro __using__(_opts \\ []) do
quote do
def shared, do: :ok
end
end
end
defmodule A do
defmacro __using__(_opts \\ []) do
quote do
use Shared
def sample, do: :a
end
end
end
defmodule B do
defmacro __using__(_opts \\ []) do
quote do
use Shared
def sample, do: :b
end
end
end
defmodule Example1 do
use A
def run do
IO.inspect(shared())
IO.inspect(sample())
end
end
defmodule Example2 do
use B
def run do
IO.inspect(shared())
IO.inspect(sample())
end
end
Example1.run()
# :ok
# :a
Example2.run()
# :ok
# :b
As before import is not a macro like those you write. It’s expanded by compiler. Do you really expect to rewrite compiler for just one case?
Think that everyone would do like you. Every library would need to maintain their own compiler in fact it would be like every library would have it’s own variation (slang?) of language. This is just a crazy idea. Alternatively every library request a change to Elixir language… It would become a dumpster.
This is why by default developer should not think how I could change the language to make my code work especially if you have alternative ways to make your library work.







