elixir – 変数スコープとwith

さて、スコープと言うのは大事だ。

elixirはJavaScriptと同じく、関数が一つのスコープになるようだ。

前回のマップとキーワードリストでも、keyが重複してるのは{}ブレイス内だけでの判断になるため、別のブレイス内で同じkeyが使えることがわかった。

elixirはレキシカルスコープで、スコープの範囲は基本的に関数の本体部分とみなされる

ということらしい。

レキシカルスコープ

レキシカル(Lexical)ってのは語彙とか構文って意味だ。当然歴史は関係ない。

構文のスコープ。つまり構文の範囲というのはどういう意味なんだろうかと考えてみる。

JavaScriptはレキシカルスコープで、それを利用したクロージャが使用可能

という感じなので、JavaScriptでクロージャ使ってる人は感覚的に理解できてるとは思うけど、いざ『レキシカルスコープってどういう意味?』『クロージャってよく聞くけど何なの?』とか言われても、スマートに答えられないんじゃないかと。

まずAにある変数xとBにある変数xは同じ名前だけど完全に別物になる。これはわかると思う。Aという構文とBという構文が違うから、中身も別になる(東京に住んでるジェイソン山田と福岡に住んでるジェイソン山田は、どっちもジェイソン山田だけど別人だ。そういう感じ)。

そして構文Bの中には構文Cが定義されてる。そしてその中には変数yが宣言されている。

この状態で、以下を満たすものがレキシカルスコープだ。

  • 構文Cの変数yは親である構文Bからはアクセスできない
  • 構文Cから構文B(つまり親)の変数xにアクセスすることができる

これがレキシカルスコープの仕組みなんで、クロージャが『自分と親の変数にアクセスできる』という覚え方してる人は、そのままレキシカルスコープの挙動を理解してることになると思う。

JavaScriptで書いてみるとこうなる。

JavaScriptでは、スコープ内に変数が見つからない場合、一つ上のスコープから探すという、スコープチェーンという仕組みになる。

その為、B()にあるC()を実行すると、”BC”という文字列が返る。

elixirにおけるスコープ

elixirではwithとforという式があるようで、取り合えずwithをやってみようと思う。

withってJavaScriptにもあるけど、まぁ意識高い人は使ってないだろうね。親オブジェクトを省略できる構文。

しかしelixirのwithは関数やマクロの扱いになるようだ。

具体的にelixirのwithって何をするものなの?と思って調べてみると、こういうことらしい。

一時的にローカル変数が必要になり、その変数は外部に漏らしたくないときにwithを使う

よくわからんのでサンプルコードを元に、紐解いていこうと思う。

ファイルを読み込んで加工するスクリプト

さて、最初のスクリプトだ。

with-scope.exs

保存したら実行してみる。

さて、これはどういうスクリプトなんだろうか。順を追って見てみる。

  1. contentという変数に文字列”Now is the time”を束縛した
  2. lpという変数に”Group: #{gid}, User: #{uid}”という文字列を束縛した(doの内容)
  3. lpを標準出力(つまり画面)に表示
  4. 最初に束縛した変数contentを標準出力に表示

結果は、最初に束縛したcontentと同じ変数名のconetntがwith内でも使われているにも関わらず、with外では一切無視され、最終的に最初と同じcontentの中身(Now is the time)が表示されるという事。

つまり、with内のcontentは一時的に使いたいだけの変数で、withの外に漏れない。そして漏れないから最初のcontentに対して再束縛されないで済む、と。

ここで重要なのは、2の部分。この2の部分を行う前に、withの中身が実行されるようだ。

2の部分を更に詳しく見ていく。passwdファイルには以下のような行があるとする。

  1. File.open関数で、Linuxのパスワードファイルを読み込む
    • 読み込むファイルは”/etc/passwd”
    • 結果が:okアトム、読み込んだ特にハンドラがfile
  2. 変数contentに、IO.readで読み込んだファイルの中身を束縛
    • fileハンドルから:allですべてを読み込む
  3. File.close(file)でfileハンドラを閉じる
  4. 正規表現でパスワードファイルにあるtcpdumpの行を取得する
    • 何を束縛しても即座に消滅し参照も不可能な専用変数_に、マッチした全文が束縛される(つまり消滅する)
    • uidに正規表現の参照1個目が束縛される
    • gidに正規表現の参照2個目が束縛される

正規表現でマッチした最初の文字列は、マッチした全体の文字列になるので、使わないので消滅させる。そのための変数が_だ。

そして実際に必要なのはLinuxのUIDとGIDなので、これはそれぞれuid、 gid変数に束縛しておく。

その後doの中で、uid、 gidが文字列として展開され、lp変数に束縛される。

という流れだ。

withの説明はこんな感じ。

何がいいたいのかというと、withの中にある変数は、外にある変数と同じ名前でも、中身は完全に別物ってこと。

そしてwithの処理が終わったら消滅してくれるのも便利。

phpとかでいちいちtmp変数つくって代入しっぱなしで放置するより、このように自動的に消滅してくれる仕組みがあるとありがたい。

withでマッチしなかった場合

今回のスクリプトは、tcpdumpユーザの行を拾ってきた。

しかし実際には実在しないユーザ、例えばogadumpなんかを指定したらどうだろうか。

マッチエラーが出た。そりゃそうだ。

しかし、なければ無いで、それもハンドリングしておきたい。

そういうときは、=ではなく<-を使うと良いらしい。

これでマッチしない場合は無が返る。ちなみにelixirでは無はnull(ナル)ではなくnil(ニル)だ。

実際にこのnilを表示させるには、IO.putsのあとに、inspect/1を使えばOKらしい。

これでマッチしなかった場合、エラーでも無でもなく、画面にnilと表示されるようになる。

おわり

次回は無名関数をやりたい。