4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Elixir: charlist でのパス解析

Last updated at Posted at 2025-12-01

はじめに

Elixir でパス文字列を扱う際は、Path モジュールや String.split/2 を使うのが一般的です。

ある日、Elixir 本体の Mix の内部状態を扱う Mix.State モジュールのコードを読んでいると、charlist(文字リスト)+パターンマッチ だけでパスを解析している箇所がありました。

この記事では、Mix.State に出てくるパターンを参考にしつつ、

  • ../elixir/ebin:elixir
  • ../ssl-9.6/ebin:ssl

のような変換を、charlist とパターンマッチだけで行う例をメモしておきます。

Mix.State に出てくるパス解析

Mix.State の中には、コードパスからアプリ名を推測する関数があります。

この関数では、path を charlist とみなして

  • 末尾の "ebin" ディレクトリを削る
  • 直前のディレクトリ名からバージョン番号を除いたアプリ名を取り出す

という処理を、パターンマッチだけで実装しています。

charlist で末尾の ebin を落としてアプリ名を取り出す

今回やりたいのは、次のようなパスからアプリ名の atom を取り出す処理です:

  • "../elixir/ebin":elixir
  • "../ssl-9.6/ebin":ssl
defmodule SampleApp.PathUtils do
  @moduledoc false

  def app_from_path(path) when is_list(path) do
    case path |> Enum.reverse() |> discard_ebin() |> collect_dir([]) do
      []    -> nil
      chars -> List.to_atom(chars)
    end
  end

  # 末尾の "ebin/" または "ebin\\" を削除する
  defp discard_ebin(~c"nibe/" ++ rest),  do: rest
  defp discard_ebin(~c"nibe\\" ++ rest), do: rest
  defp discard_ebin(_), do: []

  # ディレクトリ名部分からアプリ名を取り出す
  defp collect_dir([?/  | _], acc), do: acc # `/` が来たら終了
  defp collect_dir([?\\ | _], acc), do: acc # `\` が来たら終了(Windows 用)
  defp collect_dir([?-  | rest], _acc), do: collect_dir(rest, []) # `-` 以降はバージョン番号として無視
  defp collect_dir([ch  | rest], acc),  do: collect_dir(rest, [ch | acc])
  defp collect_dir([], acc), do: acc
end

試してみると、次のような動きになります。

IEx
iex> SampleApp.PathUtils.app_from_path(~c"../elixir/ebin")
:elixir

iex> SampleApp.PathUtils.app_from_path(~c"../ssl-9.6/ebin")
:ssl

どう動いているか

app_from_path/1 は、charlist 形式のパス(たとえば ~c"../ssl-9.6/ebin")から、アプリ名(:ssl)を抽出する関数です。

  1. パスを逆順にする
    • 最後が "ebin/" であることを想定し、それを削るには逆順が都合良いため。
  2. discard_ebin/1"ebin/" 部分を除去
    • 'nibe/''nibe\\' で終わっていれば、それを切り落とす。
  3. collect_dir/2 でディレクトリ名を収集
    • / または \ にぶつかるまで文字を集める。
    • -(ハイフン)で区切られたバージョン番号部分(例: "9.6")は無視。
  4. 集めた charlist を List.to_atom/1 で atom に変換
    • これで :ssl:elixir を得られる。

バイナリで書くとどうなるか

もちろんバイナリ文字列でも同様の処理を書けます。

IEx
iex> "../elixir/ebin" |> Path.basename() |> String.split("-") |> hd() |> String.to_atom()
:elixir

iex> "../ssl-9.6/ebin" |> Path.basename() |> String.split("-") |> hd() |> String.to_atom()
:ssl

こちらの方がなんか直感的な氣がします。

今回紹介した charlist 版は、もともと Erlang 由来の charlist がある場合以外は使うことがなさそうですね。:sweat_smile:

おわりに

普段のアプリコードでは Path.basename/1String.split/2 を使うことが多いと思いますが、Mix.State のようにcharlist をパターンマッチだけで字句処理を書くというスタイルも、知っておいて損はなさそうです。

4
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?