mcommit's message

ソフトウェア開発の仕事をしているsimotinといいます。記事の内容でご質問やご意見がありましたらお気軽にコメントしてください\^o^/

Elixir入門 ~8日目 HTTPoisonを動かすにはElixir1.2が必要だった~

昨日の続き。

mixが動くようになって、 mix.exs を編集して mix deps.get でモジュールをダウンロードするところまではできた。

と、思っていた。


今日はGithub

github.com


を参考にいざ動かそうとしたら以下のようなエラーメッセージがでた・・・

今度はなんだ?いい加減エラーメッセージでコンソールが真っ赤に染まるのには飽きてきた。

$ iex -S mix
Erlang/OTP 18 [erts-7.3] [source] [64-bit] [async-threads:10] [kernel-poll:false]

==> httpoison
warning: the dependency httpoison requires Elixir "~> 1.2" but you are running on v1.1.0-dev

== Compilation error on file lib/httpoison/base.ex ==
** (CompileError) lib/httpoison/base.ex:452: function '<-'/2 undefined
    (stdlib) lists.erl:1337: :lists.foreach/2
    (stdlib) erl_eval.erl:670: :erl_eval.do_apply/6

「httpoison を動かしたかったらElixir 1.2 を入れな!」

ということらしい。

httpoison のバージョンとかおとしたらもしかしたら動くのかもしれないが今日はやめておく。

その前に一言。

やりたいことができないこんなElixirじゃ、ぽいぞん。

そもそもどのレイヤーで依存関係が発生しているのかよく分からないがそんなに動かなくなるものかね・・・

mix 動かしたいだけなのに。


まぁ愚痴ってもしょうがないので今度はElixirのバージョンアップから試そう。

Elixir入門 ~7日目 mixが動かない原因が分かった~

mix が動かない原因が分かった。

「elixir iex INFO REPORT」

ググるといくつか情報が出ていて、日本語の情報だとQiitaのこちらの記事が参考になりそうです。

qiita.com
※参考にさせて頂き、ありがとうございます (m_m)

手元で確認したバージョンと結果

ということで、原因は要するに、 Erlang/OTPとElixirのバージョンの相性ということで間違いなさそう。

Linux環境では基本的にapt-getでインストールしていたけど、インストールされるOTPのバージョンが違うために偶然にもうまく動くパターンもあった。

多分apt使うときにupdateとか叩き忘れているという・・・

手元で確認したバージョンとかの組み合わせを一応あげておきます。

OS Elixir Erlang/OTP 動作 インストール方法
Linux Mint Elixir 1.1 18 OK apt
Linux Mint Elixir 1.1 20 NG apt
Windows10 Elixir 1.1 20 OK インストーラ

試したかったこ

さて、とりあえず手元にうごく環境もあるということでmixの勉強をしてみる。

Mixの使い方を知るには、

elixirschool.com

がよさそう。

早速 mix new で新しいプロジェクトを作る。
やりたいのはHTTP通信(クライアント側)HTTPクライアントの機能を使ってみたいのでプロジェクト名はhttpとした。
HTTPPoisonというモジュールがあるらしい。

$ mix new http                                                                      
* creating README.md
* creating .gitignore
* creating mix.exs
* creating config
* creating config/config.exs
* creating lib
* creating lib/http.ex
* creating test
* creating test/test_helper.exs
* creating test/http_test.exs

Your mix project was created successfully.
You can use mix to compile it, test it, and more:

    cd http
    mix test

Run `mix help` for more commands.

mix.exsの編集

mix.exs がRubyでいうところのGemfileに相当するファイルのようだ。

github.com

qiita.com

GithubやQiitaの記事などを参考にさせて頂き適当に編集。

mix deps.get が失敗した

$ mix deps.get

が bundle install 相当のコマンドになるようだ。

最初にmix deps.getを実行したときにモジュールの取得に失敗していた。

20:10:50.392 [error] SSL: :certify: ssl_handshake.erl:1474:Fatal error: certificate expired

Failed to check for new Hex version
Failed to fetch record for 'hexpm/httpoison' from registry (using cache)

20:10:50.393 [error] SSL: :certify: ssl_handshake.erl:1474:Fatal error: certificate expired


20:10:50.394 [error] SSL: :certify: ssl_handshake.erl:1474:Fatal error: certificate expired

ログをみてみるとSSL認証で失敗しているらしい。Elixirを試しているのはVM Ware Player上のLinux Mintだが案の定時間が大幅にずれていた。
時刻を合わせて再度試したらうまくいったようだ。いろいろとトラブルが発生するものだ・・・


続きは明日にしよう。

Elixir入門 ~6日目 elixirのインストールに失敗してた~

今日はmixを使っていろいろ試してみようと思ったらmix コマンドがちゃんと動かなかった。

どうもelixir/erlangのインストールに失敗してたっぽい。

$ mix

=INFO REPORT==== 10-Sep-2017::17:13:16 ===
    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
** (Mix) The task app.start could not be found

インストールしたけど使っていなかったwindows版だとこんなエラーメッセージでてない・・・

C:\>mix
** (Mix) "mix" with no arguments must be executed in a directory with a mix.exs file

Usage: mix [task]

Examples:

    mix             - Invokes the default task (current: "mix run")
    mix new PATH    - Creates a new Elixir project at the given path
    mix help        - Lists all available tasks
    mix help TASK   - Prints documentation for a given task

C:\>mix -v
Erlang/OTP 20 [erts-9.0] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:10]

Mix 1.5.1


elixir入門者過ぎてこのエラーメッセージの意味がわからないけどなんか必要なライブラリとか入っていないような感じだろうか。

明日ちゃんと調べてみよう。

Elixir入門 ~5日目 ファイルの読み書き~

今日は帰りが遅くなったのであまり時間が取れない。

ファイルの読みこみだけ試してみた。

CSV形式のファイルを読んでみる。

sample.csv とする。

aaa,1
bbb,2
ccc,3

ファイルアクセスはFileモジュールを使うらしい。
読み込みはreadかopen

iex(1)> File.read("sample.csv")
{:ok, "aaa, 1\nbbb, 2\nccc, 3\n"}

結果はタプルで返ってくる。

最初の

:ok

はアトム型といってrubyのシンボル相当らしい。

タプルで結果が返ってくるのは違和感があるが、読み込み結果を何らかの形で返す必要があるのでこういう仕様なんだろう。

存在しないファイルを指定すると

{:error, :enoent}

という結果が返ってくる。

カンマ区切りの分割にはStringのsplit関数が使えるらしい。
結果はリストになる。

iex(1)> String.split("aaa","bbb","ccc", ",")
["aaa", "bbb", "ccc"]

複数行・複数カラムの扱いはEnumモジュールのeachとかを使うとrubyみたいな感じでかけるらしい。

今日は疲れてるので早く寝よう、また明日だ。

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の実装が気になるところではあります。

後で見てみよう・・・