Tambourine作業メモ

主にスキル習得のためにやった作業のメモ。他人には基本的に無用のものです。

Elixirで遊んでみる(9)

13章はビルドツールのMixの話。最近は言語ごとにビルドツールがあるのが当たり前になった。

このタイミングで自分のMacにElixirをインストールすることにする。brew install elixirするだけ。 いろいろ依存で入るので、そこそこ時間がかかる。というかPython3.8と3.9を両方インストールしようとするのは何故なのか。 そして、Rubyはクリスマスに出たばっかりの3.0.0_1が入るw

では、さっそくP.143に従って、mix new issuesする。issuesというプロジェクト名なのは、 GitHubからIssueのリストを取ってくるアプリを作ってみようというサンプルだから。 libとconfig, testというディレクトリが作られる。

まず、コマンドライン引数の処理を作る。 規約として、プロジェクト名.CLIというモジュールを作るらしい。 ファイルとしては、lib/$project_name/cli.exになる。なるほど。

とりあえず、今は本のcli.exをそのまま写経する。写経しているときに浮かんだ理解や疑問点をメモっておく。

  • javadoc的なものをモジュールの属性として書くのは面白い。
  • モジュールのスタートポイントをrunという関数にするのは慣習?
  • parse_argsがdefpじゃないのは何でだろう。
  • OptionParseの戻り値の扱いが荒っぽくみえるけど、これで十分なのかな?

そして、作ったものをExUnitというテストフレームワークでテストする。testディレクトリ以下にcli_test.exsを作る。 こっちは.exじゃなくて.exsなのね。面白い。

  • doctestは何を意味しているんだろう?
  • importでparse_args/1だけを対象にしている。parse_argsがdefpじゃなかったのはこのため?

そして、テストが出来た時点で、リファクタリングする。caseのパターンマッチ1つ1つを関数にする。 これがわかりやすいのかどうかはわかんないけど、そういう流儀なんだね。

次に、GitHubにアクセスする部分を作る。httpクライアントが必要だ。 RubyRubyGemsやNode.jsのnpmみたいなものとして、Hexがある。 ただし、これはErlangとElixirの両方にとってのパッケージみたい。 全然シンタックスの違う言語だけど、共用できるものなのかしらん?

それはともかく、ここからhttpクライアントを探してインストールする。 著者はHTTPoisonというのを使うといっているのでそれを選ぶ。 Mix用の依存記述があるので、mix.exsのdepsのところにコピペする。 このあたりは、Maven Reposなんかと同じなので、戸惑うことはない。

mix depsで依存性チェックが行われる。実際に取得するにはmix deps.getする。

> mix deps
Could not find Hex, which is needed to build dependency :httpoison
Shall I install Hex? (if running non-interactively, use "mix local.hex --force") [Yn] Y
* creating /Users/tambara/.mix/archives/hex-0.20.6
* httpoison (Hex package)
  the dependency is not available, run "mix deps.get"

> mix deps.get
Resolving Hex dependencies...
Dependency resolution completed:
New:
  certifi 2.5.3
  hackney 1.17.0
  httpoison 1.7.0
  idna 6.1.1
  metrics 1.0.1
  mimerl 1.2.0
  parse_trans 3.3.0
  ssl_verify_fun 1.1.6
  unicode_util_compat 0.7.0
* Getting httpoison (Hex package)
* Getting hackney (Hex package)
* Getting certifi (Hex package)
* Getting idna (Hex package)
* Getting metrics (Hex package)
* Getting mimerl (Hex package)
* Getting parse_trans (Hex package)
* Getting ssl_verify_fun (Hex package)
* Getting unicode_util_compat (Hex package)

なるほど。

というわけで、これを使ってIssues.GithubIssues.fetchを実装する。また写経。

写経ができたら、今度はIExで動作を確かめる。iex -S mixでmixでの依存性を解決した上で起動される。

> iex -S mix
Erlang/OTP 23 [erts-11.1.5] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe] [dtrace]

Could not find "rebar3", which is needed to build dependency :parse_trans
I can install a local copy which is just used by Mix
Shall I install rebar3? (if running non-interactively, use "mix local.rebar --force") [Yn] Y
* creating /Users/tambara/.mix/rebar
* creating /Users/tambara/.mix/rebar3
===> Compiling parse_trans
===> Compiling mimerl
===> Compiling metrics
===> Compiling unicode_util_compat
===> Rebar3 detected a lock file from a newer version. It will be loaded in compatibility mode, but important information may be missing or lost. It is recommended to upgrade Rebar3.
===> Compiling idna
==> ssl_verify_fun
Compiling 7 files (.erl)
Generated ssl_verify_fun app
===> Compiling certifi
===> Rebar3 detected a lock file from a newer version. It will be loaded in compatibility mode, but important information may be missing or lost. It is recommended to upgrade Rebar3.
===> Compiling hackney
==> httpoison
Compiling 3 files (.ex)
Generated httpoison app
==> issues
Compiling 3 files (.ex)

== Compilation error in file lib/issues/github_issues.ex ==
** (SyntaxError) lib/issues/github_issues.ex:18:29: keyword argument must be followed by space after: status_code:
    (elixir 1.11.2) lib/kernel/parallel_compiler.ex:314: anonymous fn/4 in Kernel.ParallelCompiler.spawn_workers/7

途中でインストールを要求されているrebar3はErlangのビルドツール。素直に入れる。 おっと、コンパイルエラーになってるわ。直してリトライ。

> iex -S mix
Erlang/OTP 23 [erts-11.1.5] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe] [dtrace]

Compiling 3 files (.ex)
Generated issues app
Interactive Elixir (1.11.2) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> Issues.GithubIssues.fetch("elixir-lang", "elixir")
{:ok,
 "[{\"url\":\"https://api.github.com/repos/elixir-lang/elixir/issues/10621\",\"repository_url\":\"https://api.github.com/repos/elixir-lang/elixir\",\"labels_url\":\"https://api.github.com/repos/elixir-lang/elixir/issues/10621/labels{/name}\",\"comments_url\":\"https://api.github.com/repos/elixir-lang/elixir/issues/10621/comments\",\"events_url\":\"https://api.github.com/repos/elixir-lang/elixir/issues/10621/events\",\"html_url\":\"https://github.com/elixir-lang/elixir/pull/10621\",\"id\":777551967,\"node_id\":\"MDExOlB1bGxSZXF1ZXN0NTQ3ODE2NzU5\",\"number\":10621,\"title\":\"Update to a more descriptive typespec in Macro.Env\",\"user\":{\"login\":\"eksperimental\",\"id\":9133420,\"node_id\":\"MDQ6VXNlcjkxMzM0MjA=\",\"avatar_url\":\"https://avatars2.githubusercontent.com/u/9133420?v=4\",\"gravatar_id\":\"\",\"url\":\"https://api.github.com/users/eksperimental\",\"html_url\":\"https://github.com/eksperimental\",\"followers_url\":\"https://api.github.com/users/eksperimental/followers\",\"following_url\":\"https://api.github.com/users/eksperimental/following{/other_user}\",\"gists_url\":\"https://api.github.com/users/eksperimental/gists{/gist_id}\",\"starred_url\":\"https://api.github.com/users/eksperimental/starred{/owner}{/repo}\",\"subscriptions_url\":\"https://api.github.com/users/eksperimental/subscriptions\",\"organizations_url\":\"https://api.github.com/users/eksperimental/orgs\",\"repos_url\":\"https://api.github.com/users/eksperimental/repos\",\"events_url\":\"https://api.github.com/users/eksperimental/events{/privacy}\",\"received_events_url\":\"https://api.github.com/users/eksperimental/received_events\",\"type\":\"User\",\"site_admin\":false},\"labels\":[],\"state\":\"open\",\"locked\":false,\"assignee\":null,\"assignees\":[],\"milestone\":null,\"comments\":0,\"created_at\":\"2021-01-03T01:04:21Z\",\"updated_at\":\"2021-01-03T01:04:21Z\",\"closed_at\":null,\"author_association\":\"MEMBER\",\"active_lock_reason\":null,\"pull_request\":{\"url\":\"https://api.github.com/repos/elixir-lang/elixir/pulls/10621\",\"html_url\":\"https://github.com/elixir-lang/elixir/pull/10621\",\"diff_url\":\"https://github.com/elixir-lang/elixir/pull/10621.diff\",\"patch_url\":\"https://github.com/elixir-lang/elixir/pull/10621.patch\"},\"body\":\"\",\"performed_via_github_app\":null},{\"url\":\"https://api.github.com/repos/elixir-lang/elixir/issues/10620\",\"repository_url\":\"https://api.github.com/repos/elixir-lang/elixir\",\"labels_url\":\"https://api.github.com/repos/elixir-lang/elixir/issues/10620/labels{/name}\",\"comments_url\":\"https://api.github.com/repos/elixir-lang/elixir/issues/10620/comments\",\"events_url\":\"https://api.github.com/repos/elixir-lang/elixir/issues/10620/events\",\"html_url\":\"https://github.com/elixir-lang/elixir/pull/10620\",\"id\":777551630,\"node_id\":\"MDExOlB1bGxSZXF1ZXN0NTQ3ODE2NDk2\",\"number\":10620,\"title\":\"Fix cryptic error when defimpl is defined without :for option\",\"user\":{\"login\":\"eksperimental\",\"id\":9133420,\"node_id\":\"MDQ6VXNlcjkxMzM0MjA=\",\"avatar_url\":\"https://avatars2.githubusercontent.com/u/9133420?v=4\",\"gravatar_id\":\"\",\"url\":\"https://api.github.com/users/eksperimental\",\"html_url\":\"https://github.com/eksperimental\",\"followers_url\":\"https://api.github.com/users/eksperimental/followers\",\"following_url\":\"https://api.github.com/users/eksperimental/following{/other_user}\",\"gists_url\":\"https://api.github.com/users/eksperimental/gists{/gist_id}\",\"starred_url\":\"https://api.github.com/users/eksperimental/starred{/owner}{/repo}\",\"subscriptions_url\":\"https://api.github.com/users/eksperimental/subscriptions\",\"organizations_url\":\"https://api.github.com/users/eksperimental/orgs\",\"repos_url\":\"https://api.github.com/users/eksperimental/repos\",\"events_url\":\"https://api.github.com/users/eksperimental/events{/privacy}\",\"received_events_url\":\"https://api.github.com/users/eksperimental/received_events\",\"type\":\"User\",\"site_admin\":false},\"labels\":[],\"state\":\"open\",\"locked\":false,\"assignee\":null,\"assignees\":[],\"milestone\":null,\"comments\":0,\"created_at\":\"2021-01-03T01:01:34Z\",\"updated_at\":\"2021-01-03T01:01:34Z\",\"closed_at\":null,\"author_association\":\"MEMBER\",\"active_lock_reason\":null,\"pull_request\":{\"url\":\"https://api.github.com/repos/elixir-lang/elixir/pulls/10620\",\"html_url\":\"https://github.com/elixir-la" <> ...}

なんか動いているっぽい。今回はここまで。