【blog.hellorusk.net に移行しました】

旧・技術メモ(blog.hellorusk.net に移行しました)

Monadic Memoization in Python

関数の再帰呼び出しにおいて値を保持しておくことで計算が高速化されるメモ化という手法が知られています.
例えば以下のようにして80番目のフィボナッチ数が高速に求まります.

d = [0 for i in range(100)]

def fib(n):
    if n <= 1:
        return n
    if d[n] != 0:
        return d[n]

    d[n] = fib(n-1) + fib(n-2)
    return d[n]

print(fib(80)) // 23416728348467685

実は上記の d のようなグローバル変数をあらかじめ用意せず, メモの部分を隠蔽しながら計算を実行することができます. State モナドの考え方を使うのです.
学科の課題でメモ化の隠蔽を OCaml でやったのが面白かったので, Python のような動的型付け言語でも何とかできないかなーと考えてやってみました.
もちろん, Python で型アノテーションを使ってモナドを定式化するというようなことは流石にしません. あくまで考え方を使うというだけです.


ではやっていきましょう.
そもそも State モナド

state :: (s -> (a, s)) -> State s a

こういう形状をしています. ある状態をもらって, 値と新しい状態のタプルを返す関数です.
今回はこの「状態」にメモの部分(値の配列)が相当します. なので, 雰囲気としては

type int m = [int] -> (int, [int])

なるモナド int m があると考えてみてください(もちろんこんな式を直接 Python のコードにはかけません).


まず, モナド則として必ず用意されるべき2つの関数をつくります.

1つ目は return: int -> int m で, 副作用を起こさずモナドを返します. これは

def return_(x):
    return lambda t: (x, t)

こんな風に無名関数を返してやればよいです(return予約語なので return_ で).


2つ目は副作用を繋げてモナドを返す bind: int m -> (int -> int m) -> int m です.
これは OCaml のような関数型言語だととてもシンプルに書けますが, 普通のプログラミング言語だと割とつらいです. というのも, int m というモナドを一つ目の引数に取り, int -> int m という, 引数をとってモナドを返す関数を二つ目の引数に取り, そのモナド自体も関数なのですから.
実際こんな形状をしています.

def bind(x):
    def bind_sub(f):
        def bind_sub2(t):
            tup = x(t)
            return f(tup[0])(tup[1])
        return bind_sub2
    return bind_sub

この関数のお気持ちはどうなっているのかというと, メモ t を一つ目のモナドが受け取って, 値と状態を返し, それを二つ目の関数 f にぶっこんで得た結果を返すので, 全体としては, 1つ目の引数の副作用と2つ目の引数の副作用を繋げた形になっているということです. (自分でも説明になっていない...)
この bind は, HaskellOCaml のような関数の中置記法が使える言語では [1つ目の引数] >>= [2つ目の引数] というように書け, 副作用を繋げている感じがあってよいのですが, Python ではそういうことはしづらいので残念です(流石に演算子オーバーロードをやるのはちょっと...)

実装で lambda 式をあえて使わなかったのは, 最初 lambda 式を使って試してみたところ式の評価がうまくいかず実装に失敗してしまったのと, lambda 式の中で局所変数が定義できず書くのに不便だったのが理由です.
JavaScript のアロー関数だったらもっと融通が利いたのかなーという気がします.


次に, 今回のメモ化の隠蔽の中で核心にあたる memo: (int -> int m) -> int -> int m をつくります.

def memo(f):
    def memo_sub(n):
        def memo_sub2(t):
            if t[n] == 0:
                tup = f(n)(t)
                t[n] = tup[0]
            return t[n], t
        return memo_sub2
    return memo_sub

これは, 引数としてメモ t が与えられた時に, メモにデータがなかったら関数 f を評価してメモを行います.


また, メモの初期値を与えてメモ化を走らせ, 最終的な結果を得る run_memo: int m -> int をつくります.

def run_memo(m):
    tup = m([0 for i in range(100)])
    return tup[0]

以上をもって, 構成パーツの準備が完了しました.


そして, フィボナッチ数を計算する関数 fib: int -> int m がやっと登場します.

def fib(n):
    if n <= 1:
        return return_(n)
    else:
        def fib_sub(x):
            def fib_sub2(y):
                return return_(x + y)
            return bind(memo(fib)(n-1))(fib_sub2)
        return bind(memo(fib)(n-2))(fib_sub)

形がエグいですね. 中置記法 >>= と無名関数を仮に使ってみれば, 下の5行は

(memo fib (n-2)) >>= (fun x ->
(memo fib (n-1)) >>= (fun y ->
   return (x + y)))

という感じになります.

ところで, これはフィボナッチ数を計算する関数ですが, フィボナッチ数を返す関数ではありません!
例えるなら, この fib東北新幹線東海道新幹線山陽新幹線を繋いだだけであって, 実際に青森に列車を用意していないので, 博多に着いた時の乗客の様子は分からないのです. だからこそ, 先ほどの run_memo が必要なのですね.


全体像は以下のようになります.

def return_(x):
    return lambda t: (x, t)

def bind(x):
    def bind_sub(f):
        def bind_sub2(t):
            tup = x(t)
            return f(tup[0])(tup[1])
        return bind_sub2
    return bind_sub

def memo(f):
    def memo_sub(n):
        def memo_sub2(t):
            if t[n] == 0:
                tup = f(n)(t)
                t[n] = tup[0]
            return t[n], t
        return memo_sub2
    return memo_sub

def run_memo(m):
    tup = m([0 for i in range(100)])
    return tup[0]

def fib(n):
    if n <= 1:
        return return_(n)
    else:
        def fib_sub(x):
            def fib_sub2(y):
                return return_(x + y)
            return bind(memo(fib)(n-1))(fib_sub2)
        return bind(memo(fib)(n-2))(fib_sub)

https://gist.github.com/7ma7X/ad8b56f6ca6781bcf742dfc8b070bc1d

そして

print(run_memo(fib(80))) // 23416728348467685

このように値を求めることができました.



メモ化の隠蔽を Python でやってみて思ったのは, やはり関数型言語はすごいということです.
State モナドは, 実態としては一つの関数です. なので, Python のような言語でこのモナドを使うときは, ネストした関数を意識して実装する必要があり難しいです. 一方 OCaml のような関数型言語では(学科の課題の解答になるのでここには書きませんが)非常に綺麗な形で書けます. 関数を型によって抽象化することによって, 関数を関数として意識しなくても実装ができるのがとても楽なのです.
というわけで, 関数型言語のありがたみを分かるためには, こんな風に, 関数型言語でない言語でモナドを作ってみるのがいいんじゃないかなーと思うのでありました.

OCaml と TypeScript の比較に見るユーザ定義型

大学の授業の一つで OCaml という言語を今書いていて, ユーザ定義型について整理して学ぶ機会があったので, TypeScript と絡めてメモしてみます.


レコード型

レコードは組の各要素に名前が付いたもの.
OCaml での定義は type 型名 = {フィールド名: 型; ...}

OCaml だと, 例えば

type complex = { r: float; i: float };;

let add { r = a; i = b } { r = c; i = d } =
  { r = a +. c; i = b +. d };;
(* val add : complex -> complex -> complex = <fun> *)

add { r = 3.; i = 4. } { r = 5.; i = 6. };;
(* complex = {r = 8.; i = 10.} *)

こんな感じで, 複素数の足し算みたいなことができますね.

TypeScript だとオブジェクトに型名を付けるというのが一番近い形になると思います.

type complex = { r: number, i: number };

const add = (p: complex, q: complex): complex => {
  return { r: p.r + q.r, i: p.i + q.i };
}

console.log(add({r: 3, i: 4}, {r: 5, i: 6}));
// { r: 8, i: 10 }

TypeScript 独自の interface でも同じことができると思います.


ヴァリアント型

何種類かの値のうち一つをとる値であり, 型安全な操作ができます.
OCaml での定義は type 型名 = タグ1 of 型1 | タグ2 of 型2 ...

例えば

type value = VInt of int | VBool of bool;;

let string_of_value x =
  match x with
  | VInt i  -> string_of_int (2 * i)
  | VBool b -> string_of_bool (not b);;
(* val string_of_value : value -> string = <fun> *)

string_of_value (VInt 2);; (* string = "4" *)
string_of_value (VBool false);; (* string = "true" *)

こんな感じで, パターンマッチが使えます.

TypeScript の場合, union types が多分これに近いと思います.
パターンマッチの構文みたいなものはないです. typeof 演算子とかを使うと自動的に型を絞る判定をインラインではしてくれますが, パターンマッチと異なり, 全パターンを検査できているかをチェックするわけではないので, うーんという感じはします.

type value = number | boolean;

const string_of_value = (x: value): string => {
  if (typeof x === "number") {
    // この時点で x が number 型だと自動で判定される
    return String(2 * x);
  } else {
    // この時点で x が boolean 型だと自動で判定される
    return String(!x);
  }
}

console.log(string_of_value(2));  // 4
console.log(string_of_value(false));  // true


多相型

型によらず本質的に同じ動作をさせたい場合は, Polymorphism をやっていきましょう.
例えば OCaml で二分木を用意すると...

type 'a tree =
  | Leaf
  | Node of 'a * 'a tree * 'a tree;;

Node (3, Leaf, (Node (5, Leaf, Leaf)));;
(* int tree = Node (3, Leaf, Node (5, Leaf, Leaf)) *)
Node ("hoge", (Node ("fuga", Leaf, Leaf)), Leaf);;
(* string tree = Node ("hoge", Node ("fuga", Leaf, Leaf), Leaf) *)

色んな型の木が用意できていますね.


TypeScript の場合, 多相性を実現するのはジェネリクスです. ただ, OCaml の例のように, 型の中に直接多相性を持ち込むことはできないようです.
(ドキュメントを読むと, TypeScript でジェネリクスが使われるのはクラスやインターフェース, 関数の定義の中のようです).
例えば TypeScript のクラスでやると二分木の初期状態はこんな感じになるんじゃないかと...

class Tree<T> {
  value: T | undefined;
  left: Tree<T> | undefined;
  right: Tree<T> | undefined;
}

const btree = new Tree<string>(); // 最初の何もない木
console.log(btree.value); // undefined
btree.value = "test";
console.log(btree.value); // test

ここからうまく挿入や削除の関数もジェネリクスを使って書けそうです. 今度やってみようかなと思います. (5/1追記: やりました https://qiita.com/7ma7X/items/3f1e40ef142614d40210




初歩的な内容にとどまりましたが, ユーザ定義型について理解が深まったのでよかったです.

A Complete React Redux Tutorial for 2019 を読んで Redux に入門する

これまで名前しか聞いたことがなかった Redux をプライベートで知る必要が出てきたかもしれないので, 入門してみることにしました. ちょうど最近,

daveceddia.com

という記事が出たようなので, これを手がかりに, 自分用のメモを残してみます.



Redux を使うと何がうれしいのか

React では, ある親 Component から子 Component に状態を受け継ぐ時, 子 Component はそれを props として引き受けます.
例えば以下のように記述された 3 * 3 のマス目のようなものを考えてみてください.

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

function Board(props) {
  const renderSquare = i => (
    <Square value={i} onClick={() => props.onClick(i)} />
  );

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

function Game() {
  const handleClick = i => {
    console.log(i);
  };

  return (
    <div className="game">
      <div className="game-board">
        <Board onClick={i => handleClick(i)} />
      </div>
    </div>
  );
}

Square を定義する関数の中に props.value というのがあり, これはひと階層上の Board で示された value={i} を受け取っているということです.
また, Square の要素をクリックすると, props.onClick に伝わりますが, これはやはり同様に Board で示された onClick={() => props.onClick(i)} を呼び出します. そしてそれはさらに上の階層の GameonClick={i => handleClick(i)} を呼び出します.
このようにデータの受け渡しが Component の親子関係で行われます.
以上のような単純な例であれば問題無いですが, このデータの受け渡しの親子関係が, 曽祖父からやしゃごまで, と言えるくらい深い階層間で行われるとすると大変ですね. それに, その深い階層間の途中にある Component にとって, その受け渡されていくデータが無関係なものだとしたら, 無意味にそれらの Component の情報を増やすことになるので, ソフトウェアの設計上あまり良くないと言えるでしょう.


Redux はデータを一元的に管理することによってこの問題の解決を図ります.

2019 年現在なら Context API でもいいかも?

私自身は React 初心者なので, 経験に基づいた知見ではないことをご了承ください

Redux は強力なゆえに, 小規模な開発ではオーバーキルになってしまう可能性があります.
単に直接孫にデータを渡すだけであれば React ビルトインの Context API を使うのでも良さそうです.

Redux の導入

npm install redux react-redux

redux が本体, react-redux がそれを React と繋げるもの, と考えればよいでしょう.

Store

Redux の概念です. あらゆる state を管理するただ一つの倉庫です.

Reducer

Redux の概念です. ただの関数ですが, Store に保管された state を更新するという役目を担っています.
(ES6の Array にもありますが)畳み込み演算について知識があれば reduce という名前は聞いたことがあるでしょう.

[1, 2, 3, 4, 5].reduce((i, j) => i * j)  // 120

上記の計算を大仰に解説すると, 初期状態 1 に対して, 受け渡された 2 を掛け合わせて 2 に, 受け渡された 3 を掛け合わせて 6 に, 受け渡された 4 を掛け合わせて 24 に, 受け渡された 5 を掛け合わせて 120 になります. ここで重要なのは, 状態に対して, 受け渡された値を掛け合わせるという関数を適用し, 新たな状態を作ることをひたすら繰り返していることです.
Redux の Reducer にも同じことが言えます. つまり Reducer に状態を渡すことで, 新たな状態が生まれるというわけです.

Reducer について留意すべき点は

  • undefined を返さないようにする
  • 副作用を持たせないようにする

ことです.

Action と Dispatch

Redux の概念です.
まず, Action ですが, 実体としてはただのオブジェクトで, Reducer の中で行われる処理の識別子として考えればよいでしょう.
Action 単体では何も起こりません. これは Dispatch と合わせて使うものです. dispatch() という関数の引数に Action を指定してやることで, その Action にもとづいて store が操作されます.

Redux の例

ここまでの内容を整理するために, 具体的なコードの例を使いましょう.
簡単なカウンターを考えます. 初期値は0で, 1増やす操作や1減らす操作, リセットする操作などができればよさそうです.

import { createStore } from 'redux';

const initialState = {
  count: 0
};

function reducer(state = initialState, action) {
  switch (action.type) {
    case 'INCREMENT':
      return {
        count: state.count + 1
      };
    case 'DECREMENT':
      return {
        count: state.count - 1
      };
    case 'RESET':
      return {
        count: 0
      };
    default:
      return state;
  }
}

これが Reducer の一例です. これをもとに

const store = createStore(reducer)

このように Reducer を引数にとって Store を定義できます.
実際にこの Store に対して Action を Dispatch するには

store.dispatch({ type: 'INCREMENT' }); // count を 1 に
store.dispatch({ type: 'INCREMENT' }); // count を 2 に
store.dispatch({ type: 'DECREMENT' }); // count を 1 に
store.dispatch({ type: 'RESET' }); // count を 0 に

このように記述してやればよいです.
{ type: 'INCREMENT' } という Action が, switch 文の case 'INCREMENT': の部分に対応づけられているのが分かると思います.

state は Read-Only

Reducer に副作用を持たせないようにすることとも関連がありますが, Reducer は Pure な関数で無ければなりません.
つまり Reducer の内部で state.count = 0 だとか state.item.push(newItem) だとか, state を書き換える操作をしてはいけません. 新しい state の値を return すること, これが Reducer の役目です. state の書き換えは Action のレベルで管理しないといけないことなのです.

Provider

さて, ここまで Redux 本体の説明を行ってきました. ここからは, Redux をいかに React に結びつけるか, という react-redux の話が始まります.
Provider は Redux を操作できる React の Component です.

import { Provider } from 'react-redux';

const App = () => (
  <Provider store={store}>
    <Counter />
  </Provider>
);

このように Provider で包んでやると, Counter も, その子 Component も, その子 Component も ... 全ての子孫が Redux の (この場合は store という変数名で定義された)Store にアクセスする権限を持ちます.
ただし, これは権限を持っているというだけであり, 実際に操作するにはもうひと段階準備が必要です. それが以下で述べる connect です.

connect 事始め

connect を使っていかに React に Redux を導入するかを説明するために, 先ほど出てきた<Counter /> という Component の実装が具体的に以下のようになっているとしましょう.

class Counter extends React.Component {
  state = { count: 0 };

  increment = () => {
    this.setState({
      count: this.state.count + 1
    });
  };

  decrement = () => {
    this.setState({
      count: this.state.count - 1
    });
  };

  reset = () => {
    this.setState({
      count: 0
    });
  };

  render() {
    return (
      <div className="counter">
        <h2>Counter</h2>
        <div>
          <button onClick={this.decrement}>-</button>
          <span className="count">{this.state.count}</span>
          <button onClick={this.increment}>+</button>
        </div>
        <button onClick={this.reset}>×</button>
      </div>
    );
  }
}

export default Counter;

この Counter は, 現時点では, カウンターの値や, カウントを増やしたり減らしたりする関数を, 内部の実装として管理しています. これを Redux を使って外部に委ねたいです.
そのために何をするかというと, あらゆるものをpropsに変換します.
この変換を行うのが mapStateToPropsmapDispatchToProps です.

mapStateToProps

まずは, カウンターの値を Redux に委ねてみましょう.

import { connect } from "react-redux";

class Counter extends React.Component {
  increment = () => {
    /* TODO */
  };

  decrement = () => {
    /* TODO */
  };

  reset = () => {
    /* TODO */
  };

  render() {
    return (
      <div className="counter">
        <h2>Counter</h2>
        <div>
          <button onClick={this.decrement}>-</button>
          <span className="count">{this.props.count}</span>
          <button onClick={this.increment}>+</button>
        </div>
        <button onClick={this.reset}>×</button>
      </div>
    );
  }
}

function mapStateToProps(state) {
  return {
    count: state.count
  };
}

export default connect(mapStateToProps)(Counter);

これが connect の第一段階です. 何が変わったのか説明します.

  • this.state.countthis.props.count に変えた. これによりカウンターの値はもはや Counter 内部のものではなくなった.
  • export default Counter;export default connect(mapStateToProps)(Counter); に変えた. つまり, 生の Counter ではなく, connect という関数にラップされた Counter が使われるようになる.
  • connect という関数は第一引数に mapStateToProps という関数をとる. これは Component の state を props に変化させる. 具体的には return されるオブジェクトの key が props の名前に, オブジェクトの value が props の値になる.


上記コード例では /* TODO */ という部分が残っています. まだカウンターの値を変更すること, すなわち Redux の Store を操作することが出来ていないのです. 先に進みましょう.

Action Creator

復習になりますが, Redux の Store の操作の仕方は,

store.dispatch({ type: 'INCREMENT' });

このように Action を Dispatch するのでした.
ところで, .dispatch({ type: 'INCREMENT' }); の部分をそのまま毎回書くのは骨が折れます. そこで, 以下のような Action の定義ファイルを用意してやると良いでしょう. Action を一覧として管理できるというメリットもあります.

actions.js

export const INCREMENT = "INCREMENT";
export const DECREMENT = "DECREMENT";
export const RESET = "RESET";

export const increment = () => ({ type: INCREMENT });
export const decrement = () => ({ type: DECREMENT });
export const reset = () => ({ type: RESET });

この incrementdecrement など, Action を return する関数を Action Creator と呼びます. これを活用して, 先ほどの /* TODO */ の部分を埋めてみましょう.

import { increment, decrement, reset } from "./actions";

class Counter extends React.Component {
  increment = () => {
    this.props.dispatch(increment());
  };

  decrement = () => {
    this.props.dispatch(decrement());
  };

  reset = () => {
    this.props.dispatch(reset());
  };

これでカウンターの値を操作することまで出来るようになりました!
しかし, まだ出来ることがあります. この dispatch の部分も props に変えてしまおうというのが connect の第二段階 mapDispatchToProps です.

mapDispatchToProps

import { connect } from "react-redux";
import { increment, decrement, reset } from "./actions";

class Counter extends React.Component {
  increment = () => {
    this.props.increment();
  };

  decrement = () => {
    this.props.decrement();
  };

  reset = () => {
    this.props.reset();
  };

  render() {
    return (
      <div className="counter">
        <h2>Counter</h2>
        <div>
          <button onClick={this.decrement}>-</button>
          <span className="count">{this.props.count}</span>
          <button onClick={this.increment}>+</button>
        </div>
        <button onClick={this.reset}>×</button>
      </div>
    );
  }
}

function mapStateToProps(state) {
  return {
    count: state.count
  };
}

const mapDispatchToProps = {
  increment,
  decrement,
  reset
};

export default connect(mapStateToProps, mapDispatchToProps)(Counter);

mapDispatchToProps を使ってみました. 何が変わったでしょうか.

  • this.props.dispatch(increment());this.props.increment(); という風に書けるようになった.
  • その代わりに connect の第二引数に mapDispatchToProps というオブジェクトが渡されるようになった. これは実体としては, Action Creator の集合である.

以上で react-redux の基本概念の説明が完了しました.

Redux でも非同期処理がしたい!〜 Middleware の登場

ここからは全然理解が及んでいないので概略を述べるに留めます

Reducer は副作用を持ってはいけないと述べましたが, 現実のアプリケーションではデータを引っ張ったり通信したり...と非同期処理が噛みます. 汚い処理をどこかで担当しなければいけません.
Redux 自体には Middlleware という非同期処理を噛ませるレイヤーが用意されていますが, その記述に関しては統一的な手法が定まっているわけではなく, 何らかの独自のやり方で書けるようにせねばならないようです. redux-thunk とか redux-saga とかあるみたいです.

redux-thunk では Action Creator でオブジェクトではなく関数を返す, redux-saga では独立した非同期処理のプロセスを作る ... みたいな雰囲気しか私の理解は及んでいません. Redux は難しいですね...

その人が GitHub で普段どんな言語を使って開発しているかをすぐ見れるやつ作った

https://github-language-data.now.sh/

↑これです

例えば自分の今の状況はこうなる.

f:id:HelloRusk:20190309142209p:plain

仕組みといたしましては, GitHub API

GET /users/:username/repos

で, 直近の30件のリポジトリ一覧を取得し, それぞれのリポジトリjson の language をみています.

OAuth認証をしていないのですぐにAPI制限に到達してしまいます. 一文字打つごとにGETする仕組みをやめたほうがいいと思うんですが, なんとなく画面がニョロニョロ変化するのが楽しいので, このままにしておきます.

ちなみに GitHub API には, これとは別に,

GET /repos/:owner/:repo/languages

というものがあり, これは, あるリポジトリに対して, 言語別にバイト数を集計できます.

簡単な例で考える React Hooks のよさ

最近安定版にも導入された React Hooks が自分の中で面白いので, とても簡単な例で試してみたよという話をします.

例1: フォーム

class Home extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: 'ここにポエムを書いてね'
    };

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(event) {
    this.setState({value: event.target.value});
  }

  handleSubmit(event) {
    alert('お前のポエムはこれだ: ' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          ポエム記入欄:
          <br/>
          <textarea value={this.state.value} onChange={this.handleChange} />
          <br/>
        </label>
        <input type="submit" value="送信" />
      </form>
    );
  }
}

export default Home;

これはフォームになっていて, 書いた内容がそのまま表示されます.

f:id:HelloRusk:20190223232720p:plain

こんな感じ. 実は公式ドキュメントの例をほぼそのまま使っているだけですね



これを React Hooks の useState で書き直してみるとこのくらいに省力化できます.

export default () => {
  const [value, setValue] = useState('ここにポエムを書いてね');

  const handleChange = event => {
    setValue(event.target.value);
  }

  const handleSubmit = event => {
    alert('お前のポエムはこれだ: ' + value);
    event.preventDefault();
  }

  return (
    <form onSubmit={() => handleSubmit(event)}>
      <label>
        ポエム記入欄:
        <br/>
        <textarea value={value} onChange={() => handleChange(event)} />
        <br/>
      </label>
      <input type="submit" value="送信" />
    </form>
  );
}

どうですか?綺麗じゃないですか?
value が状態としての this.state.value から, ただのローカル変数に落ちたことで, めちゃくちゃすっきり書けていますよね.
注意としては, React Hooks を使う場合は(すなわち Classではなく関数で書く場合は)Class の時のように this.handleSubmit = this.handleSubmit.bind(this); というような初期化ができず, 代わりに onSubmit のプロパティの部分で () => handleSubmit(event) とアロー関数を渡します.
これはどういうことかを考えると, https://uhyohyo.net/javascript/11_2.html にあるように, bind の戻り値は新しい関数なんですね. Class を使う場合では onSubmit={this.handleSubmit} だけでは関数は更新されず, コンストラクタの部分で bind で呼び出すことで初めて更新されるんですね.
一方, アロー関数で書けば矢印の先は評価されるので, ここで既に関数の更新が行われます. というわけで, Class を使わない functional component の例では, 必ずこういう関数自体を更新したいケースではアロー関数になっているのだと理解しています.



例2: 時計

React の中での大切な概念として Lifecycle というものがあります. といっても自分も正確に理解しているわけではありませんが...

qiita.com

がいつ見ても分かりやすいなと思います.

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  componentDidMount() {
    this.timerID = setInterval(
      () => this.tick(),
      1000
    );
  }

  componentWillUnmount() {
    clearInterval(this.timerID);
  }

  tick() {
    this.setState({
      date: new Date()
    });
  }

  render() {
    return (
      <div>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

export default Clock;

さてこれは現在の時刻をリアルタイムで表示する例です(これも公式ドキュメントの例から). componentDidMount で1秒後に時刻を更新して, componentWillUnmount でそのリソースを解放しています.
このような「状態の変化に応じて何かをする」ときに役立つ React Hooks が useEffect で, これを使うと

export default () => {
  const [date, setDate] = useState(new Date());

  const tick = () => {
    setDate(new Date());
  }

  useEffect(() => {
    const timerID = setInterval(
      () => tick(), 
      1000
    );
    return () => {
      clearInterval(timerID);
    };
  })

  return (
    <div>
      <h2>It is {date.toLocaleTimeString()}.</h2>
    </div>
  )
}

これまた非常に簡潔になりました.
useEffect は優れもので, 公式ドキュメントを読むと,

If you’re familiar with React class lifecycle methods, you can think of useEffect Hook as componentDidMount, componentDidUpdate, and componentWillUnmount combined.

ということが分かります. Lifecycle を集約できるのです.



時代はどんどん functional component に向かっていくのでは

今まで見てきた内容から分かると思うのですが, React の公式ドキュメントは現状かなり Class で書かれています.
React に触れる人がまず目を通すであろう React Tutorial も, ただ DOM を render するだけのパーツでさえも Class で書かれているのが分かります. これはちょっととっつきづらいでしょう.
自分が初めて React に触れた時も Class がたくさん出てきたのに驚いた記憶があります. 使い方が分かっていくうちに Class を使わない functional component でも事足りる場合が多いことに気づいていきました. そこに追い打ちをかけるのがこの React Hooks の登場だと思います.

一方で, Facebook 社は Class を完全に捨て去るわけでもないようです. https://reactjs.org/docs/hooks-intro.html#gradual-adoption-strategy を読んでみてください.

We intend for Hooks to cover all existing use cases for classes, but we will keep supporting class components for the foreseeable future. At Facebook, we have tens of thousands of components written as classes, and we have absolutely no plans to rewrite them. Instead, we are starting to use Hooks in the new code side by side with classes.

Rustで2048をつくる

2048というゲームのブームはとっくに終わっているとは思いますが, 自分は未だに通学中とかライブ開演前とかの空き時間によくやっています.
そんな2048をパソコンでもやりたいなと考えたので, つくってみました.

Rustのビルドシステム兼パッケージマネージャである Cargo の環境があれば,

git clone https://github.com/7ma7X/2048.git
cd 2048
cargo run

で体験できます. Cargo を入れていない方は公式チュートリアルを参考にどうぞ(布教).
以下は感想戦のようなものです.

全体の設計

当初は一枚のプログラムでもいいかなと思いましたが, 盤面の操作が肥大化していくことに気づいたので, 盤面操作関連を board.rs に, 全体のゲーム進行を main.rs に分離しました.
board.rs にはテストケースも含まれています. なんとなく2048のようだが実は正しくない動作, になることを防ぐために, テストケースはかなり慎重に書きました. とくに盤面を動かす動作は本物の2048をやりながら慎重に色々なパターンを試しました.

2048は4*4の配列とスコアが全て

Rustにはクラスが無い代わりに, 型や構造体にトレイトを注入して機能を追加していきます. 2048の場合はこんな感じです.

pub struct Board {
  score: i32,
  board_data: [[Option<i32>; 4]; 4]
}

impl Board {
  // ここに機能を書いていく
}

盤面の数値は, 値が無い場合も含めてひとつの値として取り扱いたいので, Option 型を使います.

ゲームオーバー判定

Boardを初期化するメソッドと, 現在のBoardの状況を表示するメソッドを作った後は, まずゲームオーバー判定ができることが必要だろうと考えました.
これは「全ての盤面が数で埋まっていて, かつ縦横2連続で同じ数字が並んでいるものがない」かどうかを確認すれば良さそうです.

pub fn is_continuable(&self) -> bool {
  let mut ans = false;

  for i in 0..3 {
    for j in 0..4 {
      match self.board_data[i][j] {
        Some(x) => {
          match self.board_data[i+1][j] {
            Some(y) => if x == y { ans = true; break; },
            None => { ans = true; break; }
          }
        }
        None => { ans = true; break; }
      }

      match self.board_data[j][i] {
        Some(x) => {
          match self.board_data[j][i+1] {
            Some(y) => if x == y { ans = true; break; },
            None => { ans = true; break; }
          }
        }
        None => { ans = true; break; }         
      }
    }
  }

  ans
}

なお, テストケースはこんな感じです. Rustでは #[test] を関数の前につけることでテスト時実行の標識になり, cargo test で確認できます.

#[test]
fn check_continuability() {
  let mut test_board = Board::initialize();
  assert_eq!(test_board.is_continuable(), true);
  test_board.board_data = [
    [Some(4), Some(2), Some(4), Some(2)],
    [Some(8), Some(4), Some(8), Some(4)],
    [Some(4), Some(8), Some(4), Some(8)],
    [Some(2), Some(4), Some(2), Some(4)]
  ];
  assert_eq!(test_board.is_continuable(), false);
  test_board.board_data = [
    [Some(4), Some(2), Some(4), Some(2)],
    [Some(8), Some(4), Some(8), Some(4)],
    [Some(4), Some(8), Some(4), Some(8)],
    [Some(2), Some(4), Some(2), Some(2)]
  ];
  assert_eq!(test_board.is_continuable(), true);
  test_board.board_data = [
    [Some(4), Some(2), Some(4), Some(2)],
    [Some(8), Some(4), Some(8), Some(4)],
    [Some(4), Some(8), Some(4), Some(8)],
    [Some(2), Some(4), Some(2), None]
  ];
  assert_eq!(test_board.is_continuable(), true);
}

動かす動作

肝心の盤面を動かす動作ですが, 以下のように考えました(自分はこういうアルゴリズムを考えるのがとても苦手なので, もっと効率の良い方法があるような気がします. あくまで一例程度に).
基本的にはバブルソートならぬ「バブル数値潰し」をやります.
例えば

[16, -, 8, 8]

を左にスライドしたいとします.
1番目の要素を0番目の要素へ潰す. ところが1番目の要素は無いのでこのまま.
2番目の要素を1番目の要素へ潰す. すると, [16, 8, -, 8].
1番目の要素を0番目の要素へ潰す. 値が異なるので潰れずこのまま.
3番目の要素を2番目の要素へ潰す. すると, [16, 8, 8, -].
2番目の要素を1番目の要素へ潰す. すると, [16, 16, -, -].
1番目の要素を0番目の要素へ潰す. しかしここで [32, -, -, -] とすると実際の2048の挙動とは違うものになってしまう(つまり, 一度2倍された値をさらに2倍するようなことが禁止されている). なので適切にboolを設定して [16, 16, -, -] のままであるようにする.

基本的にはこれでOKですが, 例外があって, [a, a, b, b] のパターンは [2a, 2b, -, -] になり, 2回潰すことが必要です(本物の2048をプレイして確認してみてください). このケースだけ例外として先に弾きました.

スコア体系についても注意が必要です. 本物の2048では「新しくできた後の値」がスコアに追加されます.

実装してみたのが以下の通り. 左以外の方向についてもインデックスを変えるだけです.

pub fn move_left(&mut self) {
  for i in 0..4 {
    if self.board_data[i][0] == self.board_data[i][1] 
    && self.board_data[i][2] == self.board_data[i][3] 
    && self.board_data[i][0] != None
    && self.board_data[i][2] != None
    {
      self.board_data[i][0] = self.board_data[i][0].map(|v| v*2);
      self.board_data[i][1] = self.board_data[i][2].map(|v| v*2);
      self.board_data[i][2] = None;
      self.board_data[i][3] = None;

      self.score += self.board_data[i][0].unwrap() + self.board_data[i][1].unwrap();
    }
    else
    {
      let mut is_renewed = false;
      for j in 0..4 {
        for k in (0..j).rev() {
          match self.board_data[i][k+1] {
            None => {}
            Some(y) => {
              match self.board_data[i][k] {
                None => {
                  self.board_data[i][k] = Some(y);
                  self.board_data[i][k+1] = None;
                }
                Some(x) => {
                  if x == y && !is_renewed {
                    self.board_data[i][k] = Some(2 * y);
                    self.board_data[i][k+1] = None;
                    self.score += 2 * y;
                    is_renewed = true;
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

テストケースは以下のようなものを各方向にそれぞれ3つずつ書いています(全部実際にプレイしながらの実例).

#[test]
fn check_move_left() {
  let mut test_board = Board::initialize();
  test_board.score = 276;
  test_board.board_data = [
    [Some(16), Some(4), Some(2), Some(4)],
    [Some(2), Some(32), Some(8), Some(8)],
    [Some(8), Some(8), Some(16), Some(2)],
    [None, Some(4), Some(2), None]
  ];
  test_board.move_left();
  assert_eq!(test_board.score, 308);
  assert_eq!(test_board.board_data, [
    [Some(16), Some(4), Some(2), Some(4)], 
    [Some(2), Some(32), Some(16), None], 
    [Some(16), Some(16), Some(2), None], 
    [Some(4), Some(2), None, None]
  ]);
}

新しく発生する2または4

2048では初回に2つ, それ以降は1つ, 2または4が空いているマスに湧いていきます. 乱数を使ってうまいこと2または4を発生させます.
Rustでは乱数は外部ライブラリなので, extern crate rand; します. Rust ではライブラリ(パッケージ)のことをクレートと呼びます.
Cargo.toml の dependencies にも別途その旨が必要です. Node の package.json と同じような感じです.

本物の2048で遊んでみると, 2と4がだいたい3:1くらいの比率で出ているような気がしたので,

fn generate_two_or_four() -> i32 {
  let choices: [i32; 4] = [2, 2, 2, 4];
  let mut rng = thread_rng();

  *choices.choose(&mut rng).unwrap()
}

が新しい値発生器です.
次に, 現在の盤面から空いている座標一覧を取得する

fn fetch_no_number_area(&self) -> Vec<(usize, usize)> {
  let mut ans = Vec::new();

  for i in 0..4 {
    for j in 0..4 {
      if self.board_data[i][j] == None { ans.push((i, j)); }
    }
  }
  ans
}

を定義します.
これらを使って,

pub fn fill_in(&mut self) {
  let no_number_vec = Board::fetch_no_number_area(self);
  let mut rng = thread_rng();

  match no_number_vec.choose(&mut rng) {
    Some(tuple) => {
      let (x, y) = *tuple;
      self.board_data[x][y] = Some(Board::generate_two_or_four());
    }
    None => {}
  }
}

が定義されます.
余談ですが, 空いているマスをランダムに選択する no_number_vec.choose(&mut rng)Option 型を返すので, 最初の実装では単に .unwrap() していましたが, 実際にこの2048のプログラムで遊んでいたところ, 「全部のマスが数字で埋まっているがゲームオーバーでない状況」で panic! を起こして, そこで初めて,「空いているマスが無いので .choose が効かない」可能性に思い至り, きちんとパターンマッチさせるようにしました.
このように .unwrap() を適当に使っていると痛い目に会いますね.

それぞれの方向に動かせるかどうかを判定する

あとはこれで main.rs にゲーム本体の処理を書いて完成...といきたかったところですが, 実際にこの2048のプログラムで遊んでみると重大な問題があることが判明しました.
それは, 「本物の2048では一つも数字が潰れない方向にスライドしても2または4が湧くことはない」というのを見逃している点でした.
4方向のどの方向にも動かせないか否かを判定する .is_continuable とは別に, それぞれの方向で動かせるか否かを判定しなければならなかったのです.

そこでまず, 動かせる列というのは何者かを考えてみます.

fn is_movable_row(row: [Option<i32>; 4]) -> bool {
  match row {
    [_      , None   , None,    None   ] => false,
    [Some(a), Some(b), None,    None   ] 
      if a != b                          => false,
    [Some(a), Some(b), Some(c), None   ] 
      if a != b && b != c                => false,
    [Some(a), Some(b), Some(c), Some(d)] 
      if a != b && b != c && c != d      => false,
    _                                    => true
  }
}

こんな風にかけました. 型がある言語でよかったですね. あとはこの判定を4つの列に対して行うだけです. 左スライドであれば,

pub fn is_left_movable(&self) -> bool {
  let mut ans = false;

  for i in 0..4 {
    let row = [
      self.board_data[i][0],
      self.board_data[i][1],
      self.board_data[i][2],
      self.board_data[i][3]
    ];
    if Board::is_movable_row(row) {
      ans = true;
      break;
    }
  }
  ans
}

他の方向もインデックスを適宜変えるだけなので簡単です.
ゲーム本体の処理である main.rs の方では

  if input.starts_with("i") || input.starts_with("I") {
    if board.is_up_movable() {
      board.move_up();
      board.fill_in();
      board.display();
    }

というように .is_up_movable() の判定を噛ませることで, その方向に動かせない場合は, 操作の入力自体はできるものの, 盤面操作だけでなく表示も含めて何も起こらないようにしました.
これで, 勝手に2または4が湧かなくなったとともに, その方向に盤面が動かないこともプレイしながら分かりやすくなったので, とてもよかったです.

その他の感想

  • 全体的に Option 型を使って書けたのが安心感がありました.

  • 当初は.highscoreみたいなファイルを作ってハイスコアを保存することも考えたのですが, Rustのファイル I/O 関数の操作がかなり面倒だったので(open, read, writeなどあらゆる処理で Result 型 が帰ってきます. まあわざわざ IO 型まで用意されていてそこから抜けられないような Haskell よりはマシかも...), 諦めました.
    そもそもこの2048で遊んでも保存したくなるようなハイスコアまで全然行かないと思います. 理由として一つ挙げられるのが, 数字の背景に色が付いていないことです. 数字に色を付ける程度ならこのプログラムでも出来ますが, ケバくなりそうなのと, 個々のターミナルの環境依存性が強そうなので, そこまでやる気にはなりませんでした.

  • GitHubを漁ったらRustで書かれた2048でもっとすごいのが色々あった.

まあこれやる位なら本物かjavascriptを使ってブラウザ上で動くやつをやった方ががいいのでは

NFAをDFAに変換して状態数最小化する

大学の試験で「与えられた言語を認識するような状態数最小の有限オートマトンを答える」問題が頻出されているため, 検算用のプログラムを書いてみます.

NFA の入力形式

input.txt として以下のように与えます.

2 0
0,0 1,0,
1,2,2,
2,3,3,
3,,,1

一行目は[入力文字の種類数] [始状態] を空白区切りで与えています.
二行目以降はCSVのようになっていて, カンマ区切りのうち, 最初の要素は状態の番号づけ, 二番目から最後から二番目までの要素は各文字を読み取ったあとの状態遷移, 最後の要素は「その状態が終状態か否か」を示します. 状態遷移に関しては, NFAなので, 状態が複数ある場合は空白区切りで列挙し, 一つもない場合は空文字になります. 終状態か否かに関しては, 終状態の場合のみ何かしら文字を入れることにします.

状態数最小のDFA に変換するプログラム

間違いがあれば指摘してほしいです. 私のテストの成績が上がるかもしれないので... gist: https://gist.github.com/7ma7X/bf42cc4b3f251e88c9bc55b5eb34eb3b

import pprint

nfa = []

with open("input.txt", "r") as f:

    # アルファベットの種類数と始状態を読み取る
    n, nfa_initial_state = f.readline().split()
    n = int(n)
    for i in range(n+1):
        nfa.append(dict())

    for line in f.readlines():
        data = line.replace("\n", "").split(",")

        # 遷移を読み取る
        for i in range(n):
            nfa[i][data[0]] = data[i+1]

        # 終状態か否かを読み取る
        nfa[n][data[0]] = True if data[n+1] else False

# NFAを確認したい場合はここをアンコメント
# pprint.pprint(nfa)


# NFA -> DFA

dfa = []
for i in range(n+1):
    dfa.append(dict())

unchecked_states = [nfa_initial_state]
checked_states = []

while unchecked_states:
    tmp_states = unchecked_states.pop()
    checked_states.append(tmp_states)

    for i in range(n):
        # 終状態か否かを書き込む
        dfa[n][tmp_states] = False
        for el in tmp_states.split():
            if nfa[n][el]:
                dfa[n][tmp_states] = True
                break

        # 遷移を書き込む
        new_states = set()

        for el in tmp_states.split():
            if nfa[i][el]:
                next_list = nfa[i][el].split(" ")
                new_states = new_states.union(next_list)

        new_states = " ".join(sorted(list(new_states)))
        dfa[i][tmp_states] = new_states

        if new_states not in checked_states:
            unchecked_states.append(new_states)

# ラベル貼りかえ前のDFAを確認したい場合はここをアンコメント
# pprint.pprint(dfa)

# 状態のラベルの張り替え
checked_states = sorted(list(set(checked_states)))
m = len(checked_states)

for i in range(n+1):
    for j, state in enumerate(checked_states):
        if i == n:
            dfa[i][j] = dfa[i].pop(state)
        else:
            dfa[i][j] = checked_states.index(dfa[i].pop(state))

dfa_initial_state = checked_states.index(nfa_initial_state)
checked_states = list(range(m))

# ラベル貼りかえ後のDFAを確認したい場合はここをアンコメント
# pprint.pprint(dfa)


# DFAの最小化

min_dfa = []
for i in range(n+1):
    min_dfa.append(dict())

marked_list = []
unmarked_list = []

for i in range(m-1):
    for j in range(i+1, m):
        if (dfa[n][i] and not dfa[n][j]) or (not dfa[n][i] and dfa[n][j]):
            marked_list.append((i, j))
        else:
            unmarked_list.append((i, j))

while True:
    is_renewed = False

    for i in range(m-1):
        for j in range(i+1, m):
            for k in range(n):
                if dfa[k][i] < dfa[k][j]:
                    next_tuple = (dfa[k][i], dfa[k][j])
                else:
                    next_tuple = (dfa[k][j], dfa[k][i])

                if (i, j) in unmarked_list and next_tuple in marked_list:
                    is_renewed = True
                    unmarked_list.remove((i, j))
                    marked_list.append((i, j))

    if not is_renewed:
        break

# MarkedList, UnmarkedListを確認したい場合はここをアンコメント
# print(marked_list)
# print(unmarked_list)

state_num = 0

min_dfa_hash = dict()
for (i, j) in unmarked_list:
    if i in min_dfa_hash and j in min_dfa_hash:
        k = min_dfa_hash[i]
        l = min_dfa_hash[j]
        for state in checked_states:
            if state in min_dfa_hash and min_dfa_hash[state] == l:
                min_dfa_hash[state] = k
    elif i in min_dfa_hash:
        min_dfa_hash[j] = min_dfa_hash[i]
    elif j in min_dfa_hash:
        min_dfa_hash[i] = min_dfa_hash[j]
    else:
        min_dfa_hash[i] = min_dfa_hash[j] = state_num
        state_num += 1

for state in checked_states:
    if state not in min_dfa_hash:
        min_dfa_hash[state] = state_num
        state_num += 1

min_dfa_initial_state = min_dfa_hash[dfa_initial_state]
new_checked_states = []

for state in checked_states:
    tmp_state = min_dfa_hash[state]

    # 終状態か否かを書き込む
    if tmp_state in min_dfa[n] and not min_dfa[n][tmp_state]:
        if dfa[n][state]:
            min_dfa[n][tmp_state] = True
    elif tmp_state not in min_dfa[n]:
        if dfa[n][state]:
            min_dfa[n][tmp_state] = True
        else:
            min_dfa[n][tmp_state] = False

    # 遷移を書き込む
    if tmp_state in new_checked_states:
        continue
    else:
        new_checked_states.append(tmp_state)

        for i in range(n):
            min_dfa[i][tmp_state] = min_dfa_hash[dfa[i][state]]

new_checked_states = sorted(list(set(new_checked_states)))

for i in range(n+1):
    for j, state in enumerate(new_checked_states):
        if i == n:
            min_dfa[i][j] = min_dfa[i].pop(state)
        else:
            min_dfa[i][j] = new_checked_states.index(min_dfa[i].pop(state))

min_dfa_initial_state = checked_states.index(min_dfa_initial_state)

print("始状態: {}".format(min_dfa_initial_state))
pprint.pprint(min_dfa)

にある「10110を部分語として含むような言語」でやってみます. NFA で書くと

なので, input.txt

2 0
0,0,0 1,
1,2,,
2,,3,
3,,4,
4,5,,
5,5,5,1

となります. プログラムを実行してみます.

pprint.pprint(nfa) をアンコメントすると,

[{'0': '0', '1': '2', '2': '', '3': '', '4': '5', '5': '5'},
 {'0': '0 1', '1': '', '2': '3', '3': '4', '4': '', '5': '5'},
 {'0': False, '1': False, '2': False, '3': False, '4': False, '5': True}]

これは, 一つ目の連想配列が0に関して読む前をkey, 読んだ後をvalueとしていて, 二つ目の連想配列が1に関して読む前をkey, 読んだ後をvalueとしていて, 最後が終状態か否か, を表しています.
ラベルを張り替える前の pprint.pprint(dfa) をアンコメントすると,

[{'0': '0',
  '0 1': '0 2',
  '0 1 3': '0 2',
  '0 1 3 5': '0 2 5',
  '0 1 4': '0 2 5',
  '0 1 4 5': '0 2 5',
  '0 1 5': '0 2 5',
  '0 2': '0',
  '0 2 5': '0 5',
  '0 5': '0 5'},
 {'0': '0 1',
  '0 1': '0 1',
  '0 1 3': '0 1 4',
  '0 1 3 5': '0 1 4 5',
  '0 1 4': '0 1',
  '0 1 4 5': '0 1 5',
  '0 1 5': '0 1 5',
  '0 2': '0 1 3',
  '0 2 5': '0 1 3 5',
  '0 5': '0 1 5'},
 {'0': False,
  '0 1': False,
  '0 1 3': False,
  '0 1 3 5': True,
  '0 1 4': False,
  '0 1 4 5': True,
  '0 1 5': True,
  '0 2': False,
  '0 2 5': True,
  '0 5': True}]

状態数が10個にまで広がってしまっていますね. ラベルを貼りかえた後の pprint.pprint(dfa) をアンコメントすると,

[{0: 0, 1: 7, 2: 7, 3: 8, 4: 8, 5: 8, 6: 8, 7: 0, 8: 9, 9: 9},
 {0: 1, 1: 1, 2: 4, 3: 5, 4: 1, 5: 6, 6: 6, 7: 2, 8: 3, 9: 6},
 {0: False,
  1: False,
  2: False,
  3: True,
  4: False,
  5: True,
  6: True,
  7: False,
  8: True,
  9: True}]

と, だいぶ見やすくなりました. そして最終結果は

始状態: 1
[{0: 0, 1: 1, 2: 5, 3: 5, 4: 0, 5: 1},
 {0: 0, 1: 2, 2: 2, 3: 4, 4: 2, 5: 3},
 {0: True, 1: False, 2: False, 3: False, 4: False, 5: False}]

と, 状態数6個になります. これを整理して図示すると,

まあ多分合ってるんじゃないかと思います.


ここおかしいんじゃね?みたいなところがあったら是非是非コメントしてください.

今TeXを始めるならVSCodeが良いかもしれない, という話と, MacOSで最低限の文書を出せるようにするまで

VSCodeLaTeX拡張機能に, 部分的な数式のレンダリングの機能があることを今日知りました.



どうやらこれは比較的最近追加されたようで, ザッと調べてみると, https://github.com/James-Yu/LaTeX-Workshop/pull/867 で最初にHover previewが提案され, その後, https://github.com/James-Yu/LaTeX-Workshop/pull/890 で完成に至ったような感じで, 今年の10月のことでした.
個人的には, TeXは以前からシケプリを作ったりするのに使ってはいましたが, 今まではTeXShopを使っていて, 正直あまり使い勝手が良くないなと思っていましたし, 普段から使い慣れているVSCodeの方が便利そうだな, というわけで, VSCodeに乗り換えることにしました.

以下, MacOSでのセットアップの手順をメモしておきます.



doratex.hatenablog.jp

まず, 私も化学を1年間教わっていた寺田先生による詳細な環境構築の手順に従うと良いでしょう.
MacTeXのインストーラのダウンロードと, TeX Liveディレクトリの更新が非常に時間がかかります.
残念ながら私はヒラギノフォントの埋め込みがどうしてもうまくいかなかったので, 代わりにIPAexフォントの埋め込み

sudo kanji-config-updmap-sys ipaex

をしました. 私はIPAexでも十分綺麗だと思うので問題に感じませんでしたが, いつかきちんと再設定できるといいですね. とにかく,

kanji-config-updmap status

と入力して, CURRENT family for ja: [hogehoge] みたいな感じで表示されればフォントの埋め込みに成功していると思います.
これで,

ptex2pdf -l -ot -kanji=utf8 -synctex=1 [YOUR_TEX_PROGRAM]

コンパイルがうまく通り, pdf が出力されればOK.



次に, VSCode の方です. 拡張機能 LaTeX Workshop を入れましょう. そして, settings.json に 以下を追記します.

    "latex-workshop.latex.tools": [
      {
          "command": "ptex2pdf",
          "args": [
              "-l",
              "-ot",
              "-kanji=utf8 -synctex=1",
              "%DOC%"
          ],
          "name": "ptex2pdf"
      },
    ],
    "latex-workshop.latex.recipes": [
        {
            "name": "ptex2pdf",
            "tools": [
                "ptex2pdf",
            ]
        }
    ]

注意すべきなのは(おそらくVSCodeTeXの環境構築をやろうとしている多くの人が見ているであろう http://elecho.hatenablog.com/entry/2017/04/27/175500 など, 古い記事にはしばしば見られる) latex-workshop.latex.toolchain が現在は Obsolete になっていて, 代わりに latex-workshop.latex.tools でコマンドを定義して, latex-workshop.latex.recipes で(レシピという名の通り)それを組み立てる感じになっている点です. もっとしっかりとした(bibtexなども使った)settings.json の書き方は以下のようなものが参考になります. 確かにレシピっぽい.

gist.github.com



環境構築は以上の通りです. 実際の使い方は,

  1. ある程度TeXファイルを書き上げたら, Command + Shift + P -> LaTeX Workshop: Build with recipe -> ptex2pdf で pdf を生成
  2. VSCode の右上の虫眼鏡みたいなやつをクリックすると右に生成されたpdfが別タブで表示される
  3. その後はTeXファイルを適当にいじって保存すると, Sync TeXの機能によって自動で右側のpdfも修正されていく.

という感じですね. 良きTeX生活を!

Rustの配列に関する雑多なメモ

多重配列の初期化

固定長のarrayの場合は[T; N]という形式なので, 例えば2次元配列の場合は,

let a: [[i32; 3]; 2] = [[0; 3]; 2];
println!("{:?}", a); // [[0, 0, 0], [0, 0, 0]]

let mut b = [[39; 2]; 3]; // 型を省略すると推論される
b[1][1] = 8;
println!("{:?}", b); // [[39, 39], [39, 8], [39, 39]]

で良いと思われる. 初期化せずに値を入れようとするとコンパイルエラーになる.

では, 可変長のvecを二重に包む場合は?と考えてみる. 一つはvec!マクロを使う方法が考えられて,

let mut v1: Vec<Vec<i32>> = vec![vec![99; 3]; 2];
v1[1][1] = 8;
println!("{:?}", v1); // [[99, 99, 99], [99, 8, 99]]

とarrayとほぼ同様の操作ができる.
また, resizeメソッドという, vecを指定の長さに変更する(長さが増える場合は指定された値で足りない要素を埋める)メソッドを2回使えば,

let mut v2: Vec<Vec<i32>> = Vec::new();
v2.resize(4, Vec::new());
for i in 0..4 { v2[i].resize(2, i as i32); }
v2[1][1] = 8;
println!("{:?}", v2); // [[0, 0], [1, 8], [2, 2], [3, 3]]

と言う感じで行けるようだ.
注意すべきなのは, Vec::with_capacity(10)というように初期化したり, v.reserve(10)というように領域を確保しても, 領域が保証されているだけで, そのまま値を入れようとすると実行時にエラーになってしまうので, 気をつけたい.

let mut c: Vec<i32> = Vec::with_capacity(10);
println!("{}", c.capacity()); // 10
println!("{}", c.len()); // 0
// c[2] = 8; // 実行時エラー


配列のループ

https://doc.rust-jp.rs/the-rust-programming-language-ja/1.6/book/loops.html

Rustのループは

for var in expression {
  code
}

という形式で, expressionの部分はイテレータを返す式にしなければいけないようだ. 逆にイテレータにすることでmax, minやfold, map, filter等の配列特有のメソッドを利用できるようになる. https://doc.rust-lang.org/std/iter/trait.Iterator.html https://qiita.com/lo48576/items/34887794c146042aebf1

arrayの場合は, array自体はiterableではないので何かしら付ける. arrayの大きさが32以下であれば, &[T]IntoIteratorと同様になる. それ以上の大きさであれば.iter(), .into_iter() を使う.

let a = [2, 3, 5, 7, 11];

for i in &a {
  print!("{} ", i); // 2 3 5 7 11 
}

for i in a.iter() {
  print!("{} ", i); // 2 3 5 7 11 
}

for i in a.into_iter() {
  print!("{} ", i); // 2 3 5 7 11 
}

元の配列をmutableにし, ループでもmutableな参照にすることで, 規則的に配列を変換することができる.

let mut p = [2, 3, 4];

for i in &mut p {
  *i *= 2;
}

println!("{:?}", p); // [4, 6, 8]

これはmapの方が楽なのでは?と思ったが, 調べるとRustのイテレータのmapは遅延評価なので, 使いにくいことがわかった.

let a = [2, 3, 4];
let b = a.iter().map(|x| x * 2);
println!("{:?}", b); // Map { iter: Iter([2, 3, 4]) }

// collect()で評価させる
let c: Vec<i32> = a.iter().map(|x| x * 2).collect();
println!("{:?}", c); // [4, 6, 8]

IntoIterator はsliceが使え, 既存のデータ構造の一部を安全に取り出して操作することができる.

let mut p = [0; 20];

for i in &mut p[2..10] {
  *i = 9;
}

println!("{:?}", p); // [0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]


vecはiterableなのでそのままループできてしまうのだが,

let mut v = vec![2, 3, 5, 7, 11];

for i in v {
  print!("{} ", i); // 2 3 5 7 11
}

これだと所有権がiに移ってしまってループの後でv.push(13);などが出来なくなってしまう. なので, 参照渡しを徹底するように心がけたい.

let mut v = vec![2, 3, 5, 7, 11];

for i in &v[2..4] {
  print!("{} ", i); // 5 7
}

v.push(13);

println!("{:?}", v); // [2, 3, 5, 7, 11, 13]

Twitter APIに全く頼らずにslack botだけでタイムライン管理をしてみる

※ この記事はISer Advent Calendar 2018の3日目の記事としても書かれています.



最近はTwitterを開くのが面倒でやっていないのですが, 好きな声優 or アーティスト or アニメの最新の情報が手に入らないことに以前より悩まされていました. 昨日デレマスのライブに参加した際, 特にそれを痛感したので, これは何とかせねばならないということで, 打開策を講じてみることにしました.
ただし, 周知の通り, Twitter APIの新規OAuth認証は今年の夏に厳格化されましたし, この先もどうなるか分かりません. そこで今回はAPIに頼らずに泥臭くクロールしてみます. こんな感じのものができました. まだ動かし始めてから2時間程度なのでちゃんと継続して動いてくれるのか怪しいのですが, 安定して稼働してほしいですね.

f:id:HelloRusk:20181202221934p:plain



f:id:HelloRusk:20181202225615p:plain



手順

その0 slackの公式Twitterアプリケーションをインストールする

これを入れると, twitterのリンクを貼るだけで中身のツイートや写真まで展開されるので, 必須です.

その1 botの準備

slack botを作るためのフレームワークとしては,

など色々あるようです. hubot が一応GitHub謹製なので良いのかもしれません. 自分は hubot にしました. ドキュメンテーションhttps://hubot.github.com/docs/ を見るといいと思います. --adapter=slack を忘れないようにしましょう.
あとはpackage.json見たらNodeのengineのバージョンがアホみたいに小さかったので, 8.11.xに上げました. 本当になんであんなに低かったんだろう....

その2 herokuの設定

slack botトークンを環境変数として保存します.

heroku config:set HUBOT_SLACK_TOKEN:XXXXXXXXXXXXXXXXXXXXXXXXXXXX

slack botにフォロワーを記憶させたいので, ブラウザの方でherokuを開いてRedisのアドオンを入れておきます. また, heroku plugins:install heroku-redis しておくと, コマンドラインの方でもどうなっているのか確認できるので良いでしょう. また, 定番ですが, herokuのfree dynoは30分で寝るのでHeroku Scheduler(要クレカ番号登録)は必要です. heroku psで後どれくらい使ったらヤバイのかが見れるのを知っておくと便利でしょう.

その3 botの中身を書く

f:id:HelloRusk:20181202225938p:plain

hubot は中身のスクリプトが元々の例がcoffeescriptとかいうもので書かれているので, それに従ったのですが(実は他の言語でも書けた??調べていません), 結構つらかったです. do記法のようなシンタックスシュガーは確かに学問的には面白いのかもしれませんが...
幸いなことに, 普通のNodeとcoffeescriptを変換してくれるサイト http://js2.coffee/ があり, 結構助かりました. ともかくも, 中身はこんな感じで書いてます.

cronJob = require('cron').CronJob
request = require('request')

module.exports = (robot) ->

  robot.hear /^follow (\S*)/i, (res) ->
    newfollowing = res.match[1]
    following = robot.brain.get('following') ? []
    following.push newfollowing
    robot.brain.set('following', following)
    robot.brain.set(newfollowing, '0')

    res.send "#{newfollowing} をフォローしました"
  
  robot.hear /^remove (\S*)/i, (res) ->
    removed = res.match[1]
    following = robot.brain.get('following') ? []
    following = following.filter((n) -> n != removed)
    robot.brain.set('following', following)

    res.send "#{removed} のフォローを解除しました"

  robot.hear /^following$/i, (res) ->
    following = robot.brain.get('following') ? []
    textdata = following.join("\n")

    res.send textdata


  new cronJob('0 * * * * *', () ->
    following = robot.brain.get('following') ? []
    if following.length == 0
      return
    
    following.forEach (id) ->
      num = robot.brain.get(id)

      request 'https://twitter.com/' + id, (e, r, b) ->
        status_and_id_array = b.match(/data-tweet-id="\S*"/gi)
        if !status_and_id_array
          return

        tmp = '0'

        status_and_id_array.forEach (el) ->
          new_el = el.replace(/data-tweet-id="(\S*)"/gi, '$1')
          if Number(new_el) > Number(tmp)
            tmp = new_el
        
        if tmp > num
          robot.send {room: '#general'}, id + " の新しいツイート:"
          robot.send {room: '#general'}, "https://twitter.com/twitter/status/" + tmp
          robot.brain.set(id, tmp)

  ).start()

follow Xでフォロイーを追加, remove Xでフォロイーを削除, followingでフォロイー一覧...というのは自然なのですが, じゃあツイート取得はどうしているかというと, 愚直にhttps://twitter.com/[id]をGETし, data-tweet-id=の後の数値の文字列が最もデカいものを抜き取り, それを更新していくcron(1分に1回)を作っているわけです. 単純にdata-tweet-id=の後の数値の文字列の位置が一番上のものにしてしまうと, 固定ツイートが含まれてしまうこともあるので, ツイートと一対一対応し、かつ単調増加する数値を取得するのが良いと思いました. ただしNumber()で文字列を数値にするところでMaxIntを超えているので下2桁くらい抜け落ちたものを比較しているという雑さがあります. まあ100ツイートなんて世界中で瞬間的に生成されるのでいいかな〜と思うのですが, 桁が2つくらい増えたらちゃんとゼロ埋めして文字列で比較した方が良い感じがしました.
当然ですが, 非公開アカウントや, 1分間に何回もツイートするアカウントには対応できません(後者はcron間隔を狭めることで何とかなる部分もあるかもしれないが, sleepから覚めてビルドする際の時間もあるので, やっぱり無理です. そもそも自分はこれを公式アカウントのツイートの収集の目的で始めたので...).
(12/3 0:37 追記: よく考えると上のツイート2つのidを比較するだけで良い気がしました. でも突然固定ツイートの個数が増えたりしたらどうしよう.)

なお, hubotのbrainメソッドを使ったRedisとの連携は, 以下の記事が大変参考になりました.



というわけで, 雑にslack botを作ってみたという話でした.