simotin13's message

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

Elixir入門 ~4日目 モジュール・名前付き関数~

昨日は無名関数について簡単に勉強したので今日は名前付き関数について勉強してみる。

モジュール・関数については、は参考書籍として使っているプログラミングElixirの6章で説明があるので読みながら勉強してみる。

プログラミングElixir

プログラミングElixir

モジュールと名前付き関数

名前付き関数はモジュールの内側で書く必要があるそうだ。

6章のtimes.exsを写経して動かしてみた。

defmodule Times do
  def double(n) do
    n * 2
  end 
end

モジュールの定義は

defmodule モジュール名 do
end

と書く。do~endで囲むあたりはrubyと似ている。

名前付き関数は、モジュールの内部で、

def 関数名(引数) do
end

とすればよいみたい。
elixirは動的型付けなので引数は引数名だけでよい。

ファイルに書いたコードを実行するためにはいくつか方法がある。

1.iexの起動時に引数でファイル名を指定してロードする方法。

iex times.exs

2.iex内でc ファイル名として、c ヘルパーの機能でロードする方法。


コンパイルコマンドであるelixircを使う方法はここでは紹介されていない。
検索してみても見つからなかったのでこの本自体ではelixircに関する説明がなさそうだ。

名前付き関数の呼び出しは

モジュール名.関数名(引数)

の形になる。

iex(1)> Times.double(3)
6
iex(2)> 

気になって試したこと

  • iex ファイル名を指定して読み込めるのは1ファイルだけのようだ。2ファイル目以降を指定してもロードされていないっぽい。
  • モジュール名は大文字で、関数名は小文字ではじめないといけないみたい。
defmodule times do
  def double(n) do
    n * 2
  end 
end

defmodule Times do
  def Double(n) do
    n * 2
  end 
end

コンパイルに失敗する。コンパイルに失敗するとiexは起動せずエラーメッセージが出力されて終了する。

$ iex times.exs
Erlang/OTP 20 [erts-9.0] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false]


=INFO REPORT==== 10-Sep-2017::15:42:48 ===
    application: logger
    exited: {{shutdown,
                 {failed_to_start_child,'Elixir.GenEvent',
                     {undef,
                         [{gen,debug_options,[[]],[]},
                          {'Elixir.GenEvent',init_it,6,
                              [{file,"lib/gen_event.ex"},{line,538}]},
                          {proc_lib,init_p_do_apply,3,
                              [{file,"proc_lib.erl"},{line,247}]}]}}},
             {'Elixir.Logger.App',start,[normal,[]]}}
    type: temporary
** (CompileError) times.exs:1: undefined function times/0
    (elixir) expanding macro: Kernel.defmodule/2
    times.exs:1: (file)

iexの起動時にINFO REPORT==== というのがでているがこれは何なのかよくわかっていない。
別に害があるわけではないけどなんか気になる。

今は意図的に失敗させてわけだけど、このエラーメッセージから間違いに気づくのは慣れないと難しいかもしれないな。

ちなみに先頭以外は大文字でも小文字でもよくて、

defmodule TIMES do
  def dOuble(n) do
    n * 2
  end 
end

は問題なくコンパイルが通る。

  • モジュール外で関数は定義できない。

モジュールの外側で関数を定義しようとするとどうなるか?

def double(n) do
    n * 2
end

コンパイル時のメッセージは以下の通り、

** (ArgumentError) cannot invoke def/2 outside module
    (elixir) lib/kernel.ex:3772: Kernel.assert_module_scope/3
    (elixir) lib/kernel.ex:2977: Kernel.define/4
    (elixir) expanding macro: Kernel.def/2
    test.exs:1: (file)

cannot invoke def/2 outside module と書いているのでこれは比較的わかりやすい気がする。

rubyのようにトップレベルでの関数定義はできないようだ。
もっともrubyの場合はKernelモジュールの省略という話があるので、思想としてはそんなに違わないのかもしれない。

今日はこれくらいにしておこう。

4日目を終えて

少しずつの勉強なので恐ろしく進展が少ない。新しい言語を勉強するときによくやるのが、

  • CSV,JSONフォーマットを扱うファイル入出力
  • ソケットプログラミング
  • HTTPのクライアント(wget的なコード)

なんかをよくやる。

要するに使い捨てのツールを書いてみるということなんだけど、Elixirは関数型言語だしそういったお手軽スクリプティングとして使うのは違う気がする。(別にしたらだめというわけではないだろうが)

File – Elixir v1.5.1

を少し読んでみたけどまた覚えないといけないことがいろいろ出てきそうだ。

明日はファイルアクセスを少し触ってみよう。

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!
[]

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


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