eslintでflowの検証結果も表示させる

そんなにプログラミングするわけではないが、
javascriptは本当に色々変更が激しいなと最近感じてる。
今回は、eslintとflowを使って静的検証が行える環境構築手順を残そう。
eslintを実行させると、flowのチェックと結果を取得できるようにする

目次

  1. eslintを使ってみる
  2. flowを使ってみる
  3. npm run lintでflowの検証結果も表示
  4. npm run lintでflowのエラー結果も表示
  5. flow-remove-typesで実行用ファイルを作成する

1. eslintを使ってみる

  npm install --save-dev eslint

  // .eslintrcを作成
  ./node_modules/.bin/eslint --init

あとはコマンドライン上の質問に答えると、.eslintrc.jsonなどの設定ファイルができる

npm run lintでeslintが起動できるようにpackage.jsonの修正

package.json
  scripts : {
     lint: "eslint ./src"
  }

ディレクトリ、ファイルを作成する

  mkdir src
  touch eslinttest.js
eslinttest.js
  function sum(x,y) {
     return x+y;
  }

  let result = sum(1,1);

これでnpm run lintと入力すると検証結果が表示される

  3:11  error  Infix operators must be spaced                                                                space-infix-ops
  7:5   error  'result' is assigned a value but never used                                                   no-unused-vars
  7:5   error  'result' is never reassigned. Use 'const' instead                                             prefer-const
  7:27  error  A space is required after ','                                                                 comma-spacing
  7:31  error  Newline required at end of file but not found                                                 eol-last

2. flowを使ってみる

  // npmで追加
  npm install --save-dev flow-bin

  // .flowconfigを作成する
  ./node_modules/.bin/flow --init

npm run flowでflowが起動できるようにpackage.jsonの修正

package.json
   ...
   scripts :{
      flow : "flow"
   }
   ...
   touch ./src/flowtest.js
flowtest.js
  //@flow
  function sum(x: number, y: number): number {
     return x+y;
  }

  // あえて間違える
  let result: string = sum(1,1);

npm run flowでflowの検証結果が表示される

Error ----------------------------------------------------------

Cannot assign `sum(...)` to `result` because number [1] is incompatible with string [2].

   src/flowtest.js:7:24
   7| const result: string = sum(1, 1);
                             ^^^^^^^^^

References:
   src/flowtest.js:2:37
   2| function sum(x: number, y: number): number {
                                          ^^^^^^ [1]
   src/flowtest.js:7:15
   7| const result: string = sum(1, 1);
                    ^^^^^^ [2]

3. npm run lintでflowの検証結果も表示

flowの記述が残されたままだと、eslintは失敗する
なので、いくつか前準備が必要になる。

  npm install --save-dev babel-eslint eslint-plugin-flowtype

.eslintrc.jsonも修正し、eslint-plugin-flowtypeのconfigurationをコピペ。

これでnpm run lintを実行すると、eslint単体のときと同様のエラーが表示される。

4. npm run lintでflowのエラー結果も表示

しかし、表示されるのはeslintの結果だけで、flowのエラーはこのままでは表示されない。
なので、もう一つplugin追加が必要となる。

  npm install --save-dev eslint-plugin-flowtype-errors
.eslintrc.json
  ...
    "plugins": [
      "flowtype",
      // 追加
      "flowtype-errors"
    ],
    rules: {
      // 追加
       "flowtype-errors/show-errors": 2,
       "flowtype-errors/show-warnings": 1
    }
  ...

あとはnpm run lint

これでeslintのエラーに加えてflowのエラー内容も表示される.

5. flow-remove-typesで実行用のファイルを作成

npm installl --save-dev flow-remove-types

npm run buildで起動できるようにpackage.jsonの修正

package.json
 scripts: {
  "build": "flow-remove-types src -d lib --pretyy"
 }

npm run buildを実行すると、flowの型情報を削除したファイルがlibに出来る。

最後に

jsはインタプリタ言語なのに静的検証やら型検査が普通になってきているのようだ。これもCIなどでbuildまでの時間を短くできるようになったのが、背景なのかもなと感じた。 これでバシバシ書いていけるぞ

async/awaitの学習

以前Promiseを使ったループ処理について記述したが、これは再帰関数を利用していた。
普段扱いなれているfor文でループ処理はかけるのかなと感じていたが、
どうやらasync/awaitを使えばできることがわかったので、残しておく。

jQueryの$.ajaxからPromiseへの理解に時間を要したので、async/awaitも同様理解するのに
時間がかかるのかと予想していたのだが、この技術はPromiseをさらに便利にする仕組みだ。
だから、Promiseの使い方がわかっていれば簡単に理解できるし、便利になる。

目次

  • asyncを関数名につけるとreturnされるものは、必ずPromiseになる
  • async/awaitを使うと、非同期処理を同期的に記述できる
  • Promise.allも同期的にかける
  • 前回のPromiseでのループ処理をasync/awaitで記述してみる
  • 非同期処理には全てasyncをつけるのか?

asyncを関数名の前につけるとreturnされるものは、必ずPromiseになる

  async function fn() {
    return 1;
  }
  console
  > fn()
  > Promise {1}

async/awaitを使うと、非同期処理を同期的に記述できる

  async function AsyncTest() {
    let result = await fn(); // awaitを使うには必ずasync関数内でないとだめ。
    console.log(result);
    return 1;
  }
  console
  > AsyncTest()
  > 1
  > Promise {1}

Promise.allも同期的にかける

  async function AsyncTest2() {
    let [r1, r2] = await Promise.all([fn(), fn()]);
    console.log(r1);
    console.log(r2);
    return r1 + r2
  }
  console
  > AsyncTest2() 
  > 1
  > 1
  > Promise {2}

前回のPromiseでのループ処理をasync/awaitで記述してみる

  async function fn(i) {
    console.log( (i+1) + '回目')
  }

async function loop (count) {
  for ( let i = 0; i < count; i += 1 ) {
    await fn(i);
  }
  return 'end'
}
  console
>loop(5).then(r=>{console.log(r)})
 1回目
 2回目
 3回目
 4回目
 5回目
 end
Promise {<resolved>: undefined}

最後に

Promiseについて理解できていたら、async/awaitはすごい便利な技である。 nodeで使う場合、v7以上であればasync/awaitがつかえる。

条件付き確率の話

今日は確率について。僕は数学は割と好きな方だったけど、確率にはあまりよい思い出はない。久しぶりに確率の勉強をしていて、気にかかることに出会ったし、数式使ってみたかったのでブログに載せようと思う。

プログラミングのための確率統計1第2章の冒頭に、以下の会話がある。

A: 私の調査によるとゲーム機所持者の犯罪者は50%以上です。何らかの規則をするべきでしょう。
B: 何そのやたら高い数字?
A: 最近の少年犯罪では犯人の半数以上がゲーム機を所持していました。
B: ええと、つっこみ所ありすぎて困るんだけど、とりあえず犯罪関係なしで最近の少年のゲーム機所持率から調べなおしてくれない?

会話の流れからAさんの主張がおかしいことは読み取れるが、
なぜ間違っているのかちゃんと説明できるだろうか?
少なくとも僕は即答できなかったので、今日はこの説明をする。

日本語を数式に

それでは会話をもう少し理解するために、これらを数式で表現してみよう。
(ややこしい問題はまず数式で表現してみるだけでも少し整理できるものだから)

「ゲーム機所持者の犯罪者が50%以上」をどう表現する?

Aさんのはじめの発言の数式表現は正直難しいと思っている。というのは、
- ゲーム機所持者でかつ犯罪をおかす確率が50%以上なのか?(同時確率)
- ゲーム機所持しているならば、その人が犯罪を犯す確率は50%以上なのか?(条件付き確率)
のどちらか判定できないからだ。
ひとまず話をこのさきを進めやすくするため、後者だと想定する。
だから、数式にすると

P(Y=犯罪をおかす| X=ゲーム機所持) \geq \frac{1}{2} \tag{1}

「最近の少年犯罪では犯人の半数以上がゲーム機を所持していました」をどう表現する?

こちらは簡単。

{
    P(X=ゲーム機所持 | Y=少年犯罪を犯す) \geq \frac{1}{2} \tag{2}
  }

もう一度会話を整理

Aさんは、(1)故にゲームを所持しないように規制すべきだという。
つづいてBさんがその根拠は何かという疑問に対して、(2)が根拠だとわけだ。

もう少しシンプルに表現すれば、
P(X=ゲーム機所持| Y=犯罪をおかす) \geq \frac{1}{2}
がなりたつとき、
P(Y=犯罪をおかす| X=ゲーム機所持) \geq \frac{1}{2}
も成り立つから、ゲームを持たせるなという主張をしている。
つまり、条件付き確率の条件を入れ替えても確率が同じだという主張だ。
(もっとも少年犯罪も犯罪も同じと考えているが...)

Aさんの判断の間違いはどこか?

では、条件付き確率の条件を入れ替えても確率が同じとなるのは、
P(X=ゲーム機所持)やP(Y=犯罪をおかす)がどのような関係にあるときだろうか調べてみよう。

条件付き確率の公式

P(A|B) = \frac{P(B|A)P(A)}{P(B)}

を利用すると、(1)は、

{P(X=ゲーム機所持| Y=犯罪をおかす) =
  \frac{P(Y=犯罪をおかす|X=ゲーム機所持)P(X=ゲーム機所持)}
  {P(Y=犯罪をおかす)}
}

さらに変形して、

{
  P(Y=犯罪をおかす|X=ゲーム機所持)
  \geq
  \frac{1}{2}\frac{P(Y=犯罪をおかす)}{P(X=ゲーム機所持)}
}

仮にP(Y=犯罪をおかす|X=ゲーム機所持)が50%であったとすると、

{
  \frac{1}{2}
  \geq
  \frac{1}{2}\frac{P(Y=犯罪をおかす)}{P(X=ゲーム機所持)}
}

{
  P(Y=犯罪をおかす) \geq P(X=ゲーム機所持)
}

これは普通当てはまらないと容易に想像できる。
だからBさんが最後にゲーム機の所持率から調べなおしてというのである。


  1. プログラミングのための確率統計

    プログラミングのための確率統計

power-assertとmochaを使ったnodejsのテスト

昨年decodeではじめて和田卓人さんの講演を聞いて、テスト駆動開発というものを知りとても興味が湧いた。テスト環境づくりは多くのサイトで紹介されているのだけど、自分の備忘録として環境構築手順を残しておこうと思う。もちろんアサーションツールには、power-assertを使用する。

大きな流れ

  1. npmで必要なモジュールのインストール
  2. npm scriptsでテスト実行コマンド登録
  3. テストコードを書く
  4. テスト実行

1 npmで必要なモジュールのインストール

// mochaのインストール
npm install --save-dev mocha

// power-assertのインストール
npm install --save-dev power-assert intelli-espower-loader

2 npm scriptsでテスト実行コマンド登録

testフォルダを作成して、このフォルダをテスト対象として実行するようにnpm scriptsに記述する。

// package.json
  ...
  "scripts": {
    "test": "mocha --require intelli-espower-loader ./test/"
  },
  ...

3 テストコードを書く

テスト対象のコードはただの計算クラスで、2つの引数を足したり引いたりするだけ。

class Calc {

  static add(x, y) {
    return x + y;
  }

  static sub(x, y) {
    return x - y;
  }

}
exports.Calc = Calc;

テストコードでは、power-assertと上記テスト対象のコードをロードして、あとはテストコードの記法通り。本筋とはずれるけど、オブジェクトの分割代入便利ですね。

const assert = require('power-assert');
const {Calc} = require('../module/Calc');  // 分割代入

describe('Calcクラス', () => {

  it('addメソッド', () => {
    assert( Calc.add(1,2) === 3 );
  });


  it('subメソッド', () => {
    assert( Calc.sub(1,2) === -1 );
  });

});

4 テスト実行

あとは、npm testでテスト実行できる。
実行結果はこんな感じ

 Calcクラス
    ✓ addメソッド
    ✓ subメソッド

  2 passing (9ms)

疑問

  • DBアクセスするコードのテストはどうすればよいのだろうか?
  • テストごとにDBを初期化しテスト終了後も初期化する作業が必要なのか?

expressのルーティング共通処理

nodejsでHTTPサーバーを立てるときに利用するexpress。
色々なルーティングを記述していくと、いずれのルーティングでも共通処理を噛ませたいという気持ちになる。

共通処理というのは、セッションが切れていたときにログイン画面に戻す処理のことで、今までは下記のように個々のルーティングごとに記述するアホなことをやっていた。

app.post('/select',  ( req, res ) => {
  
  // ①共通処理:セッション情報の存在判定
  if ( !req.session.pass ) {
     res.json(440, {result:'expired', message:'セッションが切れました。ログインからやり直してください。'});
     return;
   }

  // ②ルーティング固有の処理
  res.header("Content-Type", "application/json; charset=utf-8");
  res.json( something );

}

Nodeクックブックを読んでると、next()を使えばよいことがわかった。

// ①共通処理:セッション情報の存在判定
const checkSession = ( req, res, next) => {
  if ( !req.session.pass ) {
     res.json(440, {result:'expired', message:'セッションが切れました。ログインからやり直してください。'});
     return;
   }
  next()
} ;

app.post('/select', checkSession,  ( req, res ) => {
  
  // ②ルーティング固有の処理
  res.header("Content-Type", "application/json; charset=utf-8");
  res.json( something );

}

expressのpost, getなどのHTTPメソッドには、第二引数以降に複数のcallbackを指定でき、next()を使うことで順番に処理を行ってくれる。
今回では、checkSession(共通処理)がまず実行されsession状態が切れていなければ、next()により次のcallback(固有の処理)を行ってくれる。

Nodeクックブック

Nodeクックブック

自炊ツールを買った

背景

昨年度年末に引っ越しをしたときに、ダンボールの数が20個近くになったのだが、
そのうち5箱が本だった。また多いだけではなく、重いので運ぶのも大変だった。
これからも本は増えていくだろうと考えると次なる引っ越しもつらいと思い、
所有している全書籍の電子化計画を決行することにした。

f:id:poppon555:20180115222307j:plain
自炊ツール

ツール

  1. 裁断機
    インターネットで検索して、おすすめの裁断機を調べた。ダンボール5箱分を裁断するので、 厚い本でも裁断でき、刃こぼれしてもメンテナンスが楽にできる裁断機を重視して選択した。

プラス 断裁機 かんたん替刃交換 PK-513LN 裁断幅A4タテ 26-309

プラス 断裁機 かんたん替刃交換 PK-513LN 裁断幅A4タテ 26-309

  1. スキャナー
    スキャナーは迷うこと来なく、ScanSnapを選択した。
    大学院時代に使っていて慣れていたし、機能も満足いくものだったので。

富士通 ScanSnap iX500 (A4/両面)

富士通 ScanSnap iX500 (A4/両面)

自炊を初めて

思った以上に電子化までの時間は遠い。
20冊電子化するのに、3時間くらいかかるので、ほとんど休日はなくなってしまう。
しかも、裁断しても裁断しきれていないページがあり、スキャナーで読み込んだときに失敗することもある。特に最初と最後のページで起こりやすいことも分かった。
でも少しでも片付き部屋のスペースが確保されると少しだけモチベーションは上がる。
まだまだ電子化計画は長そうだが、地道にやっていこうと思う。

f:id:poppon555:20180115222435j:plain
裁断済書籍

Promiseでループ処理

javascriptで通信処理を記述するときに使用するPromiseオブジェクト。
だいぶ慣れてきたが、ループ処理についてはよくわからず立ち止まってしまったので解決を考えてみたいと思う。

満たしたい要件

  1. ループ内の処理はPromiseを返却する
  2. ループ回数を指定できる
  3. ループ内処理に、異なるパラメータを渡せる(1)
  4. ループ内でエラーが起きた場合、catchできる

単純にループを

まず1,2を満たすコードを書いてみる。

// ループ内処理
// @param {Number} i - ループカウンター
function action(i) {
  return new Promise( (res, rej) => {
    setTimeout( () => {
      console.log(i + '回目です')
      res();
    },500)
  })
}

// ループ関数
// @param {Function} fn - 個々の処理
// @param {Number} i - カウント初期値
// @param {Number} end - カウント終了値
function loop( fn, i, end) {

  return fn(i)
  .then( () => {
    if ( i < end ) {
      return loop( fn, i+1, end)
    }
    else {
      return Promise.resolve('end');
    }
  })

}

// 実行
loop(action, 0, 3 );

// console
< 1回目です
< 2回目です
< 3回目です
< 4回目です
< end

ループ内処理では、setTimeoutを使用して擬似的に遅延させてPromiseを返却している。
Promiseでのループ関数のポイントは再帰処理だと思ってる。ループ内処理を呼び出して、thenでPromiseをチェーンさせる2。then内で引数の値からloopを終了させるかどうか判定し、続けるならばloopを再帰的に呼び出し、終了ならばPromiseを返すようにした。

エラー処理の追加

続いて、ループない処理でエラーが生じた場合それ以降の処理を中断するように改良する。

function action(i) {
  return new Promise( (res, rej) => {
    setTimeout( () => {
      // 修正
      if ( i === 5 ) {
        rej('5は処理できません');
      }
      else {
        console.log(i + '回目です')
        res();
      }
    },500)
  })
}

function loop( fn, i, end) {

  return fn(i)
  .then( () => {
    if ( i < end ) {
      return loop( fn, i+1, end)
    }
    else {
      return Promise.resolve('end');
    }
  })
  // 追加
  .catch( (err) => {
    console.log(err);
  })

}

// 実行
loop( action, 0, 6);
// console
< 0回目です
< 1回目です
< 2回目です
< 3回目です
< 4回目です
< 5回は処理できません

ループ内処理では、5だとrejectするように擬似的に設定。 ループ関数では、最後にcatchを追加するだけでよい。

任意のパラメータ指定

最後に3.の指定した任意のパラメータを実行させるように改良してみる

// ループ内処理
// 引数を追加
function action(i, second) {
  return new Promise( (res, rej) => {
    setTimeout( () => {
      if ( i === 5 ) {
        rej('5回は処理できません');
      }
      else {
      console.log(i + '回目です');
      // 追加
      console.log('パラメータ合計:' +  Number(i + second) ); 
      res();
      }
    },500)
  })
}

// ループ関数
// params引数を追加
function loop( fn, params, i, end) {

  // applyを使って、個々のparamを渡す
  return fn.apply(null, params[i])
  .then( () => {
    if ( i < end ) {
      return loop( fn, params, i+1, end)
    }
    else {
      return Promise.resolve('end');
    }
  })
  .catch( (err) => {
    console.log(err);
  })

}

// 個々のパラメータを用意
var params = [
  [1,10],
  [2,20],
  [3,30],
  [4,40]
]

// 実行
loop(action, params, 0, 3 )
// console
< 1回目です
< パラメータ合計:11
< 2回目です
< パラメータ合計:22
< 3回目です
< パラメータ合計:33
< 4回目です
< パラメータ合計:44
< end

修正はループ関数だけでよい。
むしろ、ループを回すために内部処理に微修正させるとかしたくない。 fnを直接呼び出すのではなく、applyメソッドでparamsをfnの引数として 利用するように指定する。applyめっちゃ便利。


  1. ループ内処理は同じ処理である

  2. チェーンさせて処理をつなげるため、whileやfor文ではループ処理ができないのだと思っている。