マイペースなRailsおじさん

Ruby、Ruby on Rails、オブジェクト指向設計を主なテーマとして扱います。だんだん大きくなっていくRuby on Rails製プロダクトのメンテナンス性を損なわない方法を考えたり考えなかったりしている人のブログです。

日記(トピック: JavaのflatMapがちょっと気に食わない)

調べたこと

やったこと

  • VMUbuntuvimをセットアップした
    • vim8のターミナルモード、つかいやすい。neovimはターミナルモードのウィンドウから移動するのがだるかった。
  • gatsby
    • GraphQLでクエリを投げてみた。
    • gatsby developでwatchがちゃんとできてないっぽかったけど解決できなかった。

考えたこと

Java.util.StreamのflatMap()はなんか微妙

flatMapの引数はStreamを返す関数。
てことは、リストのリスト(e.g. List<List>)からリスト(List)を得るみたいなありがちな処理は以下のように書くことになる。

注目してほしいのはこの部分。

List<Integer> list = listOfList.stream()
    .flatMap(List::stream) // .flatMap(l -> l.stream())でもいい
    .collect(Collectors.toList());

flatMapは、streamに対して関数を適用して、そこで得られたstreamをすべてくっつけて一つのstreamにします。 てことは受け取ったstreamの中身はリストなので、それをstreamに変換してくっつけると、リストの中身を取り出すことになるわけですね。

いやしかし私はflatMapの引数がstreamを返す関数というのがどうも気に入りません。
単にくっつけるときのstreamの中身を返すだけにしてしまうと、 streamへの変換を行わなければいけないくてそれを自動でやるのは難しいというところでしょうか。 それで、どのように変換するかはプログラマに任せないといけないという事情だと思います。が、どうになならないのかなーと思いました。
というか、その問題を解決しているライブラリがありそうなので調べてみます。

ファイルの重複行を削除するアルゴリズムってどうなってるんだろう

  • メモリに全行読み込む系
    • ハッシュテーブルを使えば、重複の検出は素早く済みそう
  • メモリに全行読み込まない系
    • ファイルをチャンクに分けて少しずつ見ていって、↑に書いたやつをメモリに書く代わりにファイルに書きながら進める
    • 一度ソートしてファイルに書く⇒次の行が同一か見るならいけそう
      • じゃあどうやってメモリに全行読み込まずにソートするんだろう問題

i18nのこと、そろそろちゃんと考えたい

qiita.com

記事のテーマ:i18n(ソフトウェアの国際化対応)は、単に文や時間(タイムゾーン)を差し替えれば済むような簡単なものじゃない。自分たちの文化にはない決まりにしたがって文ができていることがある。場合によっては、そのためのロジックを加えなければ対応できない。そのような対応が必要になるような文化を数多く持った国家で育つと、i18nに対応したプログラムを特に意識せずに書くことができるようになるのではないか。

今の仕事では、英語のUIを持ったサービスを作っているけど、ユーザーの使用言語は3つある。 ので、そのうちi18n対応をすることになりそうな気がしている。 とても興味深かった、というか、そのうちまたお世話になりそう

これからやること

  • flatMapをもう少しきれいに書けるようなライブラリがないか調べる。
  • gatsbyAPIと連携させる

今日は一時間20分くらいで書きました。

書くことに慣れたいので目標を考えました

毎日いろいろ調べてはいるけど記事にしようと思うと手が止まります。 調べて発信する力は非常に役に立つ力だと思うので、目標を決めて取り組みます。

目標

3か月後、未知の技術を調べる~記事にするを2時間でできる。

1カ月後の目標

記事を書くのにかかる時間を予想できる。

2か月後の目標

未知の技術を4時間で調べる~書くまでできる。

ねらい

記事の質にはこだわらず、書くことを習慣化することに重点を置きました。 100点のものを作ろうとして手が止まってしまうことがよくあるので、まずは記事を公開するという行為への心理的ハードルを下げられれば良いなと思っています。

目標達成のためにやること

  • 毎日記事を書いて公開する。
    • 調べたこと
    • 考えたこと
    • やったこと
  • 週に一回、一週間学んだ中で特に気に入ったものについてまとめてQiitaで公開する。
    • 今日の分は達成⇒次は8/15

↓書いたもの qiita.com

Object.equals()とObject.hashCode()のデフォルト実装

java.lang.Objectのequals()とhashCode()の実装について調べました。

Object.equals()

JDK 8 での実装は下記の通り、参照値を比較しているだけです。

    public boolean equals(Object obj) {
        return (this == obj);
    }

jdk8u/jdk8u/jdk: a71d26266469 src/share/classes/java/lang/Object.java

参照値とはすなわちオブジェクトへのポインタなので、Object.equals()は同一のインスタンスであるときだけtrueを返します。

The reference values (often just references) are pointers to these objects, and a special null reference, which refers to no object.

参照値(単に参照とも)はオブジェクトへのポインタまたはnullです。nullはオブジェクトを参照しない特殊な参照値です。

Chapter 4. Types, Values, and Variables

Object.hashCode()

hashCode()はJVMの実装に依存します。

srvaroa.github.io

こちらの記事でとても詳しく説明されていました。 結局のところ

  • OpenJDK 8, OpenJDK 9
    • スレッドごとに持つ値をXorshiftした数
  • OpenJDK 7, OpenJDK 6
    • 乱数

とのことでした。 XorShiftは、排他的論理和とビットシフトのみで(高速に)疑似乱数を生成できるアルゴリズムです。 hash codeは一度生成されるとObject Headerと呼ばれるインスタンスごとに持つ領域に記録されます。 二回目以降のhashCode()の呼び出し時には、Object Headerに記録されたhash codeを返します。

OpenJDK8でhash codeを生成するコードは次の通りです。

     // Marsaglia's xor-shift scheme with thread-specific state
     // This is probably the best overall implementation -- we'll
     // likely make this the default in future releases.
     unsigned t = Self->_hashStateX ;
     t ^= (t << 11) ;
     Self->_hashStateX = Self->_hashStateY ;
     Self->_hashStateY = Self->_hashStateZ ;
     Self->_hashStateZ = Self->_hashStateW ;
     unsigned v = Self->_hashStateW ;
     v = (v ^ (v >> 19)) ^ (t ^ (t >> 8)) ;
     Self->_hashStateW = v ;
     value = v ;

jdk8u/jdk8u/hotspot: 87ee5ee27509 src/share/vm/runtime/synchronizer.cpp

スレッドのメンバ変数_hashStateX_hashStateWを使ってハッシュコードを生成します。 xor-shiftのアルゴリズムは理解できていないのですが、hashStateX, hashStateY, hashStateWをアルファベット順で一つ前の名前を持つ変数に値を移し、hashStateWを生成したハッシュコードにすることで、次の呼び出し時にもユニークな値が生成できるようにしています。

_hashStateはスレッド生成時に次のように初期化されます。

  _hashStateX = os::random() ;
  _hashStateY = 842502087 ;
  _hashStateZ = 0x8767 ;    // (int)(3579807591LL & 0xffff) ;
  _hashStateW = 273326509 ;

jdk8u/jdk8u/hotspot: 87ee5ee27509 src/share/vm/runtime/thread.cpp

Spring BatchのReaderである程度複雑なことをする

やりたいこと

Readerである程度複雑なことをしたい。 ある程度複雑なこととは、例えば

  • 複数のテーブルからデータを読み込んでJavaで結合したい
  • テーブルからすべてのレコードを読み込んだ
  • DBのデータとRest APIのデータを結合したい

などです。

Readerでできること

Readerは、ItemReaderインターフェースの実装クラスです。 よく使いそうなItemReaderの実装クラスはSpring Batchで17種類提供されていますが、一回の読み込みで複数回クエリを発行するようなReaderやREST APIを叩くはものはありません。

このため、複雑なReaderは自分で実装しなくてはいけません。 ItemReaderインターフェースは、T read()を提供します。では、単純にread()にデータ取得処理を書けば良いのかというとそうでもありません。

Readerはreadメソッドが呼ばれるたびに新しいデータを一つ返します。 これは、一回のクエリ発行で一つのデータが返るような処理をすればいいということではありません。 read()はnullを返すまで実行されるので、そのようなクエリを発行したら無限ループしてしまうことになるからです。 ということは、簡単に言うと次のような実装が必要になります。

public class MyItemReader<T> implements ItemReader<T> {
    private List<T> data;
    
    private List <T> getData() {
        // なんか複雑な処理でデータをとってくる
        return data;
    }

    T read() {
        if (data == null) {
             data = getData();
        }

       if (data.size() < 1) {
             return null;
       }

        return data.remove(0); 
    }
}

Spring Batchはリスタート機能も備えているのですが、このままでは外部からReaderを再開してあげる方法がないので、このクラスはリスタートに対応していません。

AbstractItemCountingItemStreamItemReader

リスタートできるように、どこまで読み込んだとかの管理をうまくやってくれるクラスはないのかなー。ありました。

抽象クラスAbstractItemCountingItemStreamItemReaderは、Readerが返している要素の数と返せる要素の数(最大値)を管理してくれます。 Readerで読み込んだ要素の管理は自分でしないといけませんが… JdbcPagingItemReaderなどは、この抽象クラスを継承して実装しているので、参考になるでしょう。

public class MyItemReader extends AbstractItemCountingItemStreamItemReader<T> {
    private List<T> data;
    
    private List <T> getData() {
        // なんか複雑な処理でデータをとってくる
        return data;
    }

    @Override
    protected void doOpen() throws Exception {}

    @Override
    protected Employee doRead() throws Exception {
        if (results == null) {
              data = getData();
        }

        return results.get(getCurrentItemCount() - 1);
    }

    @Override
    protected void doClose() throws Exception {}
}

実はこのままだと、やっぱりdataを復帰させる方法がないので、やはりリスタートには対応していません。近いうちにリスタート対応版を追記します。

React + Reduxをさっくりと覚えた

こんなのつくりました

ytnk531.hatenablog.com

(さっくりと)覚えたこと

動機

  • フロントエンドのコードを読みたい
    • 障害の原因調査が捗りそう

問題

  • 読めない
    • [...array1, ...array2]
    • { key1 } = object
    • {...object1, ...object2}
  • なにやってるかわからない
    • 階層化されたコンポーネント
    • イベントを処理する流れが追いかけられない
    • reducerとかいう謎の単語

さっくりと覚えていく

React

動的なUIを宣言的に書ける。

やったこと

  • 公式サイトのチュートリアル
  • create-my-react-appで作ったテンプレートをいじくる
  • ソートするやつを作る

メモ

  • DOMコンポーネント
    • HTMLの塊を部品として定義して再利用可能にする
    • props
    • state
      • コンポーネントの内部で保持している値(フィールド)
      • stateの更新はめんどくさい
    • propsやstateが変化すると、DOM要素が再描画される(儀式が必要)

Redux

stateを一か所に集めて、stateの更新と反映を行えるようにする。
Observerパターンのちょっとマッチョなやつ。
Reducerというコンポーネントにロジックをまとめられる。

やったこと

メモ - dispatcherで生成したアクションをreducerで受け取って新しい状態を計算する - stateをsubscribeして画面を再描画する

Recat-Redux

propsにstateとdispacherを渡せるようにする。
状態の更新⇒再描画のための儀式がゴッソリ消える。
ReactDOMコンポーネントはビューを返すためだけに使えるようになる⇒スッキリする

やったこと

  • Reduxの流れで一緒に学習
  • ソートするやつをリファクタ

新しい発見

  • コンパイルエラー相当の静的解析はしてくれる
    • トランスパイルするから当たり前なんだけど盲点だった
  • 環境構築めっちゃラク
  • ライブラリごとに使うメソッドの数が少なかったので、あんまり覚えなくてよかった
  • 画面で動くの楽しい

つまづいたところ

  • 当初、Reduxを使うことにあんまりメリットを感じなかった
    • とりあえずReactコンポーネントで状態管理してみた
    • ⇒カオスになった(Reduxを使うまではそこまでカオスだとも思ってなかった)
  • Presentational ComponentとContainer Componentの構成
    • どうやってState Treeを構成すべきか悩ましい
  • Presentational Componentにどれだけロジックを入れるか悩ましい
  • SVGのアニメーションむずい
  • sleep()ないんですか

つまづかなかったところ

  • npm
    • gradleとだいたい同じ
  • Reduxの概念
  • 純粋関数

今後

  • テストしたい
  • reduxで副作用したい⇒redux-saga
  • アニメーションしたい

reactのstateがごちゃつき始めるくらいまで使ってみた。

stateがごちゃついてなんかつらいなこれ…ってなり始めるくらいまでreact.jsを使ってみました。 初心者なりにつらかったところを記録しておきます。 react.jsの環境構築や使い方については、初心者向けの記事は良い記事がたくさんあるので、そちらにお任せします。

つくったもの

ソートするやつをつくりました。 0~99までの100個の数値を、バブルソートでソートします。

f:id:ytnk531:20180617203631p:plain
ソートするやつ
もっとちゃんとしたやつをどこかで見たことがるって?はい、私も見たことがあります。いいのです。学習用なので、これでいいのです。
herokuにデプロイしてみました。 https://evening-brushlands-42735.herokuapp.com/ https://github.com/ytnk531/react-tutor ソースコードも公開しています。ただしフロントエンドド素人が作ったものであるため、くれぐれもお手本にはしないように( ´∀` )

github.com

reactのコンポーネントで状態管理するのはつらい?

下記の記事では、reactのコンポーネントの状態管理はreduxに任せてしまおう、と言っています。 qiita.com reactのコンポーネントはstateと呼ばれるプロパティを持っていて、通常はこのstateプロパティを変化させてコンポーネントの状態を変更します。
reduxは、コンポーネントごとに状態を持つのはやめて、reduxを使うことで一か所で管理してしまおう、というライブラリです。こういったライブラリが推奨されるということは、コンポーネントで状態管理することに辛さがある、ということが示唆されているわけですが、いまいちそのつらみが理解できませんでした。

つらくなるまでやってみた

reactを使うならreduxと一緒に使いなさい!と教えていただきましたが、いまいち嬉しさがわかりませんでした。reduxで状態管理する嬉しさがわからないというよりは、reactで提供されているstateを使って状態管理する困難さがわからなかったのです。
ということで、ある程度つらくなるまで使ってみました。 フロントエンドは経験も知識ありませんでしたが、react自体は難しい物じゃないのでわりとすんなりと使えるようになりました。

作るのに必要だった知識

子要素は筆者の事前の理解度や利用経験。

stateで状態管理するつらさ

stateの更新がめんどくさい

stateを操作するときは、以下のように記述します。

// value1を1増やす
this.setState(prevState => ({
  value1: prevState.value1 + 1
});

状態を変えるたびにこれをやるのはしんどいってもんですね。

stateの反映されるタイミングが謎

setState()は非同期で呼ばれます。いつstateが更新されるかは定かではないのです。
例えば、下記のコードは無限ループします。 '''javascript // finishの初期状態はfalse while(this.state.finish) { this.setState({finish: true}); } '''

やっぱり散らばるのはよくない

コンポーネント間で共有したい値があったりします。 stateを外部から参照する方法があるにはあるのですが、とてもやりづらいうえに非推奨だそうです。

minotaur.badwitch.io

コンポーネントの状態が、変更されているかわかりにくくなってしまうのは、確かに悪いことに感じます。 状態がいつ変更されているのかを追跡する手段が用意されていなければ、バグを見つけにくくなってしまうでしょう。 一つのクラスの中にstateが留められていたとしても、それはやはり同じでしょう。

結局つらかったか

すみません、stateを持ったコンポーネントを一つしか作らなかったので、そんなにつらくなかったです。 というか、stateもったコンポーネント同士で親子関係を作ると値の変更がしにくいので、子コンポーネントは状態を持たずにpropsで渡した値が状態になるような作りにするほかなかったです。
つまるところ、規模が大きくなってくると、一つのコンポーネントでたくさんの状態を管理したり、コンポーネント間で値が伝搬していくように作らなければならないように思います。
これがコンポーネントで状態を管理するつらさ…で…いいのか…な…。

つまづいたところ

コンポーネントをループで生成する h3poteto.hatenablog.com

Docker ToolBoxのDocker環境をWindows 10 HomeのBash On Windowsから使う

結論

docker-machine.exe start default
docker.exe $(docker-machine.exe config default) run hello-world

はじめに

Windows 10 HomeでDocker Toolboxで入れたDockerをBash On Windowsを使う方法を説明します。 Docker for Windows + Bash on Windowsだったり、Dockerを入れたVM + Bash on Windowsとはちょっと違う環境の話です。 Docker for Windows使えればよかったんですけどねぇ。

注意

本ページに登場するコマンドの.exeは省略しないでください。 省略すると、コマンドが見つからずエラーになるか、Linux Subsystemの方にインストールされたコマンドが実行されてしまいます。

環境

Windows 10 HomeではDocker for Windowsが利用できないので、Docker Toolboxを使います。Docker ToolboxとBash on Windowsはインストール済みという前提で説明していきます。

BoWからdocker.exeはうまくいかない

BoWからDocker Toolboxでインストールされたdocker.exeを実行すると次のようなエラーになります。

$ docker.exe ps
error during connect: Get http://%2F%2F.%2Fpipe%2Fdocker_engine/v1.31/containers/json: open //./pipe/docker_engine: The system cannot find the file specified. In the default daemon configuration on Windows, the docker client must be run elevated to connect. This error may also indicate that the docker daemon is not running.

docker.exeはDockerのクライアントですから、docker daemonというdockerのコンテナを実際に動作させるサーバと接続しなければなりません。 BoWからdocker.exeを起動した場合、接続先の設定がされていないためエラーになります。 Docker Quick Start Terminalでは起動時にこの設定を行ってくれるようです。以下では、Docker Quick Start Terminalに頼らず接続設定を行う方法を説明していきます。

docker-machine.exeでdocker-daemonを起動する

Docker Toolboxで使えるようになるDocker daemonは、実はdocker-machineで作られたVM上にあるので、docker-machine.exeを使って確認できます。

$ docker-machine.exe ls
NAME      ACTIVE   DRIVER       STATE     URL   SWARM   DOCKER    ERRORS
default   -        virtualbox   Stopped                 Unknown

defaultという名前のdocker machine(docker daemonを動作させるVM)があるのがわかりますね。Docker Quick Start Terminalではこのdocker machineを利用しています。STATEがStoppedなので、まずは起動しましょう。私の環境では起動に3分くらいかかりました。

$ docker-machine.exe start default
Starting "default"...
(default) Check network to re-create if needed...
(default) Waiting for an IP...
Machine "default" was started.
Waiting for SSH to be available...
Detecting the provisioner...
Started machines may have new IP addresses. You may need to re-run the `docker-machine env` command.

Docker Quick Start Terminalは、起動時このdocker machineの立ち上げも自動で行っていたというわけです。

Dockerを使う

起動したdocker machineを介してdockerを操作するには、下記のコマンドを使います。

docker.exe $(docker-machine.exe config default) [dockerコマンド]

[dockerコマンド]にはrun mysqlpsなど、docker操作用のコマンドを入れてください。docker-machine.exe config defaultでdefault(Docker Machine)に接続するためのdocker.exeのためのオプションが生成されるので、これを評価したものを引数にしています。

では実行してみます。

$ docker.exe $(docker-machine.exe config default) run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
9bb5a5d4561a: Pulling fs layer
9bb5a5d4561a: Verifying Checksum
9bb5a5d4561a: Download complete
9bb5a5d4561a: Pull complete
Digest: sha256:f5233545e43561214ca4891fd1157e1c3c563316ed8e237750d59bde73361e77
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (amd64)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
 https://hub.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/engine/userguide/

BoWからDocker ToolboxでインストールしたDockerを操作することができました。

おまけ: もうちょっと楽にする

docker machineの起動と、docker操作用のコマンドにaliasを張っておきます。

alias dmsd='docker-machine start default'
alias docker='docker.exe $(docker-machine.exe config default)'

docker machineの起動は/etc/rc.localに書いておけばうまくいくでしょうか? すみません、試してないのでわかる方いらっしゃいましたらコメントか何かで教えてください😷

余談

Docker Toolboxのほうでは、おそらくdocker-machine.exe env default --shellで生成される設定スクリプトを起動時に実行して環境変数を設定しているのですが、これと同じ方法はBoWでは使えません。というのも、docker.exeはBoWで設定された環境変数を読んでくれないからです。 ならば、Windowsにインストールしたdocker.exeでなく、BoWにインストールしたdockerクライアントを実行すれば環境変数を読めるのでうまく動きそうです。結果は、これもダメでした。docker-machine.exe env default --shellで設定した環境変数では、pemファイルのパスがWindowsの形式(C:\foo\bar\的な)で設定されてしまい、BoWからはそのパスをたどれないからです。