DaAnalyst

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

benwilson512

Author of Craft GraphQL APIs in Elixir with Absinthe

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

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

Eiji

The question was what unary operator could be added - overriding is not adding. :smiley: 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. :sweat_smile:

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

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. :exploding_head:

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.

Where Next?

Popular in Questions Top

Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
New
freewebwithme
Using vs code and installed ElixirLS: support and debugger. And I got an error popped up on start up says Failed to run ‘elixir’ comma...
New
minhajuddin
I have seen a lot of code which picks the first element from a list using Enum.at(0) instead of List.first. Is there a reason why people ...
New
vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New
aalberti333
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this: ...
New
Kagamiiiii
Student & New to elixir. Nice language. I want to convert a english character, e.g. “a”, which is stored in a variable, to it’s asci...
New
lk-geimfari
What is most correct way to open, read and parse JSON file with poison? For example if we have example.json file in root of some projec...
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
sabri
Can someone explain the settings of pool_size of Ecto in config file? and what is the recommend size? Thanks
New
Codball
Mix format works fine if run from the cmd. I’ve followed this to facilitate the implementation into VSC which involves downloading an ext...
New

Other popular topics Top

Qqwy
Update: How to use the Blogs & Podcasts section You can post links to your blog posts or podcasts either in one of the Official Blog...
3268 119930 1237
New
lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
sergio
In Ruby, I can go: User.find_by(email: "foobar@email.com").update(email: "hello@email.com") How can I do something similar in Elixir? ...
New
srinivasu
How to handle excepions in elixir? Suppose i have A, B, C ,D, E modules. and each module has get() function. A.get() method will call th...
New
sergio_101
I am VERY much an elixir newbie. I have taken one elixir course and one phoenix course on Udemy. During that course, I saw the instructor...
New
grych
Hi folks, Few months ago I have announced the proof-of-concept of the library to manipulate the browsers DOM objects directly from Elixi...
639 49522 488
New
malloryerik
Hi, this is for people who, like me, have had some friction using .html.heex templates in VSCode. The solution seems to be, in a hyphena...
New
chrismccord
This release brings a number of exciting features, including integration with the new Phoenix LiveDashboard and Phoenix LiveView. There h...
New
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New
9mm
I am constructing a JSON object (map) and I need to conditionally set a field. I’m trying to write proper elixir-way code… and I’m at a l...
New

We're in Beta

About us Mission Statement