プログラミングElixir第3章、第4章

第3章 不変性

  • すべての変数は 不変 (Immutable) である。
  • 関数渡しもすべて値渡し(呼び出し元の変数は変化しない)である。

第4章 Elixirの基礎

組み込みの型

Elixir Schoolの基本 を参照したほうが詳しい.

整数

10進数、16進数(0x...)、8進数(0o...)、2進数(0b...)が使える。

iex(1)> 10
10
iex(2)> 0x10
16
iex(3)> 0o10  
8
iex(4)> 0b10
2
iex(5)> 1_0
10
iex(6)> 100_000_000
100000000
iex(7)> 0x100_0000
16777216
iex(8)> 0x1000000
16777216

浮動小数点

IEEE 754の倍精度ある。少数表示、Cほど自由度の高い指数表記ではなさそう。

iex(2)> 0.2   
0.2
iex(3)> 2e-1
** (SyntaxError) iex:3: syntax error before: e

iex(3)> 2.0e-1
0.2
iex(4)> 2.e-1
** (CompileError) iex:4: invalid call 2.e()
    (stdlib) lists.erl:1354: :lists.mapfoldl/3
iex(4)> 2e-1
** (SyntaxError) iex:4: syntax error before: e

iex(4)> e-1
** (CompileError) iex:4: undefined function e/0
    (stdlib) lists.erl:1354: :lists.mapfoldl/3
iex(4)> 0.1e0
0.1
iex(5)> 0.1*1e0
** (SyntaxError) iex:5: syntax error before: e0

アトム

コロン : から始まる単一の語(アルファベット、数字、@ 記号)で、語の最後には !? の1文字付与は許される。

" で囲むと、文字列エスケープ?となるので、文字の制約はない。

純粋に文字列をタグとして使用するようで、Cでいうマクロ定数のような扱いか.

(定義不要なので スペルミスが致命的ですね)

範囲

start..end (where start, end は 整数)

正規表現

PCRE準拠の表現、Per5の正規表現相当。

~r{regexp} ~r{regexp}opts という表記ができる。{ の代わりに 任意の非アルファベット文字が使える。

iex(16)> Regex.run ~r{^(..*)/(..*)$}, "/path/to/where" 
["/path/to/where", "/path/to", "where"]
iex(17)> Regex.run ~r{^(..*)/(..*)$}r, "/path/to/where"
warning: the /r modifier in regular expressions is deprecated, please use /U instead
  (elixir) lib/regex.ex:848: Regex.translate_options/2
  (elixir) lib/regex.ex:179: Regex.compile/3
  (elixir) lib/regex.ex:207: Regex.compile!/2
  (elixir) lib/kernel.ex:4998: Kernel."MACRO-sigil_r"/3
  (elixir) src/elixir_dispatch.erl:186: :elixir_dispatch.expand_macro_fun/6

["/path/to/where", "/path", "to/where"]
iex(18)> Regex.run ~r{^(..*)/(..*)$}U, "/path/to/where"
["/path/to/where", "/path", "to/where"]

オプションに変更があった様子。

システム型

PID、ポート

プロセスIDのようなもの? ポートは...? また後で詳しく出てくるだろう。

iex(1)> self
#PID<0.105.0>

コレクション型

タプル

順序を持ったコレクション。python や Rustと同じかな。

{ m1, m2, n1, n2} { :ok, "value" } { :error, :enoent }

要素数は2~4で使うことが通例の様子。要素が増えるようなら、構造体やマップを使うとよいらしい。

よく使われるのが、関数呼び出しの結果を受け取るタプルを使う。 { 結果, 追加情報 } のスタイルで、結果は 正常終了時に :ok 異常時に :error というアトムを使うことが多いぽい。2つ目以降は 何らかの情報を返すことになるでしょう。

Cでいうポインタを渡して結果をもらってくる、ということができなく、関数の返値として受け取らなければならない。更新しあい場合は上書きする感じで。

リスト

[ 1, 2, 3 ] のような記述はすでに見てきた感じ。配列ではなく、 Lisp のリストに近いっぽい.

リストの演算も定義されている。

  • ++ は連結で、そのまま要素が連結される。
  • -- は差分で、右辺の要素を順番に検索して該当するものを削除していくようである。リスト最後まで該当しなければ何もせずに終わる。
iex(3)> [ 1, 2, 3, 4] ++ [5, 6] -- [6, 4]
[1, 2, 3, 4, 5]
iex(4)> [ 1, 2, 3, 4] ++ [5, 6] -- [3, 4]
[1, 2, 3, 4, 5, 6]
iex(5)> [ 1, 2, 3, 4] ++ [5, 6] -- [3, 2]
[1, 2, 3, 4, 5, 6]
iex(6)> [ 1, 2, 3, 4] ++ [5, 6] -- [6, 2]
[1, 2, 3, 4, 5]

リストに要素があるか、を知る in:

iex(1)> 1 in [2,3,4]
false
iex(2)> 3 in [2,3,4] 
true

キーワードリスト

キーと値のタプルのリスト。コマンドラインパラメータや関数のオプションの受け渡しに使うらしい。

この記述することが多々あるとのことで、シンタックスシュガーがある。

iex(1)> [ name: "Mike", address: "Osaka" ]
[name: "Mike", address: "Osaka"]
iex(2)> [ {:name, "Mike"}, {:address, "Osaka"} ]
[name: "Mike", address: "Osaka"]

iexでみると, タプルでの表示ではなく、キーワードリストそのもの? が出てくる。

要素が3つのタプルであれば、キーワードリスト表記にはならない様子。内部での管理はよくわからないけれど、通例では [ アトム: 値 ] と記述する。

iex(3)> [ {:name , "Mike" } ]
[name: "Mike"]
iex(4)> [ {:name , "Mike", "hoge" } ]
[{:name, "Mike", "hoge"}]

同じキーを並べることもできる...

iex(6)> [ name: "hoge", name: "huga", name: "anyone" ]
[name: "hoge", name: "huga", name: "anyone"]

マップ

キーと値のペア、のコレクション。連想配列のようなもの?

%{ key => value, key2 => value2 }

キーにアトムを使うと キーワードリストのような記述もできるようになる.

iex(1)> colors=%{ :red => 0xff0000, :green => 0x00ff00, :blue => 0x0000ff }
%{blue: 255, green: 65280, red: 16711680}
iex(2)> colors=%{ red: 0xff0000, green: 0x00ff00, blue: 0x0000ff }         
%{blue: 255, green: 65280, red: 16711680}
iex(3)> colors.red                                                         
16711680
iex(4)> colors[:red]
16711680

アトムキーワードを参照するときに . (dot)を使えるが、キーワードに存在しなければエラーになる。 [] で参照しようとすると nil が返ってくるので 処理しやすそう..

iex(5)> colors.yellow
** (KeyError) key :yellow not found in: %{blue: 255, green: 65280, red: 16711680}
iex(5)> colors[:yellow] 
nil

TODO: キーの一覧の取得方法、キーの存在確認 (nilを値として使う場合に困る?)

バイナリ

バイナリデータを扱うことができる。UTF文字列の表現に使われている。

<< 1,2,3 >>

ビット幅はデフォルトで 8bit のようであり、明示的に ::size(n) を付与することで ビット幅を指定する。

<< 1 :: size(4), 3 :: size(2), 1 :: size(2) >>

~演算子など

コーディングスタイルガイド が紹介されている。

  • キャメルケース(CamelCase):モジュール・レコード・プロトコル・ビヘイビア
  • その他:スネークケース(snake_case)

真偽値

true , false , nil が真偽値。それぞれアトムへのエイリアスらしい。厳格な比較で一致することから確認ができる(?)。

iex(1)> true === true
true
iex(2)> true === :true
true
iex(3)> false === :false
true

Cと同じように、 falsenil 以外はおおむね true として扱われ、本物の true に対して truthy と呼ぶ。

比較演算子

概説
a === b 厳格な同値性
a !== b 厳格な非同値性
a == b 同値性
a != b 非同値性
a > b 一般的な比較
a >= b ^
a < b ^
a <= b ^

整数と浮動小数の比較は 厳格に比較すると整数値であっても不一致となる。

iex(1)> 2==2.0
true
iex(2)> 2===2.0
false

ブール演算

Cの条件文のとおりに書くと、意図しない動きをする可能性もある(むしろ期待どおりか?)。

厳格な比較(この場合、aが trueかfalseを返すことを期待している)。

a or b
a and b
not a

ゆるい演算(aがtruthy...false/nil 以外であれば trueと扱われる)

a || b
a && b
!a

算術演算

+ - * / div rem

/ の結果は浮動小数となる。整数除算がしたければ div( Numerator, Denominator ) を使う。剰余は rem( Numerator, Denominator )

iex(1)> 3/2
1.5
iex(2)> div(3,2)
1
iex(3)> rem(3,2)
1
iex(4)> div(7,2)
3
iex(5)> rem(7,2)
1

with式

TODO: また使う頃に戻ってくる

プログラミングElixir第2章

第2章 パターンマッチ

Cでいうところの代入記述は、Elixirではパターンマッチになる。 = 演算子で左右のマッチングが取れれば、変数に対応する値を束縛(代入?参照?)する。

iex(1)> a=1
1
iex(2)> a
1
iex(3)> 1=a
1
iex(4)> a=2
2
iex(5)> 1=a
** (MatchError) no match of right hand side value: 2

リスト表記は [1,2,3] のように書くらしい。リストの要素がリストでもあって良い。二次元配列ではない(要素ごとに任意の型?をおける)。 [[1,2,3],[4,5]]

iex(6)> [[1,2,3],[4,5]]
[[1, 2, 3], [4, 5]]
iex(7)> [_,a]=[[1,2,3],[4,5]]
[[1, 2, 3], [4, 5]]
iex(8)> a
[4, 5]

ここで _ は、マッチングさせるために使うけれども、あとで参照しないような場合に用いる変数である。要は捨てたい場合に使うもの?(通常の変数に比べると高速になるのかもしれない)。

一つの式に現れる変数は、最初にマッチングした値とマッチする必要がある。

iex(1)> [a,a] = [1,1,1]
** (MatchError) no match of right hand side value: [1, 1, 1]
iex(1)> [a,a] = [1,1]  
[1, 1]
iex(2)> [a,a] = [1,2]
** (MatchError) no match of right hand side value: [1, 2]

最初のマッチではなく、すでに束縛した値をマッチングに使う場合は ^を変数の頭につける

iex(1)> a=1
1
iex(2)> ^a=1
1
iex(3)> ^a=2
** (MatchError) no match of right hand side value: 2

これはなぜだめなのかな...

iex(4)> 1=^a
** (CompileError) iex:4: cannot use ^a outside of match clauses

TODO: あとでしらべる

2020/06/16(火)Elixirを学ぶ

動機

さる SWEST-21 において、 Nervesハンズオンを体験した。

ここでElixir, earlangについて知るきっかけとなる。組み込み向けのシステムとして使用されており、サーバサイドのNerves Hubから末端のNerves device(raspberry Pi, Beagle Bone)までサポートされていた。

OTA(Over The Air)による自動デプロイがサポートされていたことがとても印象強く、安定した(比較的リアルタイム性を必要としない)システムを構築できそう、という感じを受けました。

なにより開発者の一人である, Justin氏が来日されていたのも大きいですね。

環境を探す

editor

IntelliJやCLionだとプラグインがある.

vim-plugin

語彙

初めて触れたとき、単語の意味がわからなかった。正確性にかけるかもしれないけれど、以下に記録しておく:

概要?
Earlang Erlangは数学者のアグナー・アーランから名前をとって命名された。Ericson社が1998年にOSS化。
OTP Elixirで Earlを使うための結合部分?
Elixir Earlを使っている新たな言語. Rubyの影響を大きく受けている?
mix ビルドシステム?
Hex パッケージ管理システム
asdf Earlang/Elixirをソースビルドする管理システム??(shell script)

公式?

入門記事

Phoenix (WEB framework)

書籍類

プログラミング Elixir(AA)

Nerves: erinit

2020/02/21

erlinit at 2020-02-14

リポジトリ

依存関係

libc

機能

initの置き換えとなる実行ファイル.

nerves_system_br(buildroot)上の busyboxベースで作られる rootfsに対して、 /sbin/initへシンボリックリンクが張られる. コマンドラインオプションでコマンド実行、マウント設定も行うことができる。

設定ファイル

/etc/erlinit.config

nerves_system_brの設定ファイルを参照するとよい。マウントオプションやerlangインタプリタ、サーチパスの設定がある。

引数の格納用に固定長配列(要素数 MAX_ARGC(=32))を使っているようで、ファイル中の設定とコマンドライン引数の総和がこれを超えられない。

FILE: src/options.c
Func: void parse_args(int argc, char *argv[])

ドキュメント(README.md)の以下の見出しにも明文化されている。迷ったらソースを読むと漏れはないだろう...

## Configuration and Command line options

主な流れ

FILE: src/erlinit.c Func: main()

    merge_config(argc, argv, &merged_argc, merged_argv);
    parse_args(merged_argc, merged_argv);

    /*
     * Mount devpts on /dev/pts, procfs on /proc, sysfs on /sys
     */
    setup_pseudo_filesystems();

    // Create symlinks for partitions on the drive containing the
    // root filesystem.
    create_rootdisk_symlinks();

    // Fix the terminal settings so output goes to the right
    // terminal and the CTRL keys work in the shell..
    set_ctty();

    fork_and_wait(&is_intentional_exit, &desired_reboot_cmd);

この中で child processを起こして、zombie processの回収を行う.

    // Set up a controlling terminal for Erlang so that
    // it's possible to get to shell management mode.
    // See http://www.busybox.net/FAQ.html#job_control

erl実行のための loopback i/f設定は netlink socketを触って設定している.. ipコマンドのたぐいではない...

FILE: src/network.c

static void configure_loopback(int ifindex)

という感じなので デフォルトで sshとかなんとかが使える状態ではない. erlinitの設定ファイルで、以下を使えば 任意のデーモンを起こしておくことはできそう. ただしネットワークインタフェースの設定は shoehornとかで行われるのかもしれないから, この時点では 外向けのinterfaceは死んでいると思ったほうがいいだろうな.

--pre-run-exec