elixir alias, require, import, use


alias

  • alias 는 주어진 모듈이름에 대한 별칭을 설정하는 기능이다.
    • 따라서 특정 모듈에 대한 shorcut을 설정할 때 주로 사용한다.
  • alias 로 별칭을 설정한 경우에도 Full Path 로 접근이 가능하다.
  • alias 는 선언된 스코프 내에서만 유효하게 작동한다 (lexically scoped)
defmodule Alias do
  defmodule Putter do
    def puts(var) do
      IO.puts var
    end
  end
end

defmodule LexicalScope do
  def scoped do
    alias Alias.Putter
    Putter.puts "hello world"
  end

  def no_scoped do
    Putter.puts "hello world"
  end
end

alias Alias
Alias.Putter.puts("hello")

alias Alias, as: P
P.Putter.puts "hello world!"


alias Alias.Putter, as: PP
PP.puts "hello world!"


LexicalScope.scoped()
# "hello world"

LexicalScope.no_scoped()
# ** (UndefinedFunctionError) function Putter.puts/1 is undefined (module Putter is not available).

Elixir에 정의된 모든 모듈은 Elixir라는 main namespace에 정의되어있다고 할수있다. (ex. Elixir.String) 하지만 편의를 위해 Elixir라는 참조 단계는 건너 뛸 수 있다.

require

  • 매크로를 사용하게 하기 위한 키워드이다.
  • 매크로는 매크로를 사용하는 프로그램이 컴파일 되는 시점에, 그 매크로가 어떤 역할을 하는지를 정확하게 알고있어야한다. 이러한 매크로는 어딘가에 정의되어 있을 것이고, require는 특정 매크로를 호출한 모듈이 컴파일 되기 전에 require로 호출한 모듈이 컴파일 됨을 보장해준다,
    • 즉 호출하는 모듈이 컴파일 될 때, 호출받은 매크로가 컴파일되어 호출하는 모듈의 코드를 동적으로 변경시킬 수 있도록 한다.
  • requirealias와 마찬가지로 선언된 스코프 내에서만 유효하다. (lexically scoped)
    defmodule RealMacro do
      defmacro macro(var) do
          IO.inspect var 
      end 
    end
    
    
    defmodule MacroUser do
      def run() do
          require RealMacro 
          RealMacro.macro(:some_var)
          # :some_var
        end
    
        def run_no_req() do
          RealMacro.macro(:some_var)
          # UndefinedFunctionError) function RealMacro.macro/1 is undefined or private. However, there is a macro with the same name and arity.
        end
    end
  • 매크로를 사용하는 경우가 아니어도 require 키워드를 사용할 수 있다. 이 경우, require를 통해 특정 모듈을 호출하는 모듈이 컴파일 되기 전에, 호출받은 모듈이 컴파일 됨을 보장한다.
    • 런타임 시점 전에 컴파일되면 성능상 이점이 있는것 아니냐고 오해할 수 있지만, elixir 코드는 어차피 런타임 전에 컴파일 되어 BEAM 바이트 코드로 전환되어 이미 최적화 된 상태라서 의미가 없다.
    • 그럼에도 require 키워드로 사전에 컴파일 하는 이유는, 매크로를 호출하는 모듈이 호출된 매크로가 컴파일 된 이후에 그 내용을 주입받아 동적으로 변경될 수 있어야 하기 때문이다 (위의 설명이랑 동어 반복..)

import

  • 일반적으로 다른 모듈의 함수나 매크로를 현재 모듈의 네임스페이스로 직접 가져오는데 사용된다.
    iex> import List
    List
    iex> last([1,2,3,4,5])
    5
    
    iex> import List, only: [duplicated: 2]
    List
    iex> duplicate(:ok, 3)
    [:ok, :ok, :ok]
  • 이렇게 import 키워드를 사용하면 List라는 모듈 자체의 네임스페이스를 입력하지 않고도, 그 모듈 내의 함수를 직접 호출 할 수 있다. (매크로도 동일하다.)
  • only 키워드로 특정 함수만 import 할 수 있다.
  • except 키워드로 특정 함수를 제외하고 import 할 수 있다.
  • require의 기능을 가지고있어서, 매크로를 불러올 때도 사용할 수 있다. 이때는 require의 기능을 하지만, 해당 매크로의 네임스페이스를 굳이 명시 하지 않아도 된다.

use

  • 복잡하다..
  • use의 동작은 require의 기능을 포함한다. use 키워드는 호출되는 모듈의 __using__/1 매크로를 호출하여, 그 모듈에서 제공하는 특정 동작이나 설정을 현재 모듈에 주입한다. 따라서, userequire의 기능을 포함한다고 할 수 있다. 여기에 더 나아가 use는 현재 모듈에 동적으로 코드를 주입할 수 있는 능력까지 가진다.
  • 예를들어 아래의 코드는..
    defmodule Example do
      use Feature, option: :value
    end
  • 아래의 코드처럼 컴파일 된다.
    defmodule Example do
      require Feature
      Feature.__using__(option: :value)
    end

  • OTP의 GenServer 구현 코드를 보며 살펴보면 좀 더 이해가 쉽다.
  • 우리는 흔히 GenServer를 사용하기 위해 아래와 같이 구현한다.
    defmodule OtpExample do
        use GenServer
    
        @me OtpExample
    
        def start_link(init_value) do
          GenServer.start_link(__MODULE__, init_value, name: @me)
        end
    
        def cast() do
          GenServer.cast(@me, :inc)
        end
    
        ##### IMPL #####
        def init(init_value) do
          {:ok, init_value}
        end
    
        def handle_cast(:inc, val) do
          IO.inspect(val)
          {:noreply, increment(val)}
        end
    
        defp increment(val) do
          :timer.sleep(1000)
          val + 1
        end
    end
  • use GenServer 구문으로 "GenServer를 사용하겠다"고 명시한다.
    • 이렇게 되면 GenServerrequire되고, __using__/1 함수가 실행된다.
    • Genserver의 __using__/1 함수는 GenServer behaviour를 사용한다고 명시되어있다.
    • 그리고 기본적인 handle_call/3, handle_cast/2, handle_info/2, terminate/2등, GenServer behaviour의 동작들을 구현해두었다.
    • 이때문에, 위의 예시 코드에서 오직 handle_cast/2 만 구현했음에도 불구하고 실행에 문제가 없었던 것이다.
    • 다시말해 use GenServer 키워드를 통해, 이 모듈이 GenSever behaviour의 구조를 따를 것이라는 선언 (@behaviour GenServer)을 대신할 수 있었고, 또 해당 behaviour의 인터페이스(?)들을 기본적으로 구현헤주는 코드들을 주입한 것이다.