simotin13's message

simotin13といいます。記事の内容でご質問やご意見がありましたらお気軽にコメントしてください\^o^/

Elixir入門 ~3日目 ずいぶんとダサいコードをElixirでダサくないコードにしてみる~

昨日はファイルに書いたElixirのコードをコンパイルして.BEAMファイルが出力されることまでは確認した。

コンパイルするとバイトコードのこととかデコンパイルできるんだろうかとかいろいろ気になってくる。

たぶん長くなるので、今日は少し観点を変えて関数プログラミングのコードを試してみることにする。

男性プログラマ必見!今どきのダサいコードはコレだ!


巷の女子高生の間では以下のようなコードはダサいとされているらしい。

#include <stdio.h>

int main(int argc, char **argv)
{
  int s = 0;
  for (int i = 0; i < 10; i++)
  {
        s = s + i;
  }
  printf("%d\n",s);
}


これは噂だが、こんなコードを書いていると


「ずいぶんとダサいコードを書いているのね。あなたの偏差値でも計算してみたの?

と女子高生に話しかけることがあるらしい。


毎日こんなコードしか書いていないのに、ここ32年くらい女子高生から話しかけられたことがないのでにわかに信じられないが。

仮に話しかけられた場合、

「いや、これは0から9までの合計を求めるコードでして・・・それに、僕の偏差値は片手で数えられるから計算とかいらなくて、それより、あの、よかったら・!?」

と必死で説明している間に女子高生は去っていくだろう。

ナウでヤングなださくないコードはこれだ!

ダサくないコードが書ければ、女子高生も一目おいてくれて、もしかしたら一緒にあんなことやこんなことができるようになるかもしれない。

ダサくないコードは、C言語では簡単に書けなくてJavascriptだと

var plus = function(a,b)
{
    return a + b;
}
var s = [0,1,2,3,4,5,6,7,8,9]
    .reduce(plus);
console.log(s);

のようなコードになるらしい。

Chromeを立ち上げてF12を押して、Consoleタブでコードを書けば完成する。

そんなことより、僕にとってはJSなんかどうでもよくてJKの方が重要だ。

Elixirで書くダサくないコード

ダサくないコードが書ければきっといいことが待っていると信じて、Elixirでのダサくないコードの書き方を勉強してみた。

もし女子高生に話しかけられた場合は、こんな感じで見せるつもりだ。

「ちょっと待ちなお嬢さん、ずいぶんと長いコードを書いているんだね。それとも長いのが好みかい?」

そしてコードを見せる。

IO.puts(Enum.reduce([0,1,2,3,4,5,6,7,8,9], 0, fn(a,b) -> a + b end)) 


「えっ!?どうして」

「君もJavascriptが好きならこう書くべきだよ。」

console.log([0,1,2,3,4,5,6,7,8,9].reduce(function(a,b){return a+b;}));

「やだ、すごく短いわ・・・こんなの初めて!あら、なんかわたしコードに酔っちゃったみたい」

「そうか、すぐそこにホテルがあるようだ。すこし休憩した方がいいだろう (⌒~⌒)ニンマリ」

関数定義

Elixirによる関数定義は、

fn(引数) ->
  処理
end

で無名関数が定義できるらしい。
ちなみにElixirのソースコードのインデントはrubyと同じで2カラムが普通らしい。

fnは定義した関数そのものを返すので

hoge = fn(a, b) ->
  a + b
end
hoge.(1,2)

とすれば定義した関数が呼び出せる。
無名関数呼び出しは

関数名.(引数)

のように . (ドット)がいるみたいで、名前付き関数というものだとどっとはいらないらしい。
あまり見慣れない構文だが、まだ勉強していないからよくわからないなにか理由があるんだろう。

今日はこれくらいにして終わる。

余談

女子高生のくだりは言うまでもなく「関数型プログラミングに目覚めた」へのオマージュです。
ヒロインの美人女子高生が関数型プログラミングについて親切に説明してくれて、自分がこの娘と二人っきりで部室でコードを書いてるような錯覚を味わうことができます。


ちなみにこの本の表紙には、本のヒロイン役の女子高生とおぼしきかわいい女の子が短いスカートとニーハイを履いている絵が描いてあります。

下からみればスカートの中が見えるかなと思って何度も角度を変えて見てみましたが、残念ながら見えませんでした。


f:id:simotin13:20170924003426j:plain


見えないようにスカートに特別な仕掛けでもしてあるんだろうか・・・

いつかこんな女の子と出会える日が来ることを信じてとりあえずElixirの勉強を頑張ろう。

Linux C言語でミリ秒の計算をする

Linux系OSのC言語ではtimeval構造体を使った時間表現があります。

しかしながら、あくまでgettimeofdayによる時間の取得・参照を目的としているからなのか、時間計算のAPIはありません。

ms秒単位で経過時間の加算をする関数が必要になったので書いてみたのですが、テストのために書いた検証コードを動かしてみたら、結果が面白かったので記事として挙げておきたいと思います。

ミリ秒加算するコード

#include <stdio.h>
#include <unistd.h>
#include <sys/time.h>

void add_ms(struct timeval *t, long ms) {
	long add_us = ms * 1000;
	long tmp_us = t->tv_usec + add_us;
	if (1000000 <= tmp_us) {
		t->tv_sec++;
		t->tv_usec = tmp_us - 1000000;
	}
	else {
		t->tv_usec += add_us;
	}
}

int main(int argc, char **argv) {
	struct timeval calc, now;
	gettimeofday(&now, NULL);

	calc = now;

	printf("------------------------------------------\n");
	printf("Start Test\n");
	printf("now  [%ld-%ld]\n", now.tv_sec, now.tv_usec);
	printf("calc [%ld-%ld]\n", calc.tv_sec, calc.tv_usec);
	printf("------------------------------------------\n");
	while(1) {
		usleep(1000 * 1);
		add_ms(&calc, 1);
		gettimeofday(&now, NULL);
		printf("------------------------------------------\n");
		printf("now  [%ld-%ld]\n", now.tv_sec, now.tv_usec);
		printf("calc [%ld-%ld]\n", calc.tv_sec, calc.tv_usec);
		printf("------------------------------------------\n");
	}
	return 0;
}

汎用OSの限界

加算処理が正しく動くか確認するため、現在時刻と書いてみた関数(add_ms)で計算した時刻の2つを縦に並べて表示しました。

usleep関数はマイクロ秒単位でのスリープ処理になります。

私の手元の環境では、上記の1ms単位での確認では徐々に計算した時刻の方が遅れていきました。
100msとか1secだとほぼ同じくらいに進んでいきますが、だいたい数十ミリ秒程度はcalcの方が遅れていました。

やはり、タイムシェアリングで動作する汎用OSだと、当然ですが精度的には限界があるようです。

usleepの精度

試しにusleepとgettimeofdayを繰り返すコードに修正して試してみましたが、

usleep(10);

としても大体100usの誤差が出ていました。

usleep関数はマイクロ秒オーダーのスリープ関数ですが、名前を信じて

usleep(1);

とかしてその通りに動くことは期待してはいけません

名前負けしてる関数なんです。

試した環境はCentOSとWindows10上のcygwinを使いましたが汎用OSで他のプロセスやカーネルの処理、ディスパッチにミリ秒かからないのであれば、むしろ頑張っているなぁという気もします。

感想

ということで実際に検証してみて、書いてみた関数の挙動的には問題はなさそうですが、こういった時間計算の関数が標準ライブラリに含まれない理由がなんとなく分かった気がしました。

日付や時間の分野は、扱うのが簡単見えてすごく難しいですね。

それにしてもusleepの実装が気になるところではあります。

後で見てみよう・・・

Elixir入門 ~2日目 リストとタプルを試す~

昨日はインタラクティブな環境iexを立ち上げてみたり、ファイルに書いたElixirのHello Worldを動かした。
まだ大してElixirの魅力とか特徴がつかめていない。

今日は、適当にデータ型をいくつか触ってみたり、昨日は触らなかったコンパイルを試してみる。

データ型

iexで少し遊んでみる。
iexの終了はCtrl-Cでabortを選べばいいみたい。

リスト

リストには何でも入る

[1, "abc", 3.14, false]

リストは内部的には、連結リストになっている。lispのconsセルと同じだ。
なので先頭に要素追加は速く、末尾への追加は遅いということらしい。

[0] ++ [1, 2, 3]

は早いけど、

[1, 2, 3] ++ [4] 

は遅い。

以下のような、指定したインデックスのオブジェクトも遅くなるそうだ。

 Enum.at(["a", "b", "c", "d"], 1)

みたいな書き方をする。

タプル

リストは連結リスト構造なのに対してタプルは配列という理解でよさそう。

tp = {1,2,3}

配列なので

tp[0]

でインデックスアクセスができそうだがそれはできない。
一般的な言語のインデックスアクセスに相当するのは

elem(tp,0)

のようなコードになる。

何をするにも関数を使う必要がある。関数プログラミングらしさを感じてきた。

コンパイル

Elixirのコードのコンパイル

$ elixirc ファイル名

コンパイルできる。

コンパイルすると、Elixir.ファイル名.BEAM

というファイルができる。


疲れたので続きは、また明日以降・・・

Elixir入門 ~1日目 導入~

コンパイラの授業でお世話になった石浦先生に教えて頂いたelixirを勉強してみることにしました。
(コンパイラの授業を受けた話はまだ書けていないのでそのうち書きたいと思います)

ちなみに石浦先生の研究室ではerlangFPGAの高位合成をする研究をしているそうです。
普段の記事は「です・ます調」で書いていますが、elixirの勉強の記事は個人的な記録としての意味合いが強いので、この勉強の記録は独白・口語体で書くことにします。
あと学習の過程を書いているのでその時点で間違ったことを書いている可能性はあります。
理解の度合いや指摘があれば直すと思いますが、もしここに書いている内容を参考にされる方は自己責任でお願いします。

はじまり

とりあえずがっつりelixirの勉強だけはできないだろうから毎日数行のコードを書いて行くことにする。
こういうのは本当に少しでもいいから毎日やった方がいいかもしれない。
コンパイラの勉強とかLinuxカーネルの勉強とか、やりたいことはいろいろあるし仕事関係の勉強もあるので時間はどうせたくさんは取れない。

なぜerlangにしなかったのか。

Elixirの文法がrubyに近いという噂を聞いたから。コツがつかめらたらerlangとかBEAMのレイヤも勉強したい。

参考書籍

プログラミングElixir

プログラミングElixir

サンプルプログラムのダウンロードもできる。

導入

elixir-lang.org

でインストール方法を調べる。

windows環境

とりあえずwindows用にインストーラを走らせてみた。
何も考えず「次へ」を押したので何が入ったとかよく分かっていない。

インストールが終わるとErlangがインストールされていた。elixirはErlang用のBEAMという仮想マシン上で動くらしいので、まぁそういうことなんだろう。
cygwinのコンソールからiexしてみても動かなかった。

後でパスを通そう。

Linux環境

VMLinux Mint上で、

$ sudo apt-get install elixir

してみた。

iexでインタラクティブなシェルが立ち上がったので多分大丈夫だろう。

Hello World

拡張子

elixirの拡張子は

.ex もしくは .exs にするのが習慣らしい。

.exはコンパイル済みのファイルに付け、 .exs はスクリプトに書くらしい。

感覚としては、Elixirは中間言語による仮想マシン方式とのことなので
Javaの .class と .java みたいな感じだろうか。
Javaと違ってコンパイルしなくても動かせる。LispHaskellと同じ。

IO.puts "Hello, World!"

スクリプト実行

コンパイルなしの実行はRubyPython等と同じで

$elixir ファイル名

とすればよい。

上記のコードをhello.exs に保存して動かす。

$ elixir hello.exs
Hello, World!

ちゃんと動いた。

iexでの実行

c ヘルパーという機能でiexからも実行できる

c "hello.exs"
Hello, World!
[]

評価された後[]が出ている。パッと見は空の配列に見えるけど、全てはリストで表現されているんだろうか。


コンパイルについてはなんか長くなりそうなので今日はこれまでにしておく。

CMakeでARM用にクロスコンパイルする

最近、オープンソースなどで配布されているソフトのビルドシステムがCMakeになっているのをよく見かけるようになりました。

CMakeはこれまでも何回か使ったことがありましたが、クロスコンパイルをしたいときの使い方を忘れてしまっていて、思い出すのに時間がかかったので備忘録として書いておきたいと思います。

CMakeでHello World

ロスコンパイルの前にC言語Hello WorldプログラムをCMakeを使って(セルフコンパイル)ビルドしてみます。

ビルドするコード

#include <stdio.h>

int main(int argc, char **argv)
{
	printf("hello world\n");
	return 0;
}

CMakeLists.txt を書く

CMakeLists.txtを最小限の構成で手っ取り早く書くとこんな感じです。

cmake_minimum_required(VERSION 2.8)
add_executable(helloworld main.c)

cmake_minimum_required は文字通りcmakeのバージョンです。
add_executable でターゲット名とソースファイルを指定します。

ビルド

cmakeの便利な点として、ソースファイルのディレクトリとオブジェクトの出力ディレクトリを分けてビルドできる点があります。
CMakeLists.txt があるディレクトリで、

$ mkdir build
$ cd build
$ cmake ..

とすると、作成したbuildディレクトリ内にMakefileなどビルドに必要なファイルが生成されます。

実行例はこんな感じです。

-- The C compiler identification is GNU 5.4.0
-- The CXX compiler identification is GNU 5.4.0
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done

$ ls
CMakeCache.txt  CMakeFiles  Makefile  cmake_install.cmake

あとはこのbuildディレクトリでmakeコマンドを実行すればビルドできます。
私の環境だとコンパイラGCCが使われたようです。

$ make
Scanning dependencies of target helloworld
[ 50%] Building C object CMakeFiles/helloworld.dir/main.c.o
[100%] Linking C executable helloworld
[100%] Built target helloworld

$ ./helloworld 
hello world

ロスコンパイルを試す

これでCMakeによるセルフコンパイルの準備はできました。
ロスコンパイルを行う場合は、更にツールチェインファイルというファイルを作成する必要があります。
といっても大げさなものではなくて、文字通りクロスコンパイル時に使用するコンパイラの指定をするだけです。

今回はRaspberrypi でも実行できるので、クロスコンパイラとして arm-linux-gnueabihf-gcc を使ってみました。
私が試したビルド環境は64bit版の Linux mint です。

$ uname -a
Linux mint 4.4.0-21-generic #37-Ubuntu SMP Mon Apr 18 18:33:37 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux

Ubuntu系のOSであればインストールは、

$sudo apt install g++-arm-linux-gnueabihf

でインストールできると思います。

ツールチェインファイルの書き方

さて、本題のツールチェインファイルは以下のようになります。

ファイル名を arm-toolchain.cmake として、以下のような記述をします。

SET(CMAKE_SYSTEM_NAME Linux)
SET(CMAKE_C_COMPILER /usr/bin/arm-linux-gnueabihf-gcc)

ツールチェインファイルを書いたら、後はセルフコンパイルの時と同様にビルド用のディレクトリを作ってcmakeコマンドを実行します。

$ mkdir cross-build
$ cd cross-build
$ cmake .. -DCMAKE_TOOLCHAIN_FILE=../arm-toolchain.cmake

セルフコンパイルの時との違いとして、

  • DCMAKE_TOOLCHAIN_FILE=../arm-toolchain.cmake

のようにツールチェインファイルのパスを指定してあげる必要があります。

-- The C compiler identification is GNU 5.4.0
-- The CXX compiler identification is GNU 5.4.0
-- Check for working C compiler: /usr/bin/arm-linux-gnueabihf-gcc
-- Check for working C compiler: /usr/bin/arm-linux-gnueabihf-gcc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done

$ make
Scanning dependencies of target helloworld
[ 50%] Building C object CMakeFiles/helloworld.dir/main.c.o
[100%] Linking C executable helloworld
[100%] Built target helloworld

$ file helloworld
helloworld: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-armhf.so.3, for GNU/Linux 3.2.0, BuildID[sha1]=9bd916148cf9880f2494f47199e8c001244a450d, not stripped

ちゃんとARM用にクロスコンパイルできました。
かなりミニマムな構成でのビルドの例なので、実際にオープンソースのプロジェクトなどをビルドする際はもう少しオプションやパスの指定などが必要になってくるかと思います。

自分の雛形用として、Githubにおいています。もし参考にされる方がいらっしゃいましたらどうぞ。

gist.github.com

[2017/10/2 追記]
クロス環境向けのインクルードパスとかのプレフィックスはどうするんだろう・・・ってなったので追記しておきます。

クロス開発環境のプレフィックスについて

クロス開発環境を使う際には、クロスコンパイラが使用するインクルードパス・ライブラリパスの指定が必要になってきます。

cmakeを使ってプレフィックスパスを指定する場合は、

 -DCMAKE_INSTALL_PREFIX:PATH=クロス環境のプレフィックスパス

とすればよいようです。

上記の Raspberry pi の例だと、

$ cmake .. -DCMAKE_TOOLCHAIN_FILE=../arm-toolchain.cmake -DCMAKE_INSTALL_PREFIX:PATH=/usr/arm-linux-gnueabihf/

のような感じになります。

感想

CMakeは書き方を覚えてしまえばMakefileを直接書くよりは楽といえば楽ですね。
昔はMakeの本を読んで一生懸命Makefileを書き方を勉強したものですが、Makefileは書かないとすぐに忘れてしまいます。

Makefileを書きたくないものぐさな私は、

$ echo "gcc main.c hoge.c -o target" > build.sh
$ chmod +x build.sh
$ ./build.sh

みたいなことをよくしています。

小規模な実験的なコードを書く場合はこのやり方でもよいのですが、プロジェクトが育ってくるとやはりビルド管理したくなります。

Makefileさえ知らない新人のころのあるプロジェクトでは、

ソースコード書いたらmfmkってコマンドを実行すればいいから」

と親切な先輩が教えくれた現場がありました。

あの便利な mkmf はLinux系のOSには標準では入っていないんですね。

依存関係とかを気にせずに特定のディレクトリのソースコード調べてMakefile生成するだけであれば簡単に作れそうに思います。気が向いたら作ってみたいです・・・