JavaScriptのbindとは何なのか

Reactのドキュメントで出てきた疑問だが、ReactというよりもむしろJSの知識だ。

同期に教わってみると意外とシンプルだった。bindは何かというよりも、むしろ本当の問題は「thisとはなにか」というところにある。thisは実行時のコンテクスト[note]スコープのようなもの?難しい[/note]であって、関数を呼ぶ方法によって変化する。

class MyClass {
  getX() {
    return this
  }
}

const myclass = new MyClass()

// クラスから呼び出すと、想定通りに動く
console.log(myclass.getX())
// MyClass{}

// 一度関数単体で取り出してしまうと、thisがなんだったか忘れてしまう
const unboundGetX = myclass.getX
console.log(unboundGetX())
// undefined

// bindを使うとthisを指定しながら関数を実行できる
const boundGetX = myclass.getX.bind(myclass)
console.log(boundGetX())
// MyClass{}

筆者はPythonを先に学んでいたのでJSの上記の仕様には違和感があった。しかしPythonのクラスのメソッドは常にselfを引数に取る(必ずselfが第一引数として渡される)一方でJSはそうではない。どちらも意図的なデザインなのだろう。

class MyClass:
    def getX(self):
        return self

myclass = MyClass()

# クラスから呼び出すと想定通り動く
print(myclass.getX())
# <__main__.MyClass object at 0x7f2a12223588>

# 関数単体で取り出しても想定通り動く
unboundGetX = myclass.getX
print(unboundGetX())
# <__main__.MyClass object at 0x7f2a12223588>

厳密な話を知りたい人はこの辺読んでください。アロー関数の話もしたいね。

第2の料理/ReactのsetStateがワカラナイ

※この記事は『Asahi 極上<キレ味>』を飲んで書かれた。

↑キレ味ってなんだ…?

昨日なんとなく焼きそばに飽きたので今日は鮭の炊き込みご飯を作った。これも僕のレパートリーの1つだが、ご飯を炊くのは時間がかかるので敬遠していた。

作り方はかんたんで、ご飯を炊くときに醤油・鮭・えのき・鮭を一緒に入れるだけだ。こだわるなら昆布も。炊き上がるころには部屋が醤油のいい香りに包まれている。

今日もReactのドキュメントを読んでいた。だいたい理解しながら読み進んでいるが、やはりReactがパフォーマンスを出すために中でゴニョゴニョやっていることを理解するのが難しい。たとえばここ。再描画の回数を減らすためにsetStateは即時に実行されないことがあるという。しかし例示されているコードでどのような不具合が起きる可能性があるのか、failの一言だけではよくわからない。

Because this.props and this.state may be updated asynchronously, you should not rely on their values for calculating the next state.

For example, this code may fail to update the counter:

// Wrong
this.setState({
  counter: this.state.counter + this.props.increment,
});

これの解決策としてsetStateに関数を渡すことで確実にsetStateが行われるタイミングでのpropsの値を取得する方法が紹介されているが、これによって何を解決しているのだろうか。

だいたい家にいた/ナウいReact

※この記事は『Asahi 極上<キレ味>』を飲んで書かれた。

強い意思があったわけではないが、今日は夕方に買い物に行った以外は家にいた。

以前からやりたいと思っていた‎@kfurumiyaさんの『正真正銘のReactだけの不純物なしでReact入門』をやった。「不純物なし」というのは意外と厄介で、複雑な状態管理はReduxでやるのが普通なのに対して、このチュートリアルではReactの新しい機能であるHooksを使っている。

Reactは形作りが面倒だが、そこに当てはめていくことで巨大なアプリでも比較的小さい負担で作れるというのがこれまでの認識だった。しかしReactの真価である差分検知システムを効率的に動かすためには無駄な再描画をさせないための工夫が必要らしい。慣れればこれも流れ作業のように書けるのかもしれないが、現時点では難しそうだ。

チュートリアルに沿ってひとりツイッターを作ったあと、本家に近づけるために

  • 空白の投稿はできない
  • 投稿したら入力欄が空白になる
  • 削除できる

の3つの機能を追加してみた。見た感じでは正しく動いている。今後はnodeでなんかいい感じにコンパイルする、テストを書く、Ajax通信するなどをやってみたい。

パセラ/実験プログラムを直している

※この記事は『麦をホップ』を飲みながら書かれた。

僕の所属しているサークルにはカラオケパセラで夜通しアニソンを歌い続けるという文化がある。高いし疲れるのでだいぶご無沙汰だったが、学生生活の締めくくりということで昨夜行ってきた。僕がトイレに行っている間にコーンスープにジムビームを盛られた。そのくらいの頭の悪い文化です。

今の家は秋葉原に近いのでパセラに参加しやすくなるな…(フラグではない)

大学にいるうちに僕が作った実験プログラムの修正をせねばならないということでやっている。Hyperappで作ったものだが、だいぶ複雑になってきたのでJestによるテストを導入した。stateを必要とするactionのテストは難しいが、それ以外のちょっとしたデータ処理は積極的に別の関数に切り出し、単体テストの対象とした。だいぶ安心感がある。

修士論文の合格が明らかになった

読みにくくて恐縮だがそういうことだ。なお「散々自分の修論をクソ呼ばわりしておいて結局優取ってんじゃねえか詐欺師野郎」という批判があるかもしれないが、文学部における優は特に意味を持たない。優上は優秀、優は普通、良は悪い、可はお情けだ。

これを受けて、僕が修了を失敗する残された可能性は

  1. 単位数の計算間違い
  2. 修了前に犯罪等で退学になる

に絞られた。JavaScriptを使ったプログラミングをしているので2が発生する確率は決して低くはないのだが、祈ろう。

カメラを止めるな

テレビで『カメラを止めるな』を放送していたので見た。すごいどんでん返しがあるという評判だったので気になっていたのだが、『ラヂオの時間』の発展系のように見えた。視聴者に見えないところで制作スタッフがリアルタイムに奔走している姿を描くという基本形に、いかにも安っぽいB級ゾンビ映画を一度フルで見せるというアレンジによって謎解きの快楽を付け加え、スタッフたちのドラマの魅力を増すことに成功している。

怪異・妖怪ゼロ食い/fish!!

学部生の授業のために作ったプログラムがどうもおかしいという話なので報告をよーく読んでみたら、0から始まる数列をExcelで読んだときに数値扱いされて左端の0が「食われる」という事件だったようだ。キレそう。Excelを許すな(俺が悪い)。なお検証の過程でJestの使い方を覚えた。でもCDNからモジュールをインポートしていることをJestが気づいてくれなくてエラーが出まくってやっぱりキレそう。

自宅のPCのシェルをfishに変えた。昆虫なので色がきれいなのは好き。欠点はググラビリティ。

TweetDeck魔改造―読み上げ

MutationObserverとSpeechSynthesisを使うシンプルな方法が既に紹介されていたが動作が不安定だったのでChrome拡張として自分で作り直した

SpeechSynthesisに長文を連続で送りつけると読み上げが止まってブラウザの再起動が必要になる。この辺りは中で呼んでいるAPIの仕様の領域なのでまだよくわかっていないが、TLの流速に合わせて調節する必要がありそうだ。

TweetDeckのページ全体がJavaScriptによって動的に生成されているので、ページの読み込みのあとにMutationObserverのターゲットを指定する必要がある。愚直にsetTimeoutを使った。新しいツイートの受信以外にもMutationObserverを発火させる要因がなにかあるようだが、まだわかっていない。

読み上げの内容についても、リプライは読むのか、ハッシュタグは読むのか、URLは読むのか、画像はどうするのかなど使い方に応じて検討すべき点がある。これらは設定で変えられるようにしたい。

Vivaldi復帰断念

なんとなくVivaldiのことを調べたら2.x系列がリリースされていた。デザインや設定の自由度の高さはchromeより好きだし、安定性さえ問題がなければしばらく戻ってみようかと思ってインストールしてセッティングしてみたが、最重要拡張機能であるBetterTweetDeckが動かないので泣く泣く諦めた。

BetterTweetDeckはとにかく優れものなのだが、僕が最もよく使うのはクリップボードからの画像投稿だ。スクリーンショットをクリップボードに保存し、それを直接投稿欄に貼り付けるのは非常に便利だ。

ところで今日は寒い。29日に寒波が来るとのことで、コミケ早朝組の生命が心配だ。寒い日は暖かい部屋にいるに限る。今日はほとんど外出しなかった。

昼食に焼きそばを作った。油が切れていたので新しいのを買ったのだが、これが以前より大きいパッケージで(当然大きい方が割安)自然と油の使用量が増える。すると焼きそばの味がよくなり、焦げ付きも減った。油も洗剤も困ったら増やしてみるといい。

JavaScript+Hyperappで心理学実験プログラムを作った

これです(github)。

僕の学科では学生が実験に参加しつつその実験についてレポートを書くという授業が多く行われている。そこで行う実験は古典的なものが多いのだが、大昔に書かれた実験プログラムがメンテナンスされずに使われ続けているということがある。

今回書いた記憶実験のプログラムもそうで、誰がいつどうやって作ったかわからないexeファイルが受け継がれ使われ続けてきた。しかし内部の処理がわからないので細かい仕様がわからず、いろいろ試してみて推測することしかできない状態だった。さらにいつ動かなくなるかもわからない。

そこでこのプログラムを移植(ソースコードがないので移植と言うかは微妙だが)しようという話になった。当初はPythonが候補に上がっていたが、多数の学生に授業中に同時に行わせるにあたり、Pythonをexeに固めたものは容量が大きくなりすぎダウンロードが長引く懸念があった(経験的には数十MB)。そこでJavaScriptでの制作を提案した。サンプルを作って披露したところゴーサインが出たので制作した。

動作は単純で

  1. x桁のランダム数列を3秒間表示
  2. 数列を消し5秒間待つ
  3. 参加者が数列を思い出して回答する
  4. 正誤を3秒間表示
  5. 桁数を変更し、1に戻る

というものだ。5の桁数変更のアルゴリズムには上下法や恒常法などがある。

上下法は正解すれば次は1段階難しく、不正解なら次は1段階簡単にするというアルゴリズムだ。これによって参加者の正解できる限界の桁数に収束する。メリットは試行数が少なく済むこと。

恒常法は一定の範囲の桁数をランダムな順番で課し、桁数を横軸に、正解率を縦軸にプロットすることで正解率を桁数を結びつける関数を得る。そして正解率50%(任意の値)に相当する桁数をその人の限界とする。メリットは関数そのものが得られるため多様な分析が可能であること。

心理学の実験は基本的にはwebではやらない。それは参加者の環境が統一できないからだ。MATLAB+Psychtoolboxが主流で、Python+Psychopy(pygame)もある。しかしポータビリティは低い。GUIを使うプログラムはDockerで動かすのは不便で、できてもパフォーマンスに不安がある。

そこまで環境にこだわらない実験や、今回のように同じ部屋で多くのPCで実験を行うときにはポータビリティの高いwebプログラミングによる実験は便利だ。そもそもGUIを扱うノウハウやライブラリという点ではJavaScript、というよりブラウザという環境が優れている。

Hyperappでhtml要素の真偽属性を操作する

※この記事は『金麦 RICH MALT』を飲みながら書かれた。

hyperappのviewはh関数で作る。これは公式サイトのサンプルだ。

const view = (state, actions) =>
  h("div", {}, [
    h("h1", {}, state.count),
    h("button", { onclick: () => actions.down(1) }, "-"),
    h("button", { onclick: () => actions.up(1) }, "+")
  ])

h関数は3つの引数をとる。1つ目はタグ名、2つ目は属性を列挙したハッシュ、3つ目は子要素だ。ここで属性はハッシュなので、キーとバリューのペアが必要だ。しかし全ての属性がキーとバリューのセットで用いられるわけではない。たとえばreadonlyは値をセットする必要がなく、存在することによって機能する。

ここでHTML5の真偽属性の仕様を確認する。

checkedおよびdisabledとなるチェックボックスの例を示す。checkedおよびdisabled属性は真偽属性である。

<label><input type=checkbox checked name=cheese disabled> Cheese</label>

これは次に書かれるものと等価であるべきである:

<label><input type=checkbox checked=checked name=cheese disabled=disabled> Cheese</label>

スタイルを混在させることもできる。以下は依然として等価である:

<label><input type='checkbox' checked name=cheese disabled=""> Cheese</label>

どういうことかというと、以下の3つはどれでもいい(参考)。

  • disabled
  • disabled=disabled
  • disabled=""

つまり真偽属性を変化させたいときは以下のように書きわけることができる。

h("input", {readonly=""}, "")
h("input", {not-readonly=""}, "")

これをhyperappの枠組みで考えるとこうなる。

const state = {
  readonly: "readonly"
}

const actions = {
  writable: () => state => ({ readonly: "not-readonly" })
}

const view = state => h("input", { [state.readonly]: "" }, "")

viewのh関数の第2引数のハッシュで、ブラケット記法を用いてキーの方を変更する。バリューはなんでもいい。