Shiopon Labo

しおぽん(@shiopon01)の個人ブログです。ポエムと技術記事が中心

Erlang/Elixir のプロセスを5歳児でも分かるくらい丁寧に説明する記事(draft)

Erlang を始めて最初によく分からないと感じたのはあの癖の強い構文でしたが、一番良くわからなかったのは Erlang の目玉機能と言っても良い並列指向プログラミングをかじるようになった辺りでした。

突然現れた Erlang プロセス、メッセージパッシング、エラー処理、etc。この記事では、自分より後に Erlang を学び始めた人が少しでもこれら並列実行を構成する要素に惑わされないようにいくつかの要素を丁寧に説明していきたいと思います。

実行環境

まず大前提ですが、 Erlangコンパイル言語 です。

コンパイルされたソースコード中間言語.beam)に変換され、Erlang の実行環境 BEAMErlang Virtual Machine)で動作します。BEAM 自体は C 言語で作成されていて、 GitHub で公開されている ので誰でもソースコードを覗くことができます。(蛇足ですが、BEAM は Java で言うところの JVMRuby で言うところの YARV です)

さて、Erlang が注目されている点として、Erlang は並列指向プログラミングが可能な言語であるという点が挙げられます。この部分を理解しようと思うなら、BEAM がどうやって生成された中間言語を実行しているかという点を探ることが近道でしょう。

BEAM の仕事は単純にコンパイルされた中間言語を読み解いて実行することですが、Erlang の並列性を実現するため実行にはいくつかの要素を取り入れています。例えば、プロセス、メールボックス、スケジューリングなど。そして、この要素のいくつかを丁寧に説明することがこの記事の本題です。

Erlang プロセス

Erlang の並列性を実現するための要素としてまず現れるのは Erlang プロセス です。

BEAM は並列性を実現するため、Erlang を実行する仕組みに OS のプロセスと同じような仕組みを取り入れています。概念的には、BEAM という OS の上で動作する Erlang プロセスという認識で間違いはありません。Erlang プロセスは OS のプロセスのようにメモリやストレージまで完全に分離されています。(Golang の goroutine に近いものがあると感じています)

ただこの Erlang プロセス、OS のプロセスほど重くも複雑でもありません。実際の所、BEAM の中での Erlang プロセスの実体は process という名前の C の小さな構造体です。大変軽量なため、ErlangRuby で1つのオブジェクトを作るくらいの感覚でプロセスを生成することができます。そして多数生成したプロセスがプロセス間通信を行いながらプログラムが実行される部分が Erlang の醍醐味でもあります。

Erlang プロセスには次のようなデータが格納されています。

  • プロセス ID
  • ステート(状態保存用)
  • メールボックス(Queue)
  • 関数(メッセージリスナー)
  • etc

完全に分離されているので外部からステートの情報を操作することは出来ません。また、プロセス ID は分離されたプロセス同士がプロセス間通信を行うための住所のようなものです。

例えば、Erlang には erl で起動できる対話形式で実行できる Erlang エミュレーターがありますが、このエミュレーター自体も実体は BEAM で動作するプロセスの1つです。実行中プロセスの ID はビルドイン関数 erlang:self/0 で確認することができます。

1> self().
<0.79.0>

(この <0.79.0> みたいなのがプロセス ID です)

他の要素であるメールボックスやメッセージリスナーなどの機能はプロセス間通信を行うための仕組みであり、各プロセスに実装されているものです。

さて、ここまででもう Erlang が BEAM のプロセスを駆使して実行されていることと、Erlang プロセスがどういうものなのかを少しは分かってもらえたと思います。次からはプロセス同士が通信するための仕組みを見ていきましょう。

補足:ステート

プロセスには状態管理のために確保された専用ストレージ(ステート)が存在しています。 そのプロセスによってのみ変更可能で、基本的には関数とやり取りを行って状態を変化させていきます。

プロセスを生成するために BEAM はヒープ領域を確保しますが、初期のヒープ領域が小さめに設定されている理由は何十万ものプロセスを同時に実行するためだとかなんとか。

補足:メールボックス

他プロセスから自プロセスに送信されたメッセージを保存するためのキューを メールボックス と呼びます。プロセス間通信の基本は、送信側が相手のメールボックスにメッセージを push し、受信側が自分のメールボックスからメッセージを pop することにあります。

(途中)

Emacs で再起動せずに設定ファイルを反映させる

Emacs で、変更した設定ファイルを再起動せずに反映させたい時は M-x load-file を使える。

ここで .emacs.d/init.el を指定すると全設定ファイルの読み直しが行われる。

M-x load-file RET ~/.emacs.d/init.el

flex.phys.tohoku.ac.jp

定期的に忘れるのでここにメモ。

VSCodeの設定・拡張機能を環境間で共有する

追記

こんなものがあるらしい。この記事いらんやんけ!

github.com

本題

VSCodeの設定を環境間で共有するのはまあまあ面倒くさい(自分はMac/Ubuntuで設定を共有している)。この記事ではそのあたりについて言述する。

共有したい設定

VSCodeで共有しておきたい設定は次の2つ。

settings.json はOSによって保存場所が違うみたいで、例えば UbuntuLinux) だと .config の下とかに保存されているが、 Mac だともっと深いところに保存されている。

  • Windows %APPDATA%\Code\User\settings.json
  • Mac $HOME/Library/Application Support/Code/User/settings.json
  • Linux $HOME/.config/Code/User/settings.json

setting.json は bashrc のように dotfiles のリポジトリ等でファイルを管理して、上記の場所にシンボリックリンク等を張れば共有することができるので、難しくはない。

ln -s $HOME/rc/settings.json $HOME/.config/Code/User/settings.json

code.visualstudio.com

曲者なのが拡張機能で、拡張機能本体の容量がまあまあ大きいのでそのままリポジトリ等で管理するわけにはいかない。(できないこともないが、 push や clone にめっちゃ時間がかかったりする)

そのため、拡張機能拡張機能の名前のリストのみ管理することにする。これには code --list-extensions コマンドが利用でき、これでインストールされている拡張機能のリストを出力することができる。(Maccode コマンドを使うのに少し設定が必要らしい https://code.visualstudio.com/docs/setup/mac

$ code --list-extensions
bungcip.better-toml
codezombiech.gitignore
Compulim.vscode-clock
dbaeumer.vscode-eslint
...

この出力結果をファイルに保存してリポジトリとかで管理して共有する。

code --list-extensions > $HOME/rc/extensions

このファイルから1発でインストールできたらいいけど、ドキュメント見たかんじ欲しいコマンドは無さそう…。

code.visualstudio.com

出力した拡張機能リストからのダウンロードには code --install-extension を使う。ただし、ドキュメントにあるように1つづつしかダウンロードできないため、ファイルの行をループして1つづつインストールしていく。このスクリプトも extensions のファイルと一緒に管理しておこう。

while read ext; do
  code --install-extension $ext
done <$HOME/rc/extensions

一応、実行したらこんなかんじ。

$ sh install.sh 
Found 'bungcip.better-toml' in the marketplace.
Installing...
Extension 'bungcip.better-toml' v0.3.2 was successfully installed!
Found 'codezombiech.gitignore' in the marketplace.
Installing...
Extension 'codezombiech.gitignore' v0.6.0 was successfully installed!
Found 'compulim.vscode-clock' in the marketplace.
Installing...
Extension 'compulim.vscode-clock' v0.0.1 was successfully installed!
Found 'dbaeumer.vscode-eslint' in the marketplace.
Installing...
...

まとめ

環境の共有には次の手順が必要。

$HOME/rc/vscode/settings.json$HOME/rc/vscode/extensions が存在している状態なら次のスクリプトでなんとかなる。(Windowsなし)

# setting.json

if [ "$(uname)" == 'Darwin' ]; then
  SETTINGS_PATH="$HOME/Library/Application Support/Code/User/settings.json"
elif [ "$(expr substr $(uname -s) 1 5)" == 'Linux' ]; then
  SETTINGS_PATH="$HOME/.config/Code/User/settings.json"
elif [ "$(expr substr $(uname -s) 1 10)" == 'MINGW32_NT' ]; then                                                                                           
  echo "Windows is not supported."
  exit 1
else
  echo "Your platform ($(uname -a)) is not supported."
  exit 1
fi

ln -s $HOME/rc/vscode/settings.json $SETTINGS_PATH

# install extensions

while read ext; do
  code --install-extension $ext
done <$HOME/rc/vscode/extensions

echo 'done.'

Homebrew を使うなら brew bundle を使っていくべきだ

Homebrew はMacユーザーなら知らぬものはいないレベルで有名なパッケージマネージャーだ。
きのこたけのこのように MacPorts と戦わされがちなポジション。僕はたけのこ派

Homebrew でパッケージをインストールするときは通常 brew install を使うが、実は Brewfile というものを使って複数パッケージをまとめてインストールすることもできる。RubyでのGemfile、Nodeでのpackage.jsonみたいなもので、使ってない人はとりあえず使ったほうが良い。

とりあえずHomebrewのインストール。公式のやつをコピペしよう。

/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

brew.sh

この後すぐBrewfileを用意して brew bundle を行う。

自分が普段使っているBrewfileはこちら。こう見るとぜんぜん使ってないものばっかりだ。(人のこういうファイルを見るのが好きなのでみんな晒してくれ)

gist.github.com

追加で brew install したくなった時は気持ちをぐっとこらえ、このファイルに追記して brew bundle を行う。

使い方としては、このファイルをローカルに落として brew bundle を実行するだけ。App Storeのアプリを落としてくれる mas では初回の警告が出るかもしれない。また、 ## mas kakin 以下の行は課金アプリで購入していないと普通にエラーを吐くと思うので、そのまま使う場合はコメントアウトするなりしてもらいたい。

$ curl https://gist.githubusercontent.com/shiopon01/a584cc20552cf3eed3012865c0b020c5/raw/298d9ff2a48783ac64ac4f2bd5c40177213b47c3/Brewfile > Brewfile
$ brew bundle

インストール後はインストールした Alfred 3Karabiner-Elements などいくつかの常駐アプリの起動と設定、 nodenvgoenv でのプログラミング言語インストールを行う。

ショートカット

SkitchのスクショのショートカットがMacOSのものと被るので注意。自分はMacOSの方をOFFにしている。

f:id:shiopon01:20190102024539p:plain

明けましておめでとうございます。抱負など

明けましておめでとうございます!

1995年生まれ男の自分は今年は猪年の年男でもあり、本厄の年でもあります。厄年でもあるので慎重に、しかし猪年らしく 一意専心 に何事にも取り組んでいける年にできたらなと思います。

ちなみにFGO福袋は酒呑童子が出ました。はじめての★5アサシンありがとう!

f:id:shiopon01:20190101013631j:plain

新年の抱負

抱負なのであんまり公言するものでもありませんが、2019年は何かまじめなサービスをリリースすることを考えています。ユーザー○人獲得とかではなくて、単純にリリースすることが目標です。

これは実は2018年後半から考えていて、せっかく個人のサービスなら流行りのアーキテクチャとか技術をもりもり積んでいこうということで、そういうインプットをなんやかんや去年からやっています。(勉強すればするほど学ばないといけないことが増える辛さ)

具体的に自分がずっと学生時代から作りたいともんもんと考えていたアプリは次の2つ。

どちらも設計の段階で他のアプリの劣化のように思えてきて何度か挫折してきたものです。

学生の頃はOICのメディアフロンティアのためにRailsとかでこれらのプロトタイプを作成したりしましたが、こういう挫折もあって方向性を見失い、結局お蔵入りとなったり…。

どちらも大きくシェアを取っているアプリがすでに存在している分野でもあるので、今更同じ土俵にあがるのも…と気負いするところでもありますが、まあ個人アプリなんで、そのあたりは気にせず良い所はパクる精神で今年はゆるゆる作っていけたらなと思います。

他の抱負で言うと、最近練習し始めた絵描きでマシな絵を書けるようになるとか、結局学ぶだけ学んで大したものを生み出せてないAfter Effectsで映像作品を作るなど。文字だけでなく動画や音声でのアウトプットを行ってみたいという気持ちもあるので、そういうのもあります。

あ、あとIIDXで十段を取りたい!(PEN以来まともにやってないブランクもあって現在悲しみの1曲目落ち)

f:id:shiopon01:20190101021149j:plain

以下まとめです。2019年の shiopon01 の活躍にご期待ください。

  • サービスをリリース
  • After Effectsで映像作品を作る
  • マシな絵を書けるようになる
  • 映像や音声でのアウトプット手段の模索
  • IIDX十段取得
  • TOEIC 500点
  • etc(考え中)

(絵の練習はPixivのSenseiがまじで良いです)

sensei.pixiv.net

2018/1/1のステータス

  • 身長:174cm
  • 体重:77.2kg
  • TOEIC:310点くらい

今年は体重を減らすか、キープか増えるにしても筋肉で……が目標…。

Node.jsの初見殺し (x) => (y) => { ... }

引数定義する部分が2つある関数定義、まさに初見殺し。

const a = (x) => (y) => {
 console.log(x * y)
}

ただでさえ function (x) { ... }シンタックスシュガーみたいな (x) => { ... } (アロー関数)があったりするのに、アロー関数には引数定義が2つ付いた (x) => (y) => { ... } みたいな定義方法もある。調べても情報が多くないように思うし、そもそも調べ方もよく分からないかんじがある。この記事はこれの話。( this とかには触れない)

カリー化

さて、結論から言うとこれは カリー化 と呼ばれるもので、先述した関数定義は次の構文のシンタックスシュガーみたいなものである。カリー化についてはいろいろ記事があると思うので 部分適応カリー化 で検索してもらえたらと思う。

const a = function (x) {
  return function (y) {
    console.log(x * y)
  }
}

これは見たとおり、定義した a は引数 x を受け取って新しい関数を生成する関数だ。ここで生成される関数は最初に渡される x を保持しているので、 function (y) の中でも x を利用できる。

実際に 2 を渡して実行してみても、関数が生成されていることが分かる。この関数はもちろん 2 を保持している。

$ node
> const a = (x) => (y) => {
... console.log(x * y)
... }
undefined
> a (2)
[Function]

生成された関数は引数 y を受け取って console.log するだけのものだ。計算に使う x は最初の a (2) で渡したものが引き継がれている。

次の2つは呼び出し方は違うが、結果は同じものである。これは生成された関数に 3 を渡してそのまま実行するパターン。

> a (2)(3)
6

そしてこれが、関数を一旦変数にいれてから 3 を渡して実行するパターン。そのまま実行するパターンではカリー化するメリットが無いので、ふつうはこうやって使う。

> const b = a(2)
[Function]
> b (3)
6

ちなみに関数定義ではいくらでも引数の定義をアローで繋げることができて、つなげた分だけ階層を深くすることができる。好きなだけ繋げるべし。

> const c = (x) => (y) => (z) => {
... console.log (x + y + z)
... }
undefined
> c (1)(2)(3)
6

使い方

この構文を使うタイミングについて少し言述する。

カリー化とはつまり、新たな関数を生み出して処理の共通化を行うためのアプローチだ。関数を生成する際に様々な設定値などを渡すことで、最適な新たな関数を生み出すことができる。この結果、何度も呼び出すが決め打ちの引数が入る煩わしい関数のをスマートに定義することができるようになる。

ありがちだが、例えば次のプログラムは消費税を計算するものだ。ここでは calcJPTax という日本の消費税を計算する新しい関数を生成している。この関数を生成する際に渡す数値を変えて複数定義すれば、国に対応した消費税計算関数を簡単に作成することができる。

const product = (tax) => (value) => {
  return (value * tax)
}

const calcJPTax = product (1.08)

console.log(calcJPTax(100)) //=> 108

少し難しいが、カリー化する際に関数を渡すこともできる。これを使うことで好きな処理を関数に差し込むことができる。

これはコンソールに値を出力する関数で、最初に渡す関数の返り値によって内容を変更できる。

const say = (func) => (value) => {
  return console.log(func(value))
}

const sayHello = say ((name) => {
  return 'Hello, ' + name
})

sayHello('Mario') //=> 'Hello, Mario'

などなど。様々な場面で使いみちがある。

E: パッケージ ○○ を再インストールする必要がありますが、そのためのアーカイブを見つけることができませんでした。

E: パッケージ virtualbox-6.0 を再インストールする必要がありますが、そのためのアーカイブを見つけることができませんでした。

こいつに時間食われたのでシェア。

やりたいこと

こいつを消したい。

$ sudo dpkg -l | grep virtualbox-6.0
iFR virtualbox-6.0        6.0.0-127566~Ubuntu~bionic        amd64    Oracle VM VirtualBox

minikube start したらVirtualBoxでエラーが出たので、 dpkg -i virtualbox〜〜 とかやってたらますますおかしくなったパターン)

いけたやつ

もし debconf: DbDriver "config": /var/cache/debconf/config.dat is locked by another process: Resource temporarily unavailable のエラーが出たらとりあえず rm /var/cache/debconf/*.dat したらなんとかなる。

自分の場合はこれでいけた。

$ sudo rm /var/cache/debconf/*.dat 

$ sudo dpkg -P --force-remove-reinstreq virtualbox-6.0
dpkg: 警告: --force が有効なので、問題を無視します:
dpkg: 警告: パッケージが非常に矛盾した状態に陥りました。
削除を行う前にこのパッケージを再インストールすべきです
(データベースを読み込んでいます ... 現在 223431 個のファイルとディレクトリがインストールされています。)
virtualbox-6.0 (6.0.0-127566~Ubuntu~bionic) を削除しています ...
virtualbox-6.0 (6.0.0-127566~Ubuntu~bionic) の設定ファイルを削除しています ...
dpkg: 警告: virtualbox-6.0 の削除中、ディレクトリ '/usr/lib/virtualbox/sdk/bindings/xpcom/python/xpcom/server' が空でないため削除できませんでした
dpkg: 警告: virtualbox-6.0 の削除中、ディレクトリ '/usr/lib/virtualbox/sdk/bindings/xpcom/python/xpcom/client' が空でないため削除できませんでした
shared-mime-info (1.9-2) のトリガを処理しています ...
hicolor-icon-theme (0.17-2) のトリガを処理しています ...
desktop-file-utils (0.23-1ubuntu3.18.04.2) のトリガを処理しています ...
mime-support (3.60ubuntu1) のトリガを処理しています ...

一応、こうしてVirtualBoxをインストールできたのであった。

$ sudo dpkg -i virtualbox-6.0_6.0.0-127566_Ubuntu_bionic_amd64.deb

Minikubeも動いた。

$ minikube start
Starting local Kubernetes v1.10.0 cluster...
Starting VM...
Getting VM IP address...
Moving files into cluster...
Setting up certs...
Connecting to cluster...
Setting up kubeconfig...
Starting cluster components...
Kubectl is now configured to use the cluster.
Loading cached images from config file.

いけなかったやつ

$ sudo dpkg -r virtualbox-6.0
dpkg: パッケージ virtualbox-6.0 の処理中にエラーが発生しました (--remove):
 パッケージが非常に矛盾した状態に陥りました。
削除を行う前にこのパッケージを再インストールすべきです
処理中にエラーが発生しました:
 virtualbox-6.0
$ sudo dpkg -p virtualbox-6.0
dpkg-query: パッケージ 'virtualbox-6.0' はまだ利用可能でありません
アーカイブファイルを調べるためには dpkg --info (= dpkg-$ sudo dpkg -p virtualbox-6.0
dpkg-query: パッケージ 'virtualbox-6.0' はまだ利用可能でありません
アーカイブファイルを調べるためには dpkg --info (= dpkg-deb --info) を、
その内容一覧を表示するには dpkg --contents (= dpkg-deb --contents) を使います。deb --info) を、
その内容一覧を表示するには dpkg --contents (= dpkg-deb --contents) を使います。
$ sudo apt install --reinstall dpkg
パッケージリストを読み込んでいます... 完了
依存関係ツリーを作成しています                
状態情報を読み取っています... 完了
E: パッケージ virtualbox-6.0 を再インストールする必要がありますが、そのためのアーカイブを見つけることができませんでした。
$ sudo apt install -f
パッケージリストを読み込んでいます... 完了
依存関係ツリーを作成しています                
状態情報を読み取っています... 完了
E: パッケージ virtualbox-6.0 を再インストールする必要がありますが、そのためのアーカイブを見つけることができませんでした。