9
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?

More than 1 year has passed since last update.

Elixir で指定バージョンより後に追加された関数を抽出する

Last updated at Posted at 2023-07-19

はじめに

AtCoder で使える Elixir のバージョンが 1.10.2 から 1.15.2 に上がるようです

これで表現の幅が広がりますね

以前、 @torifukukaiou さんが「Elixir 1.12で増えた関数を調べたい」という記事を書いていたので、私も ELixir 1.10 より後に追加された関数を出してみました

もちろん、 Livebook で実装しました

セットアップ

データフレームでテーブル表示、 CSV エクスポートしたいので、 KinoExplorer をインストールします

Mix.install([
  {:kino, "~> 0.10"},
  {:kino_explorer, "~> 0.1"}
])

モジュール一覧の取得

:code.all_loaded で読み込まれたモジュールを全て取得できます

見やすいようにアルファベット順に並べます

:code.all_loaded
|> Enum.sort_by(&elem(&1, 0))

実行結果

[
  {Access, ~c"/usr/local/lib/elixir/bin/../lib/elixir/ebin/Elixir.Access.beam"},
  {Agent, ~c"/usr/local/lib/elixir/bin/../lib/elixir/ebin/Elixir.Agent.beam"},
  {Agent.Server, ~c"/usr/local/lib/elixir/bin/../lib/elixir/ebin/Elixir.Agent.Server.beam"},
  {Application, ~c"/usr/local/lib/elixir/bin/../lib/elixir/ebin/Elixir.Application.beam"},
  {ArgumentError, ~c"/usr/local/lib/elixir/bin/../lib/elixir/ebin/Elixir.ArgumentError.beam"},
  {Base, ~c"/usr/local/lib/elixir/bin/../lib/elixir/ebin/Elixir.Base.beam"},
  {CAStore,
   ~c"/home/livebook/.cache/mix/installs/elixir-1.15.2-erts-14.0.2/a6f4be97ce5869ecd072e376fb58b81a/_build/dev/lib/castore/ebin/Elixir.CAStore.beam"},
  {CAStore.MixProject, []},
  {Code, ~c"/usr/local/lib/elixir/bin/../lib/elixir/ebin/Elixir.Code.beam"},
  {Code.Formatter, ~c"/usr/local/lib/elixir/bin/../lib/elixir/ebin/Elixir.Code.Formatter.beam"},
  {Code.Identifier, ~c"/usr/local/lib/elixir/bin/../lib/elixir/ebin/Elixir.Code.Identifier.beam"},
  {Code.Normalizer, ~c"/usr/local/lib/elixir/bin/../lib/elixir/ebin/Elixir.Code.Normalizer.beam"},
  {Collectable,
   ~c"/home/livebook/.cache/mix/installs/elixir-1.15.2-erts-14.0.2/a6f4be97ce5869ecd072e376fb58b81a/_build/dev/lib/mix_install/consolidated/Elixir.Collectable.beam"},
  ...

これらには Elixir 単体で使えるモジュールだけでなく、 Livebook や Kino 、 Explorer などのモジュールも含まれているため、それらを除いて取得します

modules =
  :code.all_loaded
  |> Enum.sort_by(&elem(&1, 0))
  |> Enum.map(&elem(&1, 0))
  |> Enum.filter(fn atom ->
    atom
    |> Atom.to_string()
    |> then(&(
      String.starts_with?(&1, "Elixir.")
      && not String.starts_with?(&1, "Elixir.Kino")
      && not String.starts_with?(&1, "Elixir.Explorer")
      && not String.starts_with?(&1, "Elixir.Livebook")
      && not String.starts_with?(&1, "Elixir.Logger")
    ))
  end)

実行結果

[Access, Agent, Agent.Server, Application, ArgumentError, Base, CAStore, CAStore.MixProject,
 Calendar.ISO, Code, Code.Formatter, Code.Fragment, Code.Identifier, Code.Normalizer, Code.Typespec,
 Collectable, Collectable.BitString, Collectable.File.Stream, Collectable.HashDict,
 Collectable.HashSet, Collectable.IO.Stream, Collectable.List, Collectable.Map, Collectable.MapSet,
 Collectable.Mix.Shell, Config.Provider, Date, DateTime, DynamicSupervisor, Enum, Enumerable,
 Enumerable.Date.Range, Enumerable.Explorer.Series.Iterator, Enumerable.File.Stream,
 Enumerable.Function, Enumerable.GenEvent.Stream, Enumerable.HashDict, Enumerable.HashSet,
 Enumerable.IO.Stream, Enumerable.Kino.Control, Enumerable.Kino.Input, Enumerable.Kino.JS.Live,
 Enumerable.List, Enumerable.Map, Enumerable.MapSet, Enumerable.Range, Enumerable.Stream,
 Enumerable.Table.Mapper, Enumerable.Table.Zipper, ExUnit.Assertions, ...]

ドキュメントからの関数取得

Code.fetch_docs にモジュールを渡すことで、そのモジュールのドキュメントを取得できます

Code.fetch_docs(List)

実行結果

{:docs_v1, 2, :elixir, "text/markdown",
 %{
   "en" => "Linked lists hold zero, one, or more elements in the chosen order.\n\nLists in Elixir are specified between square brackets:\n\n..."
 }, %{},
 [
   {{:function, :ascii_printable?, 2}, 657, ["ascii_printable?(list, limit \\\\ :infinity)"],
    %{
      "en" => "Checks if `list` is a charlist made only of printable ASCII characters.\n\nTakes an optional `limit` as a second argument. `ascii_printable?/2` only\nchecks the printability of the list up to the `limit`.\n\n..."
    }, %{since: "1.6.0", defaults: 1}},
   {{:function, :delete, 2}, 133, ["delete(list, element)"],
    %{
      "en" => "Deletes the given `element` from the `list`. Returns a new list without\nthe element.\n\nIf the `element` occurs more than once in the `list`, just\nthe first occurrence is removed.\n\n## Examples\n\n..."
    }, %{}},
   {{:function, :delete_at, 2}, 846, ["delete_at(list, index)"],
    %{
      "en" => "Produces a new list by removing the value at the specified `index`.\n\nNegative indices indicate an offset from the end of the `list`.\nIf `index` is out of bounds, the original `list` is returned.\n\n## Examples\n\n..."
    }, %{}},
    ...

実行結果を見ると、タプルの最後(インデックス6)に関数の一覧が入っています

また、各関数のタプルの最後(インデックス4)に since: "1.6.0" というように、どのバージョンから追加されたのか、という情報を持っています

これを利用し、 "1.10.2" よりも後に追加された関数だけを抽出します

Code.fetch_docs(List)
|> elem(6)
|> Enum.filter(fn function ->
  function
  |> elem(4)
  |> Map.get(:since, "0.0.0")
  |> Version.compare("1.10.2")
  |> Kernel.==(:gt)
end)
|> Enum.map(fn function ->
  %{
    function: function |> elem(2) |> Enum.at(0),
    since: function |> elem(4) |> Map.get(:since)
  }
end)

実行結果

[
  %{function: "keyfind!(list, key, position)", since: "1.13.0"},
  %{function: "keysort(list, position, sorter \\\\ :asc)", since: "1.14.0"}
]

List.keyfind!List.keysort が対象として抽出されました

確かに Hexdocs でも 1.10.2 よりも後に追加されたことが確認できます

ただし、 @torifukukaiou さんの記事にあるように、 List.first/2List.last/2 のように、ドキュメントの since に書いていないが、実は後から追加された関数については抽出できません

あくまでもドキュメントを信用して抽出します

ここまでの条件を踏まえ、 1.10.2 よりも後に追加された関数の一覧を取得します

modules
|> Enum.map(fn module ->
  case Code.fetch_docs(module) do
    {:error, _} -> []
    docs ->
      docs
      |> elem(6)
      |> Enum.filter(fn function ->
        function
        |> elem(4)
        |> Map.get(:since, "0.0.0")
        |> then(fn version ->
          version_length =
            version
            |> String.split(".")
            |> length()
          cond do
            version_length >= 3 -> version
            version_length == 2 -> "#{version}.0"
            true -> "0.0.0"
          end 
        end)
        |> Version.compare("1.10.2")
        |> Kernel.==(:gt)
      end)
      |> Enum.map(fn function ->
        %{
          module: module |> Atom.to_string(),
          function: function |> elem(2) |> Enum.at(0),
          since: function |> elem(4) |> Map.get(:since)
        }
      end)
  end
end)
|> List.flatten()
|> Explorer.DataFrame.new()
|> Explorer.DataFrame.select([:module, :function, :since])

スクリーンショット 2023-07-18 19.16.14.png

いくつか、 AtCoder に影響しそうなものを挙げておきます

  • Enum.count_uintil
  • Enum.product
  • Enum.slide
  • Enum.zip_reduce
  • Enum.zip_with
  • Float.pow
  • Integer.extended_gcd
  • Integer.pow
  • **
  • binary_slice
  • ..
  • first..last//step
  • then
  • List.keyfind!
  • List.keysort
  • Range.new(first, last, step)

他にも色々ありますが、とにかく then が嬉しいのと、 first..last//stepRange.new(first, last, step) でステップが使えるようになるのは良いですね

Float.pow Integer.pow ** で累乗が分かりやすくなるのも良いです

まとめ

今までは手元で OK でもバージョンの問題で AtCoder 上でエラーになることがありました

今後はそのケースが減るのでありがたいです

早く AtCoder でも 1.15.2 で書きたい!

9
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
9
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?