LoginSignup
14
7

More than 5 years have passed since last update.

趣き重視でElixir Tips

Posted at

Elixir Short Tips

(ほぼ)Elixirだけを書いてもうすぐ1年。いくつか短いTipsが浮かんでいるのでまとめる。
自分で書きつつ、コードレビューしつつ、されつつ、今Elixirを書くときに考えるようになった色々なこと。

しかしただ書いてもなんだか味気ないので、「趣き重視」で書く。

「あなたはtupleを比較する」

  • 解説

    • あまりにも基礎的で、ともすれば見過ごしてしまう明快な事実。
    {2016, 12, 31} < {2017, 1, 1}
    # true
    
    • :calendar.date/0はだから美しい。:calendar.time/0もまた美しい。

      • 一方で、このようなデータ構造を我々に作らせようと誘惑する魔とは戦うべきだろう。
      #XXX Do we REALLY need this?
      %{year: 2016, month: 12, day: 31}
      
      • コード上のkey順序が意味をなさないmapでは、比較の際にどうなるか、言うまでもない。
      • これだけを見れば相当に愚かな例だが、map、そしてstructに手をかける前、一呼吸入れて損はない。
    • Erlangでは、「全ては比較できる」。同質なモノ同士であれば、例えばここでの同サイズのtupleのように、その振る舞いは比較的自然に定義されている。

      • さらに言えば、異質なモノ同士を比較しようとしたとき、どのように振る舞うかでさえ明確に定義されている

Enumを見よ。Enumに求めよ」

  • 解説
    • Enumに無いものは無い。などということは無い。
    • だがEnum.map/2がある。Enum.map/2のことを考えない日など有るだろうか?
    • そしてEnum.group_by/3があり、Enum.map_join/3があり、Enum.map_reduce/3があり、Enum.partition/2があり、Enum.filter_map/3がある。
    • 数え上げうるものに対して作用したいと思ったとき、Enumが応えてくれないことは少ない。
      • Enumが応えてくれないときは、むしろ己を疑ってもいいとさえ思う。
    • リンクした記事でも書いたように、Enum.reduce/3は表現力の高い「ビルドブロック」的関数である。その分、実装に使うと読みづらくなりがちだ。
      • Enumが用意している関数の中に用途を満たすものがないか、まず探してからreduce/3を使わなければならないかどうか考えるべきといえる。

「母なるMix」

  • 解説

    • Mix.env/0Mix.Project.config/0など、Mixから情報を引き出すことはできる。だがそれができるのはrelease前だけだ
      • 今少し正確に言うと、Mixアプリケーションが起動しているときだけだ。
      • mix testコマンドでテストしたり(MIX_ENV=test)、iex -S mix(MIX_ENV=dev)したりするとき、暗黙にMixアプリケーションも起動していて、mix.exsファイルの中に書かれたConfig情報などは状態として保持している。
        • Mix.env == :sys.get_state(Mix.State) |> Map.get(:env)
      • 当然だ。Mix自身がそれを使ってユーザのアプリケーションを世話してくれるのだから。
    • しかし、アプリケーションをreleaseの形でまとめた場合は話が変わる。そこには通常、Mix自身は含まれない。
    • ユーザのアプリケーションは、runtimeに本当に必要となるアプリケーションだけを従えて、独り立ちする。Mixは送り出すだけだ。母のように。
    • Mixを含まない起動命令に基づけば、以下は実行時エラーになる。
    defmodule M do
      def endpoint_url do
        case Mix.env do
          :prod -> "https://public.endpoint.com"
          _     -> "http://localhost:8080"
        end
      end
    end
    
    • コンパイル時に使えば良い。母はそこにいる。
    defmodule M do
      @endpoint_url (case Mix.env do
        :prod -> "https://public.endpoint.com"
        _     -> "http://localhost:8080"
      end)
    
      def endpoint_url: do: @endpoint_url
    end
    

Keyword.tの美、mapの罪」

  • 解説

    SomeModule.some_fun(prop1: 1, prop2: nil, prop3: "Three")
    
    • これはKeyword.t1、すなわち[{atom, any}]を関数に渡すときにのみ許された構文だ。
    • Keyword.tはあくまでlistの特殊型に過ぎず、当然listを扱うのと同じくせねばならない。
    • しかしmapを知ってしまった我々は、時に過ちを犯す。
    defmodule M do
      def list_fun(prop1: p1, prop2: p2) do
        {p1, p2}
      end
    
      def map_fun(%{prop1: p1, prop2: p2}) do
        {p1, p2}
      end
    end
    
    M.list_fun(prop1: 1, prop2: 2)
    # {1, 2}
    
    M.list_fun(prop2: 2, prop1: 1)
    # ** (FunctionClauseError) no function clause matching in M.list_fun/1
    
    M.list_fun(prop1: 1, prop2: 2, prop3: 3)
    # ** (FunctionClauseError) no function clause matching in M.list_fun/1
    
    M.map_fun(%{prop2: 2, prop1: 1})
    # {1, 2}
    
    M.map_fun(%{prop1: 1, prop2: 2, prop3: 3})
    # {1, 2}
    
    • 実際にここまで愚かなコードを書くことは流石にないだろう。
    • だが、mapの柔軟さに慣らされた者が荒々しく近づけば、美しいKeyword.tは折れてしまうことを知っておかねばならない。
      • ライブラリ関数の末尾引数としてoptionsのようなKeyword.tを取ることはままある。省略記法を綺麗に適用できるため、誘惑に駆られる。
      • しかし、翻って関数実装側はKeyword.tに対して鮮やかにパターンマッチすることはできない。Keyword.get/3の連発になるだろう。2

結び

深夜だなあ。
明日は@ikeyasuさん。


  1. Erlangのproplistsとは、「関数のオプション指定」といった頻出用途で似ているものの、別物である点には注意。 

  2. すぐに思いついた例だと、Regex.split/3のように。一旦変換してデフォルト値の入ったmapMap.merge/2してからパターンマッチするということも可能ではある。美しいかどうかはわからない。 

14
7
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
14
7