3 minute read

ElixirConf 2022 - Jason Axelson - Quick Iteration in Elixir - Tips from 6 Yrs of Elixir Development’ 발표를 재미있게 봤다. 팁을 가볍게 정리하고 넘기려다가 발표자의 매일 자신의 도구를 익히는 자세를 기억하고 싶어서 글로 남긴다.

Elixir interactive shell인 IEx 팁

IEx는 Elixir의 interactive shell이다. 코드를 짜고 실행해서 동작을 확인할 수 있다. 몰입되게 하는 빠른 루프를 제공한다. user switch command는 처음 알았다.

C+] auto bracket

자동으로 짝을 맞춰준다. 도움말에도 안 보이던데 이런 키는 어떻게 알았데?

recompile

전체를 재컴파일하는 함수다. 특정 모듈을 재컴파일하는 r/1 함수도 있는데 이건 귀찮아서 잘 사용을 안 하게 된다.

h/0, h/1

인자 없이 h 함수를 실행하면 전체 명령을 볼 수 있다.

iex> h

                                  IEx.Helpers

Welcome to Interactive Elixir. You are currently seeing the documentation for
the module IEx.Helpers which provides many helpers to make Elixir's shell more
joyful to work with.

This message was triggered by invoking the helper h(), usually referred to as
h/0 (since it expects 0 arguments).
...

함수나 모듈을 인자로 넘겨

iex(1)> h Enum.map

                            def map(enumerable, fun)

  @spec map(t(), (element() -> any())) :: list()
  ...

i/1

데이터 타입 정보를 출력한다.

iex> i {:ok, %{a: :b}}
Term
  {:ok, %{a: :b}}
Data type
  Tuple
Reference modules
  Tuple
Implemented protocols
  IEx.Info, Inspect

b/1

콜백 함수 정보를 출력한다.

iex> b GenServer
@callback code_change(old_vsn, state :: term(), extra :: term()) ::
            {:ok, new_state :: term()} | {:error, reason :: term()}
          when old_vsn: term() | {:down, term()}

@callback format_status(reason, pdict_and_state :: list()) :: term()
          when reason: :normal | :terminate

...

open/1

파일을 ELIXIR_EDITOR 환경변수로 정의한 에디터로 열어준다. Emacs로 열고 싶으면 아래와 같이 세팅한다.

$ ~export ELIXIR_EDITOR="emacsclient --no-wait +__LINE__ __FILE__"

IEx에서 소스 코드를 보고 싶으면 함수나 모듈 앞에 open을 붙인다.

iex> open Enum.map

~/.iex.exs

IEx 기본 설정을 ~/.iex.exs 파일에 정의한다.

IO.puts("Loaded ~/.iex.exs")

IEx.configure(inspect: [charlists: :as_lists])

Elixir 프로젝트 루트에 .iex.exs 파일을 만들고 아래 코드를 추가하면 공통 설정을 손쉽게 사용할 수 있다.

File.exists?(Path.expand("~/.iex.exs")) && import_file("~/.iex.exs")

있으면 편하다. 발표를 보고 바로 설정했다. 개인적으로 사용하는 Elixir 프로젝트 template에도 추가했다.

user switch command

IEx와 shell은 1:1 관계가 아니다. 이번에 처음 알게 됐다. shell을 관리할 수 있다.

iex(1)> Enum.each(1..10_000_000, fn _ -> :timer.sleep(1000) end)

위처럼 무한루프가 걸렸다면? C-c C-c 로 종료할 수 있다. C-g 키를 눌러 user switch command를 사용하면 IEx를 종료하지 않고 interrupt 할 수 있다.

iex(1)> Enum.each(1..10_000_000, fn _ -> :timer.sleep(1000) end)
# 여기서 C-g 입력
User switch command
 --> h
  c [nn]            - connect to job
  i [nn]            - interrupt job
  k [nn]            - kill job
  j                 - list all jobs
  s [shell]         - start local shell
  r [node [shell]]  - start remote shell
  q                 - quit erlang
  ? | h             - this message
 --> j
   1* {'Elixir.IEx',start,[[{dot_iex_path,nil},{on_eof,halt}],{elixir,start_cli,[]}]}
 --> i 1
 --> c 1
** (EXIT) interrupted

Interactive Elixir (1.16.3) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)>

이건 사실 C-c C-c 를 눌러 종료하고 다시 IEx를 시작하는 것과 별로 달라 보이지 않는다.

새로운 shell을 만들어서 넘어가고 전환하며 작업할수도 있다.

iex(1)> hello = :iex
:iex
User switch command
 --> s 'Elixir.IEx'
 --> j
   1  {'Elixir.IEx',start,[[{dot_iex_path,nil},{on_eof,halt}],{elixir,start_cli,[]}]}
   2* {'Elixir.IEx',start,[]}
 --> c 2
Interactive Elixir (1.16.3) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> hello
error: undefined variable "hello"
└─ iex:1

** (CompileError) cannot compile code (errors have been logged)

User switch command
 --> c 1

iex(3)> hello
:iex

IEx가 내부적으론 Erlang의 erl 셸을 사용하기 때문에 새로운 IEx 시작을 s 'Elixir.IEx' 로 했다.

그래서 어떻게 활용하면 좋을까? 그건 잘 모르겠다. 언젠가는 유용한 도구로 사용할 상황이 오지 않을까 기대한다.

shell history

IEx를 종료해서 입력한 명령 히스토리를 유지할 수 있다. iex --erl "-kernel shell_history enabled" 처럼 인자로 넣어도 되는데, 난 환경 변수에 세팅했다.

export ERL_AFLAGS="-kernel shell_history enabled"

의존성 라이브러리를 수정해서 테스트하려면

우선 받아놓고 버전이 아니라 path로 경로를 지정하면 된다.

defp deps do
[
-    {:ecto, "~> 3.12"}
+    {:ecto, path: "deps/ecto"}
]
end

ExSync - 코드 수정을 감지하고 자동 컴파일

ExSync 라이브러리를 의존성에 추가하면 된다.

def deps do
  [
    {:exsync, "~> 0.4", only: :dev},
  ]
end

적극적으로 활용하는 snippets

반복되는 코드 패턴을 snippet으로 등록해서 자잘한 실수를 줄이고 효율도 높인다.

발표에서는 이런 snippet들을 소개한다.

  • lin -> IO.inspect($1, label: $1)
  • pin -> |> IO.inpsect(label: "$1")
  • logi -> Logger.info("$1: #{inspect($1, pretty: true)}")
  • k -> $1: $1
  • label -> IO.inspect($1, label: "$1 (hello_pehoenix.ex:15)")

나는 일일이 다 쳤던 것 같다. 발표 보고 반성을 좀 했다. snippet? GitHub Copilot이 이런 건 다 해결해 주지 않나? 아니다. 내 의도를 추론 없이 표현하는 방법이 필요하다. 게다가 이렇게 세팅한 snippet이 Coding agent(코딩 에이전트)를 더 부스팅해준다.

도구 벼리기(sharpening tools)

  • 5-10 minutes of getting to know my tools
  • daily-ish practice
  • it’s all about continual learning
  • i keep a doc to capture ideas and annoyances in the moment
    • review them later

발표를 한 번에 만들었을 것 같지 않다. 도구 벼리기 챕터에서 설명하는 것처럼 매일 조금씩 배우고 발전시키며 남겨놓은 기록을 모으니 이런 발표자료가 만들어졌을 것이다. 배움은 증분이다. 멋지다.

마치며

몰랐던 IEx 기능과 ExSync를 배웠다. snippet 활용에 대한 자극을 받았다.

매일 조금씩 자신의 도구를 배우고 벼리는 시간은 복리 보상으로 찾아온다.