Conditional import and lexical scope

Is there a way to conditionally import a function, but make the import available outside the if or case?

this obviously works:

  def foo() do
    import Enum, only: [map: 2]

    map(1..5, &IO.inspect(&1))
  end

this works as well:

    if is_integer(term) do
      import Enum, only: [map: 2]
      map(1..term, &IO.inspect(&1))
    end

this does not work due to lexical scope.

    if true do
      import Enum, only: [map: 2]
    end
  
    map(1..integer, &IO.inspect(&1))

So far so good.
My question is, how can we import conditionally at runtime, and make the import available outside the condition clause.

The only solution that I thought it could make it work was this, it does not and it baffles me.:

    true && (import Enum, only: [map: 2])
    map(1..integer, &IO.inspect(&1))

Note: for my use case I can solve this by running the condition at compile time, but I am just trying to understand how to do it at runtime.

Thank you

Imports are compile-time.

1 Like

Elixir being FP I don’t see any other way except closures or just passing a function like so

defmodule Helper
  if compile_time_condition do
    def with_scope(fun) do
      import Enum
      fun.()
    end
  else
    def with_scope(fun) do
      import Stream
      fun.()
    end
  end
end

Or you can do it with a keyword list parameter with :do and :else like the if macro does.

1 Like

That’s what I’d do, which seems a lot cleaner than conditionally importing things.

mod = if condition, do: Stream, else: Enum
mod.map(1..integer, &IO.inspect(&1))
4 Likes

This is pretty much what i am doing.
It is for a BackPort module, where I give support for older Elixir versions importing functions/macros based on macro_exported?(Kernel, :macro_name, :arity)

It still baffles me why using && will not work.

macro_exported?(Kernel, :is_struct, 2) && (import BackPort, :is_struct, 2)

I have not read && creating a lexical scope within itself.

The behavior makes sense if you look at the implementation:

The right-hand expression in && winds up inside a case statement, so the import does not leak out.

2 Likes

@al2o3cr you are so right! I wrongly assumed that it was &&/2 was inlined by the compiler. I guess it all comes down to this, anyway had it been, it would have been the same:

:erlang.andalso(true, (import Enum, only: [map: 2]))
map(1..integer, &IO.inspect(&1))

because this seems to work the same way.

So I guess the only option here is evaluating the condition at compile time.

Thank you all guys for your contributions.