概念モデルで実装方針の認識合わせ

経緯

すでに実装済みのスケジュール適用機能を改修し、分割適用を行うという案件があったのですが、レビューを行っているとスケジュール適用のための予定と実際のスケジュールの同期がよく取れていないと言うことがありました。
なれた製品での開発だったので設計レビューせず実装してもらったのをレビューしたのですが、そもそもの方針の時点で共有してもらってから出ないと手戻りが発生する可能性があるので、修正の認識合わせとして概念モデルレベルでのオブジェクトの責務割り当てが使えそうなので振り返ってみたいと思います。

改修内容(簡略) 詳しくは書けないので、大分簡略化した既存仕様と修正後の仕様は以下のようになっています。

続きを読む

c++で関数型のパーサー作成

実用性は置いといてc++でmap, flatMapを利用したパーサーコンビネーターを作成していこうと思います。c++ではLL1パーサーを実装するのが相性が良さそうだと思うのですが、今回はパーサーコンビネータを使うのでPEGパーサーのようになるので書きやすいかどうか確認していきたいです。
実装は以下になります。

github.com

以下のscalaで実装されたパーサコンビネータを参考にしています。

github.com

概要

まずパーサークラスについて、以下のようにindexを受け取ったらパース結果を返す型として定義しておきます。

template <typename T>
class ParseSuccess {
 public:
  int index_;
  T value_;
  ParseSuccess(int index, T value) : index_(index), value_(value) {}
};

class ParseFailure {
 public:
  int index_;
  std::string message_;
  ParseFailure(int index, std::string message)
      : index_(index), message_(message) {}
};

template <typename T>
using ParseResult = std::variant<ParseSuccess<T>, ParseFailure>;

template <typename F>
using Parser = std::function<ParseResult<F>(int index)>

それからパーサーを利用し、パース結果を返すクラスとして以下のように定義しておきます。文字列はinput_でクラス内で保持し、parseの関数を呼び出すと言う流れです。

template <typename T>
class CCombinator {
 public:
  Parser<T> root_parser_;
  std::string input_;
  CCombinator(Parser<T> root_parser) : root_parser_(root_parser), input_("") {}

  Result<T> parse(std::string input) {
    input_ = input;
    auto parse_result = root_parser_(0);

    if (std::holds_alternative<ParseSuccess<T>>(parse_result)) {
      auto p_success = std::get<ParseSuccess<T>>(parse_result);
      return Success(p_success.value_);
    } else {
      auto p_failure = std::get<ParseFailure>(parse_result);
      return Failure(Location{0, p_failure.index_}, p_failure.message_);
    }
  }
~省略~

実際に利用する際は以下のようにCCombinatorを拡張して利用しています。

class ParseAAAorBBB : public CCombinator<std::string> {
 public:
  explicit ParseAAAorBBB() : CCombinator<std::string>(gen_parser()) {}

  Parser<std::string> gen_parser(void) {
    auto p1 = pstr("aaa");
    auto p2 = pstr("bbb");
    return orp(p1, p2);
  }
};

TEST(ccombinator, orp) {
  auto scombinator = ParseAAAorBBB();
  auto result = scombinator.parse("bbb");
  ASSERT_TRUE(std::holds_alternative<Success<std::string>>(result));
  auto result_success = std::get<Success<std::string>>(result);
  std::cout << result_success.value_ << std::endl;
}

基本的なパーサー

文字列パーサー

基本的なパーサーはCCombinatorに追加していきます。文字列のパーサーについては以下のように文字列を受け取ったらパース結果を吐き出す関数を返します。

  Parser<std::string> pstr(std::string literal) {
    return [this, literal](int index) {
      std::string input = input_.substr(index);
      int len = literal.size();
      if (input.size() >= len && input.substr(0, len) == literal) {
        return static_cast<ParseResult<std::string>>(
            ParseSuccess(index + len, literal));
      } else {
        return static_cast<ParseResult<std::string>>(
            ParseFailure(index, "expect " + literal));
      }
    };
  }

ORパーサー

ORパーサーについてはパーサーを2つ受け取ってパースに成功した方を有効にするパーサーを返します。

  template <typename V>
  Parser<V> orp(Parser<V> parser1, Parser<V> parser2) {
    return [this, parser1, parser2](int index) {
      auto parse_result1 = parser1(index);
      if (std::holds_alternative<ParseSuccess<V>>(parse_result1)) {
        return static_cast<ParseResult<V>>(
            std::get<ParseSuccess<V>>(parse_result1));
      } else {
        auto parse_result2 = parser2(index);
        if (std::holds_alternative<ParseSuccess<V>>(parse_result2)) {
          return static_cast<ParseResult<V>>(
              std::get<ParseSuccess<V>>(parse_result2));
        } else {
          auto p_failure = std::get<ParseFailure>(parse_result2);
          return static_cast<ParseResult<V>>(
              ParseFailure(p_failure.index_, p_failure.message_));
        }
      }
    };
  }

ANDパーサー

ANDパーサーではパーサーを2つ受け取って順番に2つのパーサーを実行するパーサーを返します。

  template <typename V, typename U>
  Parser<std::tuple<V, U>> andp(Parser<V> parser1, Parser<U> parser2) {
    return [this, parser1, parser2](int index) {
      auto parse_result1 = parser1(index);
      if (std::holds_alternative<ParseSuccess<V>>(parse_result1)) {
        auto parse_success1 = std::get<ParseSuccess<V>>(parse_result1);
        auto parse_result2 = parser2(parse_success1.index_);

        if (std::holds_alternative<ParseSuccess<V>>(parse_result2)) {
          auto parse_success2 = std::get<ParseSuccess<U>>(parse_result2);

          return static_cast<ParseResult<std::tuple<V, U>>>(ParseSuccess(
              parse_success2.index_,
              std::tuple<V, U>(parse_success1.value_, parse_success2.value_)));
        } else {
          auto p_failure2 = std::get<ParseFailure>(parse_result2);
          return static_cast<ParseResult<std::tuple<V, U>>>(
              ParseFailure(p_failure2.index_, p_failure2.message_));
        }
      } else {
        auto p_failure1 = std::get<ParseFailure>(parse_result1);
        return static_cast<ParseResult<std::tuple<V, U>>>(
            ParseFailure(p_failure1.index_, p_failure1.message_));
      }
    };
  }

繰り返しパーサー

パーサーを受け取って繰り返しパースを実行する(0回以上パース成功で成功とする)パーサーとして以下のように定義しています。

  template <typename V>
  Parser<std::vector<V>> repeat0(Parser<V> parser) {
    return [this, parser](int index) {
      std::vector<V> value;
      while (true) {
        auto parse_result = parser(index);
        if (std::holds_alternative<ParseSuccess<V>>(parse_result)) {
          auto parse_success = std::get<ParseSuccess<V>>(parse_result);
          value.push_back(parse_success.value_);
          if (index == parse_success.index_) {
            break;
          } else {
            index = parse_success.index_;
          }
        } else {
          break;
        }
      }
      return static_cast<ParseResult<std::vector<V>>>(
          ParseSuccess(index, value));
    };
  }

関数型パーサー

map

パース結果の値を変換するmap処理は以下のようにtemplateを使って実装します。

  template <typename S, typename V>
  Parser<V> map(Parser<S> parser, std::function<V(S)> f) {
    return [this, parser, f](int index) {
      auto parse_result = parser(index);
      if (std::holds_alternative<ParseSuccess<S>>(parse_result)) {
        auto p_success = std::get<ParseSuccess<S>>(parse_result);
        return static_cast<ParseResult<V>>(
            ParseSuccess(p_success.index_, f(p_success.value_)));
      } else {
        auto p_failure = std::get<ParseFailure>(parse_result);
        return static_cast<ParseResult<V>>(
            ParseFailure(p_failure.index_, p_failure.message_));
      }
    };
  }

flatMap

それからflatMapを以下のように実装します。

  template <typename S, typename V>
  Parser<V> flatMap(Parser<S> parser, std::function<Parser<V>(S)> f) {
    return [this, parser, f](int index) {
      auto parse_result = parser(index);
      if (std::holds_alternative<ParseSuccess<S>>(parse_result)) {
        auto p_success = std::get<ParseSuccess<S>>(parse_result);
        return static_cast<ParseResult<V>>(
            f(p_success.value_)(p_success.index_));
      } else {
        auto p_failure = std::get<ParseFailure>(parse_result);
        return static_cast<ParseResult<V>>(
            ParseFailure(p_failure.index_, p_failure.message_));
      }
    };
  }

使用感

jsonの文字列をパースする場合、参考元のscalaの実装の場合は以下のように簡単にかけています。

    lazy val string: Parser[String] = rule{for {
      _ <- $("\"")
      contents <- ($("\\") ~ any ^^ { case _ ~ ch => escape(ch).toString} | except('"')).*
      _ <- $("\"").l("double quote")
      _ <- DefaultSpace.*
    } yield contents.mkString}

    lazy val jstring: Parser[JValue] = rule(string ^^ {v => JString(v)})

エスケープ処理が含まれていて複雑かもしれないので、除外すると以下のようになります。

    lazy val string: Parser[String] = rule{for {
      _ <- $("\"")
      contents <- except('"').*
      _ <- $("\"").l("double quote")
      _ <- DefaultSpace.*
    } yield contents.mkString}

    lazy val jstring: Parser[JValue] = rule(string ^^ {v => JString(v)})

今回のc++の実装では以下のようになりましたが、for式のようなシンタックスシュガーがないので直接map, flatMapを呼び出すしかないので以下のようになりました。flatMapを呼び出すための関数を定義しておくしかないので、大分わかりづらくなることが分かります。

Parser<JString*> JsonCombinator::gen_jstring_prser(void) {
  auto p1 = pstr("\"");
  auto p2 = repeat0(except('"'));

  std::function<Parser<std::string>(std::string)> f1 = [&, p2](std::string a) {
    std::function<std::string(std::vector<std::string>)> f =
        [](std::vector<std::string> list) {
          std::string ret = "";
          for (std::string b : list) {
            ret += b;
          }
          return ret;
        };
    return map(p2, f);
  };
  std::function<Parser<std::string>(std::string)> f2 = [&, p1](std::string a) {
    std::function<std::string(std::string)> f = [a](std::string b) {
      return a;
    };
    return map(p1, f);
  };
  std::function<JString*(std::string)> f3 = [](std::string a) {
    return new JString(std::move(a));
  };
  return map(flatMap(flatMap(p1, f1), f2), f3);
}

開発環境構築からのReact

React基礎

開発環境構築(Webpack)

Reactの開発をするにあたって、まずは開発環境の構築を進めていきたいと思います。利用するビルドツールですがよく使われていると思うのでWebpackを使っていきます。

以下の記事では最近のビルドツールについて説明されています。
https://tech-blog.rakus.co.jp/entry/20210930/frontend

そもそもWebpackとは公式ページのコンセプト に説明がありますが、ビルド時にエントリーのjsファイルを起点に依存関係を読み取って一つにまとめたりします。Webpackが出た当初はES5, ES6で書かれたjsがブラウザ上でそのまま動かなかったのでWebpack上でBabel(トランスパイラ)を利用してES5,ES6に対応できていないブラウザでも動くようにしてきました。 ただ、現在のブラウザの対応状況を見てみると主要なブラウザでは末尾再帰最適化以外の機能に対応できていることが分かります。
http://kangax.github.io/compat-table/es6/

Webpackではエントリーポイントのjsを起点にバンドルして一つにまとめるという特徴がありますが、後発のビルドツールであるviteではESModuleといって別のjsファイルをimportする機能がブラウザ側にある前提でバンドルを行わずビルド時間の面で改善されているようです。
https://ja.vitejs.dev/guide/why.html#%E5%95%8F%E9%A1%8C%E7%82%B9

プロジェクト作成

ビルドまで

では公式のページを参考にプロジェクト作成から進めていきます。以下のコマンドを実行しpackage.jsonを作成します。

mkdir react-tutorial
cd react-tutorial
npm init -y
npm install webpack webpack-cli --save-dev

npm install webpack webpack-cli --save-devにて対象のプロジェクトのnode_modules以下のディレクトリにwebpackの依存が追加されています、package.jsonにも開発用ライブラリとしてdevDependenciesに追加されていることが確認できます。gitでnpmプロジェクトをクローンしてきた時はプロジェクトの依存を解決するためにnpm installを実行しpackage.jsonに記述された依存をダウンロードしてきます。

次に src/index.js を作成します。既存はlodashを使用していましたがES6だとArray.prototype.join()が使えて不要なので書き換えます。

function component() {
  const element = document.createElement('div');
  element.innerHTML = ['Hello', 'webpack'].join(' ');
  return element;
}

document.body.appendChild(component());

それからtemplateのhtmlも追加しておきたいので、以下のindex.htmlも追加しておきます。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>webpack App</title>
  </head>
  <body>
  </body>
</html>

Webpack上でhtml用のプラグインを使いたいので以下のコマンドを実行します。

npm i -D html-webpack-plugin

それからwebpackの設定ファイルを追加したいので以下のwebpack.config.jsを追加します。

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
 mode: 'development',
 entry: {
   index: './src/index.js',
 },
 plugins: [
    new HtmlWebpackPlugin({
      template: 'index.html',
      filename: '[name].html'
    }),
  ],
  output: {
   filename: '[name].bundle.js',
   path: path.resolve(__dirname, 'dist'),
   clean: true,
  },
};

entryにて起点となるjsを指定しています、それからhtml-webpack-pluginでテンプレートのhtmlを指定し、outputで出力先の設定をしています。

これでビルドの準備が整ったのでnpx webpack --config webpack.config.jsを実行しビルドします、configはwebpack.config.jsがデフォルトなので省略しても問題ありません。distのindex.htmlをブラウザで開いたらビルドできていることが確認できるかと思います。

[arimura@MacMini]$ npx webpack --config webpack.config.js
asset index.bundle.js 1.37 KiB [emitted] (name: index)
asset index.html 173 bytes [emitted]
./src/index.js 186 bytes [built] [code generated]
webpack 5.73.0 compiled successfully in 57 ms

npxはnpmのスクリプトを直接実行するためのコマンドなのですがいpackage.jsonに以下のようにbuildを追加しnpm run buildを実行するのと同じになります。

~省略~
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack --config webpack.config.js"
  },
~省略~

webpack-dev-server導入

開発時の効率アップのためwebpack-dev-serverを利用します、以下のコマンドでwebpack-dev-serverの依存を追加します。

npm install -D webpack-dev-server

それからwebpack.config.jsにdevServerを追加します。

~省略~
module.exports = {
  mode: 'development',
  devServer: {
    hot: true,
    compress: true,
    port: 9000,
    open: true,
  },
  entry: {
    index: './src/index.js',
  },
~省略~

これでwebpack-dev-serverの確認ができるのでnpx webpack serve --config webpack.config.jsを実行し、ブラウザが開いてビルド結果がみれたかと思います。hot: trueでHot Module Replacementに対応しているので、index.jsを修正することで自動でブラウザの表示も切り替わります。

React

hello world

Reactのプロジェクト作成にはCreate React Appが使われているかと思うのですが、今回は自前でビルド環境を作っていきたいと思います。

まずReactの依存をpackage.jsonに追加するので以下のコマンドを実行します、ビルドの依存ではなくアプリの依存になるので--save または -Sで依存を追加します。

npm install -S react react-dom

それからjsxのビルドのためにbabelも追加します、@babel/preset-reactを追加するとその依存で@babel/plugin-transfrom-jsxも入るのでこれでjsxもビルドできるようになります。

npm install -D babel-loader @babel/core @babel/preset-env @babel/preset-react

そもそもjsxとは何かですが、Javascript側で利用できるDom定義のシンタックスシュガーで素のJSでDocument.createElement()を直接呼び出したり、ReactのReact.createElement()ではDomの全体的な構造が把握しずらいのでJSXにて通常のhtmlのように記述することができます。例えばjsxを利用すると以下のようにdomの定義を行うことができます。

const element = (
  <div>
    <h1>Hello!</h1>
    <h2>Good to see you here.</h2>
  </div>
);

webpack上でbabelを利用できるように以下のようにmoduleにbabel-loaderを追加します、それからエントリーのjsを./src/index.jsxに変更しておきます。

~省略~
  resolve: { extensions: ["*", ".js", ".jsx"] },
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        loader: "babel-loader",
      },
    ]
  },

  entry: {
    index: './src/index.jsx',
  },
~省略~

それから@babel/preset-reactを利用できるように以下の.babelrcを追加します。

{
    "presets": [
        "@babel/preset-env",
        "@babel/preset-react"
    ]
}

次にReactのhello worldを書いていこうと思います、今回はReact18が依存に追加されていたのでsrc/index.jsxに以下のファイルを追加します。

import * as React from 'react';
import * as ReactDOMClient from 'react-dom/client';

const root = ReactDOMClient.createRoot(document.getElementById('root'));
root.render(<h1>Hello, world!</h1>);

const root = ReactDOMClient.createRoot(document.getElementById('root'));の部分はReactのルートのエレメントを作成し、html上にあるdocument.getElementById('root')で取得できるエレメントに対してマウントする動きになります。それからroot.render(<h1>Hello, world!</h1>);でマウント先に書き込みを行なっています。templateのhtmlにはrootのdomがなかったので以下のようにindex.htmlを修正します。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>react App</title>
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>

これで npx webpack serveを実行するとブラウザ上にHello, world!が表示されるかと思います。

Reactで開発しやすくするようにSourceMapを追加します、これでビルド後に開発者ツールのソースで確認した時にソースがそのまま表示されるようになるかと思います。

~省略~
module.exports = {
  mode: 'development',
  devtool: 'source-map',
  devServer: {
~省略~

チュートリアル

Reactのhello worldまでできたので次はチュートリアルのコードを動かしたいと思います。 https://ja.reactjs.org/tutorial/tutorial.html

先にスタイルを読み込めるようにしたいと思うので、以下のコマンドでcss-loaderを依存に追加しておきます。

npm install --save-dev style-loader css-loader

それからwebpack.config.jsを修正しcss-loader, style-loaderを追加します。

~省略~
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        loader: "babel-loader",
      },
      {
        test: /\.css$/i,
        use: ["style-loader", "css-loader"],
      },
    ]
  },
~省略~

次に読み込み対象のcssをmain.cssで追加します。

body {
    font: 14px "Century Gothic", Futura, sans-serif;
    margin: 20px;
  }
  
  ol, ul {
    padding-left: 30px;
  }
  
  .board-row:after {
    clear: both;
    content: "";
    display: table;
  }
  
  .status {
    margin-bottom: 10px;
  }
  
  .square {
    background: #fff;
    border: 1px solid #999;
    float: left;
    font-size: 24px;
    font-weight: bold;
    line-height: 34px;
    height: 34px;
    margin-right: -1px;
    margin-top: -1px;
    padding: 0;
    text-align: center;
    width: 34px;
  }
  
  .square:focus {
    outline: none;
  }
  
  .kbd-navigation .square:focus {
    background: #ddd;
  }
  
  .game {
    display: flex;
    flex-direction: row;
  }
  
  .game-info {
    margin-left: 20px;
  }

それからindex.jsx側でcssを読み込ませたら際ビルドでスタイルが適用されたことが確認できるかと思います。

import * as React from 'react';
import * as ReactDOMClient from 'react-dom/client';

import "../main.css";

const root = ReactDOMClient.createRoot(document.getElementById('root'));
root.render(<h1>Hello, world!</h1>);

次にindex.jsxを以下のように書き換えることでチュートリアルのコードが動きます。

import * as React from 'react';
import * as ReactDOMClient from 'react-dom/client';

import "../main.css";

function Square(props) {
    return (
        <button className="square" onClick={props.onClick}>
            {props.value}
        </button>
    );
}

class Board extends React.Component {
    renderSquare(i) {
        return (
            <Square
                value={this.props.squares[i]}
                onClick={() => this.props.onClick(i)}
            />
        );
    }

    render() {
        return (
            <div>
                <div className="board-row">
                    {this.renderSquare(0)}
                    {this.renderSquare(1)}
                    {this.renderSquare(2)}
                </div>
                <div className="board-row">
                    {this.renderSquare(3)}
                    {this.renderSquare(4)}
                    {this.renderSquare(5)}
                </div>
                <div className="board-row">
                    {this.renderSquare(6)}
                    {this.renderSquare(7)}
                    {this.renderSquare(8)}
                </div>
            </div>
        );
    }
}

class Game extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            history: [
                {
                    squares: Array(9).fill(null)
                }
            ],
            stepNumber: 0,
            xIsNext: true
        };
    }

    handleClick(i) {
        const history = this.state.history.slice(0, this.state.stepNumber + 1);
        const current = history[history.length - 1];
        const squares = current.squares.slice();
        if (calculateWinner(squares) || squares[i]) {
            return;
        }
        squares[i] = this.state.xIsNext ? "X" : "O";
        this.setState({
            history: history.concat([
                {
                    squares: squares
                }
            ]),
            stepNumber: history.length,
            xIsNext: !this.state.xIsNext
        });
    }

    jumpTo(step) {
        this.setState({
            stepNumber: step,
            xIsNext: (step % 2) === 0
        });
    }

    render() {
        const history = this.state.history;
        const current = history[this.state.stepNumber];
        const winner = calculateWinner(current.squares);

        const moves = history.map((step, move) => {
            const desc = move ?
                'Go to move #' + move :
                'Go to game start';
            return (
                <li key={move}>
                    <button onClick={() => this.jumpTo(move)}>{desc}</button>
                </li>
            );
        });

        let status;
        if (winner) {
            status = "Winner: " + winner;
        } else {
            status = "Next player: " + (this.state.xIsNext ? "X" : "O");
        }

        return (
            <div className="game">
                <div className="game-board">
                    <Board
                        squares={current.squares}
                        onClick={i => this.handleClick(i)}
                    />
                </div>
                <div className="game-info">
                    <div>{status}</div>
                    <ol>{moves}</ol>
                </div>
            </div>
        );
    }
}

// ========================================

const root = ReactDOMClient.createRoot(document.getElementById('root'));
root.render(<Game />);

function calculateWinner(squares) {
    const lines = [
        [0, 1, 2],
        [3, 4, 5],
        [6, 7, 8],
        [0, 3, 6],
        [1, 4, 7],
        [2, 5, 8],
        [0, 4, 8],
        [2, 4, 6]
    ];
    for (let i = 0; i < lines.length; i++) {
        const [a, b, c] = lines[i];
        if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
            return squares[a];
        }
    }
    return null;
}

Chromeでの開発では以下を使うことでコンポーネントのpropsやstateが確認しやすくなります。 https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?hl=ja

開発者ツールで確認すると以下のようなコンポーネントの構造だというのが分かるかと思います。

  • Game. ← state管理
    • Board. ← propsとして表示
      • Squrare. ← propsとして表示
      • Squrare. ← propsとして表示
      • ~省略~

コンポーネントにはstateとpropsという2種類のデータがあるのですが、stateは生成したコンポーネントが管理対象とするデータでpropsは親のコンポーネントから受け取った表示対象のデータになります。今回のマルバツの選択やステップの情報は一番親のGameコンポーネントで生成して管理し、それから更新があれば子コンポーネントの描画に反映されます。もう少し具体的に言うとGameコンポーネント内でsetStateでステートを更新するとGameコンポーネントのrenderが呼び出され、さらに更新したstateを子コンポーネントはpropsとして受けとって再描画が行われます。なのでrender関数内で不要にオブジェクトを生成しないようにする方がパフォーマンスは良くなるらしいです。(render内の処理でarrow関数使うと楽ですが、パフォーマンスを最適化したいなら コンポーネントに関数を渡す – React使わない方が良いらしいです。) パフォーマンスについてはいかにもまとめられています。

ステートレスなコンポーネントによるReactのパフォーマンス最適化 – WPJ.
Reactのパフォーマンスチューニングの歴史をまとめてみた | blog.ojisan.io

state とライフサイクル – Reactにあるように、データの流れは親から子になるので、コンポーネント毎に管理対象の決めておいて、そこでstateとして生成する必要があります。今回であれば一番親のGameコンポーネントでstateを生成し、子コンポーネント側はstate更新の操作が検知できたら親コンポーネントから受け取ったアクションを呼び出し、親側でstateを更新し親から子へ再描画が伝搬しています。パフォーマンス最適化であったようにコンポーネントが増えてくると親に変更があっても、再描画が必要でない場合がありそう言う場合はPureComponentを使ったり、shouldComponentUpdateを書いたりして無駄な再描画は減らせるらしいです。

statelessにして再利用性を高くする

チュートリアルのコードでもstateの管理は親コンポーネントで行い、子コンポーネントをpropsとして表示でしたが、以下の公式ではstatelessにすることで再利用性が高くなると言うのが確認しやすいのでみてみます。 state のリフトアップ – React

実装自体は以下のようになります。

const scaleNames = {
    c: 'Celsius',
    f: 'Fahrenheit'
};

function toCelsius(fahrenheit) {
    return (fahrenheit - 32) * 5 / 9;
}

function toFahrenheit(celsius) {
    return (celsius * 9 / 5) + 32;
}

function tryConvert(temperature, convert) {
    const input = parseFloat(temperature);
    if (Number.isNaN(input)) {
        return '';
    }
    const output = convert(input);
    const rounded = Math.round(output * 1000) / 1000;
    return rounded.toString();
}

function BoilingVerdict(props) {
    if (props.celsius >= 100) {
        return <p>The water would boil.</p>;
    }
    return <p>The water would not boil.</p>;
}

class TemperatureInput extends React.Component {
    constructor(props) {
        super(props);
    }

    handleChange = (e) => {
        this.props.onTemperatureChange(e.target.value);
    }

    render() {
        const temperature = this.props.temperature;
        const scale = this.props.scale;
        return (
            <fieldset>
                <legend>Enter temperature in {scaleNames[scale]}:</legend>
                <input value={temperature}
                    onChange={this.handleChange} />
            </fieldset>
        );
    }
}

class Calculator extends React.Component {
    constructor(props) {
        super(props);
        this.state = { temperature: '', scale: 'c' };
    }

    handleCelsiusChange = (temperature) => {
        this.setState({ scale: 'c', temperature });
    }

    handleFahrenheitChange = (temperature) => {
        this.setState({ scale: 'f', temperature });
    }

    render() {
        const scale = this.state.scale;
        const temperature = this.state.temperature;
        const celsius = scale === 'f' ? tryConvert(temperature, toCelsius) : temperature;
        const fahrenheit = scale === 'c' ? tryConvert(temperature, toFahrenheit) : temperature;

        return (
            <div>
                <TemperatureInput
                    scale="c"
                    temperature={celsius}
                    onTemperatureChange={this.handleCelsiusChange} />
                <TemperatureInput
                    scale="f"
                    temperature={fahrenheit}
                    onTemperatureChange={this.handleFahrenheitChange} />
                <BoilingVerdict
                    celsius={parseFloat(celsius)} />
            </div>
        );
    }
}

ここではTemperatureInputが単純にテキストの表示のみでデータの管理自体は親に任せることで摂氏、華氏用のテキストインプットとして利用できています。性能改善の

C++でProtocol Bufferを試してみた

Protocol Bufferとはシリアライズ、デシリアライズ対象のデータ構造を定義したインタフェース定義言語になります。言語に依存しないのでgRPCなどの通信でも使われていまして、Googleのオリジナルの実装にはC++, Java, Pythonなどがあります。

developers.google.com

言語に依存しないデータ構造等してJsonもありますが、Jsonに比べるとシンプルさとパフォーマンスを目的においているようですが、文字列型が多い場合はJsonの方がパフォーマンスが良かったりするので、どちらのパフォーマンスが良いかはデータ定義にも依存するようです。

medium.com

今回はGoogleのドキュメントにあるC++チュートリアルのデータ定義を用いてBoost::Asioでサーバ、クライアント間でシリアライズ、デシリアライズの確認をしてみます。

続きを読む

JavaのNettyとC++のBoost.AsioでTCP通信してみた

JavaのNettyとC++のBoost.AsioでTCP接続を試してみたときのメモになります。

Nettyとは

Netty は非同期のイベント駆動型ネットワークアプリケーションフレームワークで、FTP,SMTP,HTTPなどのレガシープロトコルから得られた経験をもとに開発の容易さ、パフォーマンス、安定性、柔軟性の実現に成功しているらしいです。

netty.io

続きを読む

Javaでサイズ付きMapを比べてみた

Javaで上限サイズ付きのMapが実装が必要でしたのでいくつか比べてみました。具体的にはLinkedHashMapとCaffeineを使ったので、その実装内容を見たうえで結果を比較していきたいと思います。

続きを読む