DaAnalyst

DaAnalyst

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?

Showing Posts 1 to 10

Eiji

Eiji

DaAnalyst

DaAnalyst OP

Never applied it in a case like this. This is not an “inheritance” case. You think it will work?

DaAnalyst

DaAnalyst OP

Nope, I can’t get it to work. Besides, even if it did work, it would require other libraries to define it too.

Eiji

Eiji

It would be easier if you would give something from you like an example Elixir script or at least tell us what error you have.

I can’t get it to work.

does not tell us anything.

DaAnalyst

DaAnalyst OP

Ok. For starts, here’s the sketch of the original that I explained above that works, but imports all functions and macros from a prior library (the operator is - but it’s still the same point).

defmodule Helpers do
  def prev_import( caller_env, fun_name, arity, to_reject) do
    Macro.Env.lookup_import( caller_env, { fun_name, arity})
    |> Enum.reject( & &1 in to_reject)
    |> List.last()
    |> then( & &1 && elem( &1, 1))
  end
end

defmodule OtherLibrary do
  defmacro __using__( _) do
    prev_import = Helpers.prev_import( __CALLER__, :-, 1, macro: OtherLibrary)

    quote do
      import unquote( prev_import), except: [ -: 1]
      import OtherLibrary, only: [ -: 1]
    end
  end

  defmacro -value do
    IO.inspect( value, label: "OtherLibrary")
    OtherLibrary.doit( value, __CALLER__)
  end

  def doit( { :&, _, _}, _caller_env) do
    IO.puts( "OtherLibrary caught it!")
  end

  def doit( other, _caller_env) do
    quote do
      -unquote( other)
    end
  end
end

defmodule MyLibrary do
  defmacro __using__( _) do
    prev_import = Helpers.prev_import( __CALLER__, :-, 1, macro: MyLibrary)

    if prev_import && __CALLER__.module do
      Module.put_attribute( __CALLER__.module, :prev_import, prev_import)
    end

    quote do
      import unquote( prev_import), except: [ -: 1] # note: this is a problem for it imports all else even if unwanted
      import MyLibrary, only: [ -: 1]
    end
  end

  defmacro -value do
    IO.inspect( value, label: "MyLibrary")
    MyLibrary.doit( value, Module.get_attribute( __CALLER__.module, :prev_import))
  end

  def doit( { :%{}, _, _}, _) do
    IO.puts( "MyLibrary caught it!")
  end

  def doit( other, prev_import) do
    IO.puts( "MyLibrary passing it on to prev..")
    IO.inspect( prev_import)

    if prev_import do
      quote do
        unquote( prev_import).-( unquote( other))
      end
    else
      quote do
        -unquote( other)
      end
    end
  end
end

defmodule LibraryUser do
  use OtherLibrary
  use MyLibrary

  def run_map() do
    -%{ a: 1}
  end

  def run_capture() do
    -&bla
  end

  def run_other() do
    -2
  end
end
  • edit: removed obsolete comments from code
DaAnalyst

DaAnalyst OP

Regardless of the defoverridable/1, if I remove the lines with import unquote( prev_import) .. it will complain about ambiguous call of the - operator. If I don’t remove the lines, I am importing all the functions and macros.

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.

Eiji

Eiji

There is no good solution for this. Look that if you do import … except … then what happens with all previous imports?

For example:

defmodule Example do
  import Kernel. except: [+: 2]
  use OtherLibrary
  use MyLibrary

  # …
end

Firstly I would go for: Defining custom operators. and then I would register an (accumulate?) attribute :prev_import and within use Helper I would get said attribute and define a custom function/macro depending on value of such attribute.

Alternative both libraries could put an attribute and add @before_compile which would work as same as use Helper.

DaAnalyst

DaAnalyst OP

Yes, and there’s also another solution and that is not to stop at just taking the prev_import but then also (while still in compile time) take all of the module’s macros and functions and exclude them all unless already imported, but it’s already getting cumbersome.

My point being:

  1. There’s a scarcity of custom operators, unary in particular.
  2. It wouldn’t hurt if import had an additional option (say :not) to un-import some but not import all other functions/macros.

Just sayin’

DaAnalyst

DaAnalyst OP

Your observation is valid if the library is a very specific one. But if you’re developing something of a very generic purpose that can be applied all over your code base, then evading this problem will hardly do it. Still, even if done on a function by function basis, I am simply reluctant of importing all of third party module’s functions/macros just because I need to exclude one.

Where Next? Top

Trending in Questions Top

RSP87
I’m working on a project that simulates the bumbl example in the programming phoenix book. It acts almost like an email client. We have a...
New
nseaSeb
Hello, I know there is an approach for handling lists that allows for optimized traversal, but I can’t recall the specific method (somet...
New
brecabral
Documentation While reading the Scoped Routes section, I noticed that the documentation currently refers to a problem without explainin...
New
RemyXRenard
I’m seeing that a list inside a Kino.DataTable will be interpreted as a charlist, even if the Kino.configure() is set to charlists: :as_l...
New
velrest
So my question is quite simple and i have found no conclusive answer on forum, google or AI. Should we use :erlang.float for Integer to ...
New
samoloth
Hi, I’ve just set up an application with ash_authentication. There is only magic link strategy for now, so there is no confirmation add o...
New
asweet-confluent
I recently noticed that Elixir’s Logger defaults its primary log level to :debug when no :logger, :level application configuration is pre...
New

Other Trending Topics Top

JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
mhanberg
Hi everyone! The first release candidate for the Expert language server project is now available! We’ve published a press release detai...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Dmk
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews