本買った

 cf. 20 次にどこへ行こう – Where to go next – Elixir

さて、今年ももうすぐ終わりだ。
12月の初めから1月弱をかけて Elixir のチュートリアル(GETTING STARTED)をやってきたわけだけども、Ruby 風の文法の中に Haskell のようなパターンマッチングがあったりと結構面白い言語だと思った。特にパイプ演算子(|>)で関数を数珠つなぎにしてデータを流していくところなんか、Haskell にもなく、新鮮に感じた。

というわけで、本を買った。来年はこれをやるぞ!

[amazonjs asin=”4274219151″ locale=”JP” title=”プログラミングElixir”]

シギル

cf. 19 シギル(印) – Sigils – Elixir

シギルとは ~ +一文字から始まる文字表現によってデータを記述するものだ。Ruby の % 記法みたいなもの。

正規表現

最もよく使うのは正規表現だ。正規表現のシギルは ~r で始まる。

iex(1)> re = ~r/foo|bar/
~r/foo|bar/
iex(2)> "foo" =~ re
true
iex(3)> "baz" =~ re
false

~r の後にセパレータ(ここでは / )が続いて、セパレータに挟まれた形で正規表現(の文字列)が来る。

セパレータ

上の例ではセパレータに / を使ったけど、Elixir では全部で8つのセパレータをサポートしている。

  • //
  • ||
  • “”
  • ()
  • []
  • {}
  • <>

内容に応じて使いやすいセパレータを使えばいい。

文字列、文字リスト、単語リスト

~s シギルは文字列を生成する。

iex(4)> ~s(hello)
"hello"

~c は文字リスト。

iex(5)> ~c(hello)
'hello'

そして ~w は単語のリストを生成する。これは Ruby の %w と同じだ。

iex(6)> ~w(foo bar baz)
["foo", "bar", "baz"]

カスタムシギル

Elixir の目標の一つに拡張性がある。シギルも拡張できる。どういうことかというと、標準では提供されていないシギルを自分で定義できるってこと。
初めに例を挙げた ~r は実際には sigil_r 関数の呼び出しになっている。

iex(7)> sigil_r(<<"foo">>, '')
~r/foo/

だから sigil_ナントカ という関数を定義すればシギルを定義したことになる。例えば整数を返す ~i を定義してみよう。

iex(8)> defmodule MySigils do
...(8)>   def sigil_i(string, []), do: String.to_integer(string)
...(8)> end
{:module, MySigils,
 <<70, 79, 82, 49, 0, 0, 5, 4, 66, 69, 65, 77, 69, 120, 68, 99, 0, 0, 0, 172,
   131, 104, 2, 100, 0, 14, 101, 108, 105, 120, 105, 114, 95, 100, 111, 99, 115,
   95, 118, 49, 108, 0, 0, 0, 4, 104, 2, ...>>, {:sigil_i, 2}}
iex(9)> import MySigils
MySigils
iex(10)> ~i<42>
42

なるほど、期待した通りに動いている。

ところで sigil って、Perl のシジルと同じスペルなんだけど、どっちの発音が正しい(というか原語に近い)んだろうか。

内包表記

Elixir には Haskell や Python のようにリスト内包表記がある。

 cf. 18 内包表記 – Comprehensions – Elixir

iex(1)> for n <- [1, 2, 3, 4], do: n * n
[1, 4, 9, 16]

生成器とフィルタ

上の式で n <- [1, 2, 3, 4] が生成器だ。列挙可能なものなら何でも生成器の右の式に入れられる。

iex(2)> for n <- 1..4, do: n * n
[1, 4, 9, 16]

生成器はパターンマッチングに対応していて、パターンにマッチしない値は無視する。次の例では、キーワードリストからキーが :good の値だけを生成している。

iex(3)> values = [good: 1, good: 2, bad: 3, good: 4]
[good: 1, good: 2, bad: 3, good: 4]
iex(4)> for {:good, n} <- values, do: n * n
[1, 4, 16]

生成器に続けてフィルタを書くと、フィルタを通った値だけが残る。次の例は奇数だけがフィルタを通り、2乗を生成する。

iex(5)> require Integer
Integer
iex(6)> for n <- 1..4, Integer.is_odd(n), do: n * n
[1, 9]

Bitstring生成器

ビットストリングを生成することもできる。

iex(7)> pixels = <<213, 45, 132, 64, 76, 32, 76, 0, 0, 234, 32, 15>>
<<213, 45, 132, 64, 76, 32, 76, 0, 0, 234, 32, 15>>
iex(8)> for <<r::8, g::8, b::8 <- pixels>>, do: {r, g, b}
[{213, 45, 132}, {64, 76, 32}, {76, 0, 0}, {234, 32, 15}]

into

通常、内包表記はリストを返す。けれど :into オプションを使うことで、結果を別の型に挿入することができる。次の例は、Bitstring 生成器と :into オプションを使って、文字列から空白文字を取り除いている。

iex(9)> for <<c <- " hello world ">>, c != ?\s, into: "", do: <<c>>
"helloworld"

try, catch, rescue

 cf. 17 トライ,キャッチとレスキュー – try, catch and rescue – Elixir

エラー

エラーの例は、例えばアトムに数を足そうとするとみることができる。

iex(1)> :atom + 1
** (ArithmeticError) bad argument in arithmetic expression
    :erlang.+(:atom, 1)

意図的にランタイムエラーを起こそうというときには、raise/1 マクロを使う。

iex(1)> raise "oops"
** (RuntimeError) oops

その他のエラーは raise/2 マクロにエラーの名前とキーワード引数を渡す。

iex(1)> raise ArgumentError, message: "Invalid argument foo"
** (ArgumentError) Invalid argument foo

モジュールの中で defexception/1 マクロを使うと、自分でエラーを定義することもできる。

iex(1)> defmodule MyError do
...(1)>   defexception message: "default message"
...(1)> end
{:module, MyError,
 <<70, 79, 82, 49, 0, 0, 14, 252, 66, 69, 65, 77, 69, 120, 68, 99, 0, 0, 1, 40,
   131, 104, 2, 100, 0, 14, 101, 108, 105, 120, 105, 114, 95, 100, 111, 99, 115,
   95, 118, 49, 108, 0, 0, 0, 4, 104, 2, ...>>, :ok}
iex(2)> raise MyError
** (MyError) default message

iex(2)> raise MyError, message: "custom message"
** (MyError) custom message

try / rescue

エラー(例外)は、try / rescue 構造で拾うことができる。

iex(2)> try do
...(2)>   raise "oops"
...(2)> rescue
...(2)>   e in RuntimeError -> e
...(2)> end
%RuntimeError{message: "oops"}

上の例ではランタイムエラーを拾って、iex で表示するようにエラー自身を返している。

とはいえ、実際には try / rescue 構造を使うことはめったにないそうだ。というのも、例えば File.read/1 関数はファイルを開くとき、失敗したらエラーを起こすのではなく、「成功した」/「失敗した」という情報を含んだタプルを返すからだ。

iex(3)> File.read "hello"
{:error, :enoent}
iex(4)> File.write "hello", "world"
:ok
iex(5)> File.read "hello"
{:ok, "world"}

ファイルを開くときに起こりうる複数の結果(成功/失敗)に対応するには、単に case でパターンマッチングすればいい。

throw / catch

Elixir では後で値を捕れるように trhow / catch 構造が用意されている。たとえば、Enum モジュールで 13 の倍数となるような最初の値を見つけたいとしよう。

iex(6)> try do
...(6)>   Enum.each -50..50, fn(x) ->
...(6)>     if rem(x, 13) == 0, do: throw(x)
...(6)>   end
...(6)>   "Got nothing"
...(6)> catch
...(6)>   x -> "Got #{x}"
...(6)> end
"Got -39"

exit

Elixir ではすべてのコードは複数のプロセスの中で動いていてたがいにメッセージをやり取りしている。プロセスが死んだとき、exit という信号を送る。あるいは exit 信号を送ることで明示的に死ぬこともできる。

iex(7)> spawn_link fn -> exit(1) end
** (EXIT from #PID<0.80.0>) 1

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

exittry / catch 構造でキャッチできる。

iex(1)> try do
...(1)>   exit "I am exiting"
...(1)> catch
...(1)>   :exit, _ -> "not really"
...(1)> end
"not really"

try / catch と使うことも珍しいけど、exit をキャッチするのはもっと珍しいと書いてある。
というのも、プロセスは通常、単にプロセスの exit 信号を待つだけの監視プロセスを備えた監視ツリーにくみこまれて動作していて、監視プロセスが exit 信号を受けとると、監視対象のプロセスは再起動させられるからだ。この監視システムが、エラーの後に「早く失敗する」ことでアプリケーションを既知の初期状態へ戻すことを保証している。らしい。

after

特定の動作の後、リソースをきれいに片付けられることを保証しなければならない場合、try / after が利用できる例えばファイルを開くとき、必ず閉じることを try / after で保証できる。

iex(2)> {:ok, file} = File.open "sample", [:utf8, :write]
{:ok, #PID<0.120.0>}
iex(3)> try do
...(3)>   IO.write file, "ola"
...(3)>   raise "oops, something went wrong"
...(3)> after
...(3)>   File.close(file)
...(3)> end
** (RuntimeError) oops, something went wrong

プロトコル

 cf. 16 プロトコル – Protocols – Elixir

プロトコル

プロトコルは Elixir で多態を生み出すための仕組みだ。プロトコルの処理は、どんなデータ型であれそのデータ型がプロトコルを実装していれば処理できる。つまりこれが多態ってわけだ。
プロトコルは次のように定義する。

iex(1)> defprotocol Blank do
...(1)>   @doc "Returns true if data is considered blank/empty"
...(1)>   def blank?(data)
...(1)> end
{:module, Blank,
 <<70, 79, 82, 49, 0, 0, 18, 36, 66, 69, 65, 77, 69, 120, 68, 99, 0, 0, 1, 181,
   131, 104, 2, 100, 0, 14, 101, 108, 105, 120, 105, 114, 95, 100, 111, 99, 115,
   95, 118, 49, 108, 0, 0, 0, 4, 104, 2, ...>>, {:__protocol__, 1}}

そしてこのプロトコルをデータ型に実装する。

iex(2)> defimpl Blank, for: Integer do
...(2)>   def blank?(_), do: false
...(2)> end
{:module, Blank.Integer,
 <<70, 79, 82, 49, 0, 0, 6, 80, 66, 69, 65, 77, 69, 120, 68, 99, 0, 0, 0, 207,
   131, 104, 2, 100, 0, 14, 101, 108, 105, 120, 105, 114, 95, 100, 111, 99, 115,
   95, 118, 49, 108, 0, 0, 0, 4, 104, 2, ...>>, {:__impl__, 1}}
iex(3)> defimpl Blank, for: List do
...(3)>   def blank?([]), do: true
...(3)>   def blank?(_), do: false
...(3)> end
{:module, Blank.List,
 <<70, 79, 82, 49, 0, 0, 6, 100, 66, 69, 65, 77, 69, 120, 68, 99, 0, 0, 0, 211,
   131, 104, 2, 100, 0, 14, 101, 108, 105, 120, 105, 114, 95, 100, 111, 99, 115,
   95, 118, 49, 108, 0, 0, 0, 4, 104, 2, ...>>, {:__impl__, 1}}
iex(4)> defimpl Blank, for: Map do
...(4)>   def blank?(map), do: map_size(map) == 0
...(4)> end
{:module, Blank.Map,
 <<70, 79, 82, 49, 0, 0, 6, 140, 66, 69, 65, 77, 69, 120, 68, 99, 0, 0, 0, 207,
   131, 104, 2, 100, 0, 14, 101, 108, 105, 120, 105, 114, 95, 100, 111, 99, 115,
   95, 118, 49, 108, 0, 0, 0, 4, 104, 2, ...>>, {:__impl__, 1}}
iex(5)> defimpl Blank, for: Atom do
...(5)>   def blank?(false), do: true
...(5)>   def blank?(nil), do: true
...(5)>   def blank?(_), do: false
...(5)> end
{:module, Blank.Atom,
 <<70, 79, 82, 49, 0, 0, 6, 124, 66, 69, 65, 77, 69, 120, 68, 99, 0, 0, 0, 211,
   131, 104, 2, 100, 0, 14, 101, 108, 105, 120, 105, 114, 95, 100, 111, 99, 115,
   95, 118, 49, 108, 0, 0, 0, 4, 104, 2, ...>>, {:__impl__, 1}}

いま、IntegerListMapAtom について Blank プロトコルを実装した。必要であればほかの型にも実装する。
それじゃ、確かめてみよう。

iex(6)> Blank.blank?(0)
false
iex(7)> Blank.blank?([])
true
iex(8)> Blank.blank?([1, 2, 3])
false

文字列に対しては実装していないのでエラーになる。

iex(9)> Blank.blank?("hello")
** (Protocol.UndefinedError) protocol Blank not implemented for "hello"
    iex:1: Blank.impl_for!/1
    iex:3: Blank.blank?/1

プロトコルと構造体

構造体はマップの拡張だけど、プロトコルの実装は共有していない。確かめるために、User 構造体を定義してみる。

iex(9)> defmodule User do
...(9)>   defstruct name: "john", age: 27
...(9)> end
{:module, User,
 <<70, 79, 82, 49, 0, 0, 8, 240, 66, 69, 65, 77, 69, 120, 68, 99, 0, 0, 0, 186,
   131, 104, 2, 100, 0, 14, 101, 108, 105, 120, 105, 114, 95, 100, 111, 99, 115,
   95, 118, 49, 108, 0, 0, 0, 4, 104, 2, ...>>, %User{age: 27, name: "john"}}

そして確かめる。

iex(10)> Blank.blank?(%{})
true
iex(11)> Blank.blank?(%User{})
** (Protocol.UndefinedError) protocol Blank not implemented for %User{age: 27, name: "john"}
    iex:1: Blank.impl_for!/1
    iex:3: Blank.blank?/1

(空の)マップに対しては true が返ってきているのに対して、User ではエラーになっている。エラーを避けるためには、User 構造体に対して Blank プロトコルを実装する必要がある(とはいえ、どういうときに true にするかわからないけど。この例ではすべて false かな)。

デフォルト実装

すべての型にプロトコルを実装するのは面倒なので、デフォルトの実装を与えておく方法がある。プロトコル定義の中で@fallback_to_anytrue に設定すると可能になる。

iex(11)> Blank.blank?(%User{})
** (Protocol.UndefinedError) protocol Blank not implemented for %User{age: 27, name: "john"}
    iex:1: Blank.impl_for!/1
    iex:3: Blank.blank?/1
iex(11)> defprotocol Blank do
...(11)>   @fallback_to_any true
...(11)>   def blank?(data)
...(11)> end
warning: redefining module Blank (current version defined in memory)
  iex:11

{:module, Blank,
 <<70, 79, 82, 49, 0, 0, 18, 40, 66, 69, 65, 77, 69, 120, 68, 99, 0, 0, 1, 136,
   131, 104, 2, 100, 0, 14, 101, 108, 105, 120, 105, 114, 95, 100, 111, 99, 115,
   95, 118, 49, 108, 0, 0, 0, 4, 104, 2, ...>>, {:__protocol__, 1}}

あー、なんか Blank モジュールを再定義したんで warning が出てるな。大丈夫かな?とりあえず進めてみる。
とにかく、上のようにプロトコルを定義して、Any に対して実装すればいい。

iex(12)> defimpl Blank, for: Any do
...(12)>   def blank?(_), do: false
...(12)> end
{:module, Blank.Any,
 <<70, 79, 82, 49, 0, 0, 6, 68, 66, 69, 65, 77, 69, 120, 68, 99, 0, 0, 0, 207,
   131, 104, 2, 100, 0, 14, 101, 108, 105, 120, 105, 114, 95, 100, 111, 99, 115,
   95, 118, 49, 108, 0, 0, 0, 4, 104, 2, ...>>, {:__impl__, 1}}

これで、プロトコルを明示的に実装していないすべての型(構造体を含む)は、Blank.blank? に対して false を返すようになった。

iex(13)> Blank.blank?(%User{})
false

組み込みプロトコル

Elixir には組み込みのプロトコルがある。例えば Enumerable。これを実装しているデータ型には Enum モジュールの関数が使える。
また、String.Charsto_string/1 が様々な型に適用できるのはこのプロとるを実装しているからだ。
ほかにもあるらしいけど、とりあえずこのへんで。

文字列間のレーベンシュタイン距離を求める(3)Haskell版ふたたび

去年の12月に2つの文字列のレーベンシュタイン距離を求めるっていうのを、JavaScriptHaskell でやった。もともとは Python での実装を参考に2次元配列を使ってやってみたもので、JavaScript版はともかく Haskell版は2次元配列の更新に苦労した。
それが、今日ふと1次元配列でやってみたらどうだろうと思いついた。
つまりこうだ。(m + 1)行×(n + 1)列(m、n は比較する文字列2つの長さ)の各行をつなげた1次元配列を考えて、2次元配列の時の座標を表すタプル (i, j) で初期化しておく。0 < i, 0 < j のとき、(i, j) の値はひとつ左(i-1, j)、ひとつ上(i, j-1)、左上(i-1, j-1)から決まるから、これを1次元配列のインデックスに直すと次のようになる:

  • ひとつ左: i * (n + 1) + j – 1
  • ひとつ上: (i – 1) * (n + 1) + j
  • 左上: (i – 1) * (n + 1) + j – 1

これをコードに落としこんでやるとこうなった:

module Main where

import System.Environment (getArgs)

levenshteinDistance :: String -> String -> Int
levenshteinDistance s1 s2 = last ld
  where
    ld = map f [(x, y) | x <- [0..m], y <- [0..n]]
    m = length s1
    n = length s2
    f (0, 0) = 0
    f (i, 0) = i
    f (0, j) = j
    f (i, j) = minimum [a, b, c]
      where
        a = ld !! (i * (n + 1) + j - 1) + 1
        b = ld !! ((i - 1) * (n + 1) + j) + 1
        c = ld !! ((i - 1) * (n + 1) + j - 1) + c'
        c' = if s1 !! (i - 1) == s2 !! (j - 1) then
          0
        else
          1

main :: IO ()
main = do
  args <- getArgs
  let s1 = args !! 0
  let s2 = args !! 1
  print $ levenshteinDistance s1 s2

結果はこうだ。

takatoh@apostrophe $ runhaskell ld2.hs apple play
4
takatoh@apostrophe $ runhaskell ld2.hs perl pearl
1

OK、うまくいった。

だけど、上のコードはまだ2次元配列の意識を引きずっている。もっと単純にできるはずだ。1次元配列のインデックスを x とすると:

  • ひとつ左: x – 1
  • ひとつ上: x – (n + 1)
  • 左上: x – (n + 1) – 1

となる。これで一般部については2次元配列を気にしなくて良くなった。ただし問題がある。いちばん上の行(第0行)といちばん左の列(第0列)だ。少し考えて、x を (n + 1) で割った商と余りを使えばいいと気がついた。コードにするとこう:

module Main where

import System.Environment (getArgs)

levenshteinDistance :: String -> String -> Int
levenshteinDistance s1 s2 = last ld
  where
    ld = map f [0..((m + 1) * (n + 1) -1)]
    m = length s1
    n = length s2
    f x | x < n + 1                       = x
        | x `rem` (n + 1) == 0 = x `div` (n + 1)
        | otherwise                       = minimum [a, b, c]
    where
      a = ld !! (x - 1) + 1
      b = ld !! (x - (n + 1)) + 1
      c = ld !! (x - (n + 1) - 1) + c'
      c' = if s1 !! i == s2 !! j then
        0
      else
        1
      i = x `div` (n + 1) - 1
      j = x `rem` (n + 1) - 1

main :: IO ()
main = do
  args <- getArgs
  let s1 = args !! 0
  let s2 = args !! 1
  print $ levenshteinDistance s1 s2

1行だけだけど長くなってしまった。だけど考え方はシンプルのように思う。実行してみよう。

takatoh@apostrophe $ runhaskell ld3.hs apple play
4
takatoh@apostrophe $ runhaskell ld3.hs perl pearl
1

OK。

構造体

cf. 15 構造体 – Structs – Elixir

構造体はマップの拡張だ。初期値、コンパイル時の保証、多態をもたらす、って書いてある。多態ってのはどういう意味だ?それから初期値があるのもなんだかなぁという気がする。
まあ、いい。例を見てみよう。構造体を定義するには、モジュールの中で defstruct/1 を使う。

defmodule User do

  defstruct name: "john", age: 27

end
^o^ > iex struct.exs
Eshell V8.0  (abort with ^G)
Interactive Elixir (1.3.4) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> %User{}
%User{age: 27, name: "john"}
iex(2)> %User{name: "meg"}
%User{age: 27, name: "meg"}
iex(3)> is_map(%User{})
true

構造体は、用意しているフィールドが存在することをコンパイル時に保証する。逆に言うと、用意していないフィールドを使おうとするとエラーになる。

iex(4)> %User{oops: :field}
** (KeyError) key :oops not found in: %User{age: 27, name: "john"}
    (stdlib) :maps.update(:oops, :field, %User{age: 27, name: "john"})
             struct.exs:3: anonymous fn/2 in User.__struct__/1
    (elixir) lib/enum.ex:1623: Enum."-reduce/3-lists^foldl/2-0-"/3
             expanding struct: User.__struct__/1
             iex:4: (file)

構造体はマップの拡張なので、マップと同様のアクセスができる。

iex(4)> john = %User{}
%User{age: 27, name: "john"}
iex(5)> john.name
"john"
iex(6)> meg = %{john | name: "meg"}
%User{age: 27, name: "meg"}
iex(7)> %{meg | oops: :field}
** (KeyError) key :oops not found in: %User{age: 27, name: "meg"}
    (stdlib) :maps.update(:oops, :field, %User{age: 27, name: "meg"})
    (stdlib) erl_eval.erl:255: anonymous fn/2 in :erl_eval.expr/5
    (stdlib) lists.erl:1263: :lists.foldl/3

パターンマッチングでもよく用いられる。

iex(7)> %User{name: name} = john
%User{age: 27, name: "john"}
iex(8)> name
"john"
iex(9)> %User{} = %{}
** (MatchError) no match of right hand side value: %{}

構造体はディクショナリではないので Dict モジュールからは使えない。

iex(9)> Dict.get(%User{}, :name)
** (UndefinedFunctionError) function User.get/3 is undefined or private
    User.get(%User{age: 27, name: "john"}, :name, nil)

モジュールのアトリビュート

cf. 14 モジュールのアトリビュート – Module attributes – Elixir

Elixir では、モジュールのアトリビュートは3つの目的に使われる。

  1. モジュールは注釈をつける
  2. 定数として利用
  3. コンパイルの際にモジュールの一時的な保管場所として利用

注釈

最もよく使われるのは2つ。これだけは覚えておこう。

  • @moduledoc – モジュールのドキュメント
  • @doc – このしるしの次にある関数やマクロのドキュメント

例えば、前に作った Math モジュールにドキュメントを追加してみよう。

defmodule Math do

  @moduledoc """
Provides math-related functions.

## Example

iex> Math.sum(1, 2)
3

"""

  @doc """
Calculates the sum of two numbers.
"""
  def sum(a, b) do
    a + b
  end

end

これをコンパイルしてから iex を立ち上げると、モジュールが読み込まれ(カレントディレクトリにあるコンパイル済みファイルは自動的に読み込まれるのを思い出すこと)、ドキュメントを見ることができる。

^o^ > elixirc math.ex

^o^ > iex
Eshell V8.0  (abort with ^G)
Interactive Elixir (1.3.4) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> h Math
* Math

Provides math-related functions.

## Example

  iex> Math.sum(1, 2)
  3


iex(2)> h Math.sum
* def sum(a, b)

Calculates the sum of two numbers.

定数

Elixir ではしばしばアトリビュートを定数のように使う。

defmodule MyServer do

  @initial_state %{host: "127.0.0.1", port: 3456}

  IO.inspect @initial_state

end
^o^ > elixir myserver.exs
%{host: "127.0.0.1", port: 3456}

アトリビュートは関数の中でも読める。

defmodule MyServer do

  @my_data 14

  def first_data, do: @my_data

  @my_data 13

  def second_data, do: @my_data

end

IO.puts MyServer.first_data
IO.puts MyServer.second_data
^o^ > elixir myserver2.exs
14
13

一時的な貯蔵

なんか難しそうなので、パス。

alias, require, import

cf. 13 alias,requireとimpot – alias, require and import – Elixir

alias

alias はモジュールに別名をつける。例えば、数学に特化した特殊なリスト操作のモジュールを考えてみる。

defmodule Math do

  alias Math.List, as: List

end

この例では Math.List モジュールに List という別名をつけている。List へのアクセスはすべて Math.List へと展開される。オリジナルの List へは Elixir.List としてアクセスできる。

List.flatten              # => Math.List.flatten
ELixir.List.flatten       # => List.flatten
Elixir.Math.List.flatten  # => Math.List.flatten

alias はレキシカルスコープだ。あるモジュールの中で定義した alias はそのモジュールの中だけで有効。また、特定の関数の中だけで定義することも可能だ。

defmodule Math do

  def plus(a, b) do
    alias Math.List, as: List
    # ...
  end

  def minus(a, b) do
    # ...
  end

end

require

たとえば、Integer.is_odd/1 マクロを使おうという場合、Integer モジュールをプログラムに読み込まなければならない。それをするのが require ディレクティブだ。

iex(1)> Integer.is_oss(3)
** (UndefinedFunctionError) function Integer.is_oss/1 is undefined or private. Did you mean one of:

      * is_odd/1

    (elixir) Integer.is_oss(3)
iex(1)> require Integer
Integer
iex(2)> Integer.is_odd(3)
true

require もレキシカルスコープ。

import

ある別のモジュールの関数やマクロを何度も使いたとき、import すれば装飾名(モジュール名)をつけずに使うことができる。たとえば List モジュールの duplicate を何度も使いたいときはこうする:

iex(3)> import List, only: [duplicate: 2]
List
iex(4)> duplicate(:ok, 3)
[:ok, :ok, :ok]

この例では List モジュールから duplicate/2 だけを import している。only は省略可能だけど、つけることが推奨されている。only の代わりに except を使うこともできる。
only には :macros:functions を渡すこともできる。:macros を渡すと、モジュールのすべてのマクロを import する。
import もレキシカルスコープで、特定の関数内で import することもできる。

iex(3)> import List, only: [duplicate: 2]
List
iex(4)> duplicate(:ok, 3)
[:ok, :ok, :ok]

ImageMagickでR,G,BのRとBを入れ替える

手元に色のおかしい bmp 画像があった。どうも R,G,B が入れ替わっているらしい。
ImageMagick の convert コマンドで何とかできないかとググってみたら、↓このページを見つけた。

 cf. ImageMagickを使ったグレイスケール、反転、RGB入れ替えについて – Qiita

このページによると、いったん R,G,B それぞれの画像に分解してから、順番を入れ替えて合成すればいいようだ。詳しくは書かないけど、中間ファイルを作らなきゃいけないし、convert コマンドのオプション指定も難しい(詳しくは理解してない)。それでも試してみると、どうやら R と B を入れ替えると正常らしい色に変換できた。
とはいえ、画像ファイルは千個以上もあっていちいち手でやってられないので、スクリプト(バッチファイル)を書いた。
一つは、色のおかしい bmp ファイルを正常な png ファイルに変換するスクリプト。

@echo off
convert %1 -fX R img_R.png
convert %1 -fX G img_G.png
convert %1 -fX B img_B.png
convert ( img_B.png img_G.png img_R.png ) -set clorspace RGB -combine -set colorspace sRGB img_BGR.png
convert -flip img_BGR.png %2
del img_*.png

もう一つは、上記のスクリプトをカレントディレクトリの全 bmp ファイルに適用するスクリプト。

for %%I in (*.bmp) do convertBGR %%I %%~nI.png

これで全部正常になった。