経験は何よりも饒舌

10年後に真価を発揮するかもしれないブログ 

Jestのカバレッジ計測について

Jestのカバレッジ計測について、次のコードで調べてみる。

function sum(a, b) {
  return a + b;
}

function minus(a, b) {
  return a - b
}

module.exports = {
  sum: sum,
  minus: minus
}

sumだけテストしてみる。

const { sum } = require('./sample');

describe('sample', () => {
  it('sum', () => {
    expect(sum(1, 2)).toBe(3);
  })
});

結果は、Funcsが50%、Linesが66.66%になっている。
Funcssumminusのうちsumしかテストしていないので1/2で50%、行数は66%程度なんだな、と納得できる。

$ npm run test

> jest-playground@1.0.0 test
> jest --collectCoverage

 PASS  ./sample.test.js
  sample
    ✓ sum (2 ms)

-----------|---------|----------|---------|---------|-------------------
File       | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
-----------|---------|----------|---------|---------|-------------------
All files  |   66.66 |      100 |      50 |   66.66 |                   
 sample.js |   66.66 |      100 |      50 |   66.66 | 6                 
-----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        0.506 s, estimated 1 s

次のようなテストを書けば、予想通りカバレッジは100%になる。

const { sum, minus } = require('./sample');

describe('sample', () => {
  it('sum', () => {
    expect(sum(1, 2)).toBe(3);
  })
  it('minus', () => {
    expect(minus(2, 1)).toBe(1);
  })
});
$ npm run test

> jest-playground@1.0.0 test
> jest --collectCoverage

 PASS  ./sample.test.js
  sample
    ✓ sum (1 ms)
    ✓ minus

-----------|---------|----------|---------|---------|-------------------
File       | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
-----------|---------|----------|---------|---------|-------------------
All files  |     100 |      100 |     100 |     100 |                   
 sample.js |     100 |      100 |     100 |     100 |                   
-----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        0.642 s, estimated 1 s

次に、やむを得ない状況が発生して一方でNode.jsのassert module、一方でJestのExpectを使用している場合を想定する。

const assert = require("assert")
const { sum, minus } = require('./sample');

describe('sample', () => {
  it('sum', () => {
    expect(sum(1, 2)).toBe(3);
  })
  assert.equal(minus(2, 1), 1);
});

カバレッジを出してみると、予想に反して100%になる。
てっきりJestのExpectを使用していないと計測できない(50%になる)と思っていた。

$ npm run test

> jest-playground@1.0.0 test
> jest --collectCoverage

 PASS  ./sample.test.js
  sample
    ✓ sum (1 ms)
    ✓ minus

-----------|---------|----------|---------|---------|-------------------
File       | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
-----------|---------|----------|---------|---------|-------------------
All files  |     100 |      100 |     100 |     100 |                   
 sample.js |     100 |      100 |     100 |     100 |                   
-----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        0.642 s, estimated 1 s

つまり、次のようにテストをせずただ意味もなく呼び出しただけでもカバレッジは100%になる

describe('sample', () => {
  it('sum', () => {
    expect(sum(1, 2)).toBe(3);
  })
  minus()
});
$ npm run test

> jest-playground@1.0.0 test
> jest --collectCoverage

 PASS  ./sample.test.js
  sample
    ✓ sum (2 ms)

-----------|---------|----------|---------|---------|-------------------
File       | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
-----------|---------|----------|---------|---------|-------------------
All files  |     100 |      100 |     100 |     100 |                   
 sample.js |     100 |      100 |     100 |     100 |                   
-----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        0.527 s, estimated 1 s

次に、minusのテストが通らない場合を想定する。
双方にJestのExpectを使った場合はカバレッジは100%になる。

describe('sample', () => {
  it('sum', () => {
    expect(sum(1, 2)).toBe(3);
  })
  it('minus', () => {
    expect(minus(2, 1)).toBe(5);
  })
});
$ npm run test

> jest-playground@1.0.0 test
> jest --collectCoverage

 FAIL  ./sample.test.js
  sample
    ✓ sum (1 ms)
    ✕ minus (2 ms)

  ● sample › minus

    expect(received).toBe(expected) // Object.is equality

    Expected: 5
    Received: 1

       7 |   })
       8 |   it('minus', () => {
    >  9 |     expect(minus(2, 1)).toBe(5);
         |                         ^
      10 |   })
      11 | });
      12 |

      at Object.<anonymous> (sample.test.js:9:25)

-----------|---------|----------|---------|---------|-------------------
File       | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
-----------|---------|----------|---------|---------|-------------------
All files  |     100 |      100 |     100 |     100 |                   
 sample.js |     100 |      100 |     100 |     100 |                   
-----------|---------|----------|---------|---------|-------------------
Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 passed, 2 total
Snapshots:   0 total
Time:        0.572 s, estimated 1 s

次のようにminusでNode.jsでassertが通らない場合は、sumのテストが通るにも関わらず、カバレッジは0%になる。

describe('sample', () => {
  it('sum', () => {
    expect(sum(1, 2)).toBe(3);
  })
  assert.equal(minus(2, 1), 5);
});
$ npm run test

> jest-playground@1.0.0 test
> jest --collectCoverage

 FAIL  ./sample.test.js
  ● Test suite failed to run

    AssertionError [ERR_ASSERTION]: 1 == 5

       6 |     expect(sum(1, 2)).toBe(3);
       7 |   })
    >  8 |   assert.equal(minus(2, 1), 5);
         |          ^
       9 | });
      10 |

      at sample.test.js:8:10
      at Object.<anonymous> (sample.test.js:4:1)

----------|---------|----------|---------|---------|-------------------
File      | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
----------|---------|----------|---------|---------|-------------------
All files |       0 |        0 |       0 |       0 |                   
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 failed, 1 total
Tests:       0 total
Snapshots:   0 total
Time:        0.599 s, estimated 1 s
まとめ
  • JestはJestのAPIを用いたテストに対してカバレッジを計測しているのではなく、テストで呼び出されたか否かでカバレッジを計測している。
  • Node.jsのassert moduleを使ってテストが通らなかった場合は、カバレッジの計測は機能しない。
  • Stmts, Branchは追って調査していく

I contribute to denoland 1 month in a row


Summary

https://github.com/pulls?q=involves%3Awafuwafu13+-user%3Awafuwafu13+author%3Awafuwafu13+org%3Adenoland

  • 24PR Merged
    • deno_std
      • 5feat
      • 1fix
      • 6chore
      • 6test
      • 4docs
      • 1refactor
    • deno_lint
      • 1test
  • 17/311 Contributers in deno_std


What I'll do

:+1: :-)

GCI 2021 Summer 修了した

今までWebアプリケーションには関わってきたけど、機械学習に関しては無知だったのでGCI 2021 Summer | 東京大学グローバル消費インテリジェンス寄付講座に参加してみた。
申し込みしたら誰でも参加できる感じで門戸は広く、修了した人は5人に1人くらいだった。
週1の課題(満点)と2回のコンペと最終課題を提出したら修了できた。
講義と資料の質はめちゃくちゃ高くて、自分は使わなかったけど授業外の個別サポートもあったから無料にしては体験が良すぎた。
難易度は、講義の内容を補足まで理解しようと思ったら難しかったけど、宿題をこなすにはちょうどよかった。
講義と宿題合わせて毎週3~5時間、最終課題は丸2日くらいかけてやった。
コンペに関してはKaggleの経験が皆無だったけど、ヒントとか他の人のコードが見れる機会があったから意味を理解して提出はできた。
Numpy/Pandas/Matplotlib/教師あり学習/教師なし学習/SQL/仮想化・クラウド基礎/MLOps基礎/マーケティング基礎、応用の一部 を1回の講義を受けて進めていったので、機械学習を理解できたというよりその上部を一周できたという感じだった(とても満足)。
AWS EducateでAWSもいじれた。
ゲスト講師の回もなかなか話が面白かった。
ちなみに最終課題はこれを提出した(マサカリは許さない)。
https://www.canva.com/design/DAEk_g1dwT4/a2fodMbJli3y_5hnwteDlQ/view?utm_content=DAEk_g1dwT4&utm_campaign=designshare&utm_medium=link&utm_source=publishsharelink
修了した後は特に個人間のやりとりはないが、修了生のSlackがあってそこに松尾研が関わっている会社のインターン募集とかが流れている。
個人的には金融輪読会に興味があってだいぶ前に応募したつもりが反応がないのでこれからアプローチしたいと思っている。
サマースクール2021 深層生成モデルにも応募して現在も講義が続いているが、難易度が急激に上がって何もわからないから離脱して最近は数学の独学をしている。

サイボウズのフロントエンドエキスパートチームのインターンに参加しました

8/30日から9/3日の5日間、申し訳ございませんお探しのページが見つかりませんでしたに参加させていただきました。

参加するまで

フロントエンドエキスパートチームへの強い興味のきっかけは、おそらく2021年1月20日にこの記事を読んだことだと思います(もっと前に知ってはいただろうけどいつから知っていたということを覚えていない)。
大規模 Closure Tools プロジェクトに Prettier を導入するまでの道のり - Cybozu Inside Out | サイボウズエンジニアのブログblog.cybozu.io
この記事の次の部分を京阪三条駅に着きかける電車の中で読んで驚いた記憶があります。

偶然にも私は Prettier のメンテナーでリリースを担当しているので、リリースとそのための作業を業務時間に行い迅速にリリースをすることができました。

自分が初めて簡単なPRをOSSに投げたのが2020年11月3日なので、OSSに少し興味はあったものの、こうしてOSSの世界でバリバリに活躍している同年代の方がいるとはっっという感じでした。
今までずっとフロントエンドだけをやってきたわけではないので、そこからは会社でフロントエンドを書いたり書かなかったり、フロントエンドマンスリーを見たり見なかったりという生活をしていました。
が、OSSへの憧れと単純な楽しさが相まってOSSJavaScriptを書くことは継続してやっていました。
そしてまさかのインターン募集があったので、迷わず第1志望で提出しました。
通るとは思っていなかったですが、書類と1時間の面接だけで参加が決まったので、簡単にでもOSSしててよかったです。

参加してから

ざっくり言うと「技術的負債の解消段階で生じた問題を、構成を変えることにより解消するため、技術選定から導入、実装、他チームへの共有、ADRの作成まで」をしました。
フルリモートで、社員さん含め5,6人くらいが常時同じzoomにいて、インターン生2人がドライバーを交代しながら作業すると言う形でした。
モブプロが初めてだったので、ドライバーをする際、1人で考え込んでしまう時間があって難しい部分はありましたが、全体を通じて他の方から得れる部分が多々あった(というかほぼそれだった)ので、とても体験が良かったです。
あとはそもそも自分の知識が足りないところがあった(特に技術選定時にNXとかRushが出てきた時や、このOSSではこう使われているから...とかの会話はついていけなかった)のでまだまだ甘い!!!と感じました。
業務以外にもイベントがたくさんあって楽しかったです。
単純に普段からフロントエンドの先頭を走ってる方々の話を聞くのもそうですし、フロントエンドマンスリーにも参加させていただいたのは貴重な機会でした(業務時間外にもPrettierで関われた)。
フロントエンドエキスパートチームだけではなく、他のチームの業務内容や連携を知れたのもよかったです。

参加した後

フロントエンドが大好き、かつ世界の動きに置いていかれたくないので、もっとアンテナの強度を高めていきたいと思います。
社員の方々ももちろんですが、もう1人のインターン生の方の知識というか感性に敵ってない予感がしているので良い刺激になっています。
特にフロントエンドのエコシステムにまつわるOSSが好きなので、関われた方々と同じ世界線で生きていけるように色々頑張りたいです。
貴重な5日間に関わってくださった方々、ありがとうございました!!!

非推奨になったESLintのルールがESLintのコード上でそう記されているか確かめるESLintのテストを書いた

ESLintのレポジトリでは、lib/rulesディレクトリ配下にルールがまとまっている。
以下に記されているように、それらのルールは非推奨になることがあるが、削除されることはない。
Rule Deprecation - ESLint - Pluggable JavaScript Linter

Balancing the trade-offs of improving a tool and the frustration these changes can cause is a difficult task. One key area in which this affects our users is in the removal of rules.
...
Rules will never be removed from ESLint.

非推奨となったルールは、そのコードとドキュメントにその旨が記されている。
例えば、ESLint v7.0.0で非推奨となったcallback-returnというルールでは、lib/rules/callback-return.jsdocs/rules/callback-return.mdでそれぞれ以下のような記述がある。

/**
 * @fileoverview Enforce return after a callback.
 * @author Jamund Ferguson
 * @deprecated in ESLint v7.0.0
 */
...
This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-node`](https://github.com/mysticatea/eslint-plugin-node).

しかし、コードとドキュメントにこのようにきちんと記されているか確認するフローがなかったため、記述が抜け漏れている箇所があった。
非推奨になっているルールの一覧が Rules - ESLint - Pluggable JavaScript Linter にあるので、それを目視で確認しながら修正していった。
Chore: Update deprecated information by wafuwafu13 · Pull Request #14961 · eslint/eslint · GitHub
このPRで、「非推奨になったルールのコードに@deprecatedタグがあること」、「README.mdThis rule was deprecated...の記述があること」を確かめるテストがあればよいというレビューがあった。

「目的のファイルに目的の記述があること」は、ShellJSのcatと正規表現を使って、すでに同じようなテストをしている箇所があったので書けそうだった。
eslint/Makefile.js at 80cfb8f858888bddfefd7de6b4ecbf5aabe267bc · eslint/eslint · GitHub

function hasIdInTitle(id) {
    const docText = cat(docFilename);
    const idOldAtEndOfTitleRegExp = new RegExp(`^# (.*?) \\(${id}\\)`, "u"); // original format
    const idNewAtBeginningOfTitleRegExp = new RegExp(`^# ${id}: `, "u"); // new format is same as rules index
    
    return idNewAtBeginningOfTitleRegExp.test(docText) || idOldAtEndOfTitleRegExp.test(docText);
}

問題は、「ルールが非推奨になっていること」をどのように定義するかだった。
先ほど述べたように非推奨になっているルールの一覧はRules - ESLint - Pluggable JavaScript Linterにあるのだが、それをfetch等でとってくるのは、コードが煩雑になりそうなのと、ルールの一覧のフォーマットやページが変わることによってテストが落ちるので避けたかった。
非推奨になったルールの一覧を写したjsonファイルを作る手もあったが、そのjsonがきちんと更新されなければ元も子もない。
ESLintのリリースフローはよく知らないが、このルールの一覧もコードから生成されているため、どこかにフラグがあるに違いないと思い探していると、メンテナーのPRが見つかった。
Update: deprecate Node.js & CommonJS rules by kaicataldo · Pull Request #12898 · eslint/eslint · GitHub
そこでは、各ルールのコードで書かれているドキュメントのような箇所でdeprecated: true,が追加されていた。

module.exports = {
    meta: {
        deprecated: true,
        ...

これはおそらくリリースフローに組み込まれているため、非推奨のルールが抜け漏れている心配はない。
よって、以下のようなコードで、非推奨になったESLintのルールがESLintのコード上でそう記されているか確かめるテストがかける。

function hasDeprecatedInfo() {
    return 
        「非推奨になったルールのコードに@deprecatedタグがあること」 && 
        「README.mdにThis rule was deprecated...の記述があること」
}

if (ruleDef.meta.deprecated && !hasDeprecatedInfo()) {
    エラー++
}

今朝approvedされた。
Chore: Add test that deprecated rules display a deprecated notice by wafuwafu13 · Pull Request #14989 · eslint/eslint · GitHub

ミクシィのインターンで みてね の開発に幅広く関われました

https://mixigroup-recruit.mixi.co.jp/recruitment-category/internship/5071/ に8/2~8/27の約1ヶ月間参加しました。
選考は、インフラ/SRE領域に普段あまり触れていなかったため、その枠にチェックを入れて書類を提出しました。
3回の面接を通じて、今まで自分がやってきたこと等も考慮し、みてね のアプリ開発チームを主体に、SREチームにも関わるという形での就業となりました。
勤務形態としては前半2週間が東京、後半2週間が京都、前半3週間がアプリ開発チーム、後半1週間がSREチームという形で動きました。

mitene.us

アプリ開発チーム

アプリ開発チームでは複数のプロジェクトが同時進行で動いていて、それぞれのチームでiOS, Android, Railsの開発を進めていくという体制が取られていました。
自分はその中のひとつのプロジェクトに入って、そのプロジェクトの機能を少し拡張するために、モデルの関連の追加、管理画面の作成、今後の方針を立てる、ということをしました。
時間あたりの作業量は普段より少なく、難易度も至って普通でしたが、単独したタスクや既存の改善ではなく、新しくプロジェクトが進んでいる段階でモデルの作成から入る経験は少なかったため、開発のフローやスピード感がリアルに感じれました。
メンターの方が2年目だったため、仕事のイメージも湧きやすく、直接話も聞けたのでよかったです。
モバイルの画面を作ったりすることはなかったですが、リファクタリングのタスクが落ちていたので、方針に沿ってリファクタ作業もしました。
Swiftを実務で使うのは初めてで、設計まで踏み込んで読めたのはよかったです。
モバイルからサーバーまでやっているチームと聞いて、主にモバイルをやってサーバーはちょっとした変更に留まっているようなイメージを持っていましたが、DB設計からモバイルのアーキテクチャ設計まで思っていたよりガッツリやられていました。
1ヶ月という期間もあり、自分は0=>1のDB設計の段階の少しに関わったり、あらかじめ方針が固まったリファクタをしたに過ぎないですが、普段から両方に目をやれる体制が自分には合っているなと感じました。
チーム内でiOS, Android, Railsの足並みを揃えるのもスムーズにやられているように感じたのと、デザインに関しては みてね のチーム全体でより良いものに改善していく取り組みがなされていたので、そこの体制も良いなと思いました。
あと単純にどんどん新しい施策が0から生まれていってる環境は楽しいなと感じました。

主なリファレンス

SREチーム

1スプリントを みてね のSREメンバーと一緒に朝会から参加させていただきました。
アプリ開発チームにいたときには見えなかったタスクの内容や動き方が知れてよかったです。
インフラに普段あまり触れていないのとチームのレベルがとても高い分、人や技術に対して尖った印象があったのですが、温かく迎え入れてくださり、終始ペアプロもしてくださったのでとても助かりました。
タスクの内容としては、「IP不足の問題が起きたことがあったため、EKSのCNI Metrics Helperを使って、割り当てられた IP アドレスの数と使用可能な IP アドレスの数を可視化する」ということをしました。
リファレンス通りにコマンドを打てばいいという訳ではなく、TerraformやHelmで管理しているレポジトリにアプローチをしなければならず、メンターの方にとてもサポートしていただいてタスクを進めていきました。
短い期間でしたが、TerraformでのIAM作成やArgoCDでのデプロイ、Grafanaでの可視化を一通りやることができて楽しかったです。
実務でKubernetesが動いている環境を触ったことが初めてで、アプリ開発の裏で何がどう動いているのかを確認できてよかったです。
高い技術を駆使して増え続けるユーザーを支えているチームには憧憬があります。

主なリファレンス

おわりに

働き方やタスクも柔軟に相談して決めることができたのでありがたかったです。
みてね の方々はもちろん、人事や労務の方々にも大変お世話になりました。
不満は特になく、あえて挙げるなら参加が決まってから現場に入るまでの連絡がSlackではなくて電話主体だったのでそわそわしたくらいです。
みてね に関しては普段過ごす上では聞いたことはなかったですが、海外展開も進んでいて1000万ダウンロード突破している温かみのあるアプリでした。
Slackにアプリの評価が流れてくるチャンネルがあり、そこを眺めるとモチベーションが上がるような環境でした。
そもそもミクシィという会社についてはmixiとモンストのイメージが強かったですが、選考に進むにつれてミクシルなどの記事を読む機会が増え、思ってたよりいいサービスが多いなーということに気づきました。
みてね だけに限ってもそれぞれの部署で強みのある方が多数おられて、技術的にもおもしろいなと思っています。
1ヶ月間ありがとうございました!

『Go言語で作るインタプリタ』をTypeScriptで実装する(後編)

前編を書いたのが約1年前なので1年間かかってしまった。

wafuwafu13.hatenadiary.com

github.com


ずっとやってたわけではなく、前回は2章までできたらいいかって感じで終わらせて、1年越しに再開してみた。
Commits · wafuwafu13/Interpreter-made-in-TypeScript · GitHub


前回のブログの、「字句解析器(レキサー)」で述べている通り、基本的にはGoのStructをJavaScriptのclassに置き換えるとうまくいくけれど、詰まったところとかもあるので整理しておく。

まずは、新たに『Monkeyソースコードを評価する際に出てくる値全てをObjectで表現する』(p.121)ために作ったObjectインターフェースの差異からみていく。
これは前回と同様にクラスで構築していけばいい。

const(
    STRING_OBJ = "STRING"
)

...
type String struct {
	Value string
}

func (s *String) Type() ObjectType { return STRING_OBJ }
func (s *String) Inspect() string  { return s.Value }
export const STRING_OBJ = 'STRING';

...

interface StringProps {
  value: string;
}

export class String<T extends StringProps> {
  value: T['value'];

  constructor(value: T['value']) {
    this.value = value;
  }

  inspect(): string {
    return this.value;
  }

  type(): ObjectType {
    return STRING_OBJ;
  }
}

呼び出しの際、例えばevaluatorのevalStringInfixExpression関数では、Goではこうなっているが、

return &object.String{Value: leftVal + rightVal}

今回はコンストラクタを返せばいい。

const evalStringInfixExpression = (
  operator: string,
  left: any,
  right: any,
): any => {
  if (operator != '+') {
    return new Error(
      `unknown operator: ${left.type()} ${operator} ${right.type()}`,
    );
  }
  const leftVal = left.value;
  const rightVal = right.value;

  return new String(leftVal + rightVal);
};

evalatorのEval関数では、Goの場合はType switchesを使ってast.Nodeが保持している型によって処理を分けているが、

func Eval(node ast.Node, env *object.Environment) object.Object {
	switch node := node.(type) {

	case *ast.Program:
		return evalProgram(node, env)

	case *ast.ExpressionStatement:
		return Eval(node.Expression, env)
        ....

ast.tsではそもそもNodeインターフェースを定義していない。
今回の場合はnode.constructor.nameでクラス名を見てやることで解決する。

export const Eval = (node: any, env: Environment): any => {
  switch (node.constructor.name) {
    case 'Program':
      return evalProgram(node, env);
    case 'ExpressionStatement':
      return Eval(node.expression, env);
    ...

次に、evaluatorのevalInfixExpressionでoperatorが"=="の際、Booleanオブジェクトを返す処理があった。

case operator == "==":
    return nativeBoolToBooleanObject(left == right)

TypeScriptの場合は、デバッグ結果が一見同じで、true判定して欲しいにも関わらず、false判定がなされてうまくいかなかった。

if (operator == '==') {
     console.log(left, right); // Boolean { value: false } Boolean { value: false }
     return new Boolean(left == right);
}

Goの場合は、本にも書いてある通り、『ここで真偽値の等価性を確認するためにポインタの比較を使っている。これでうまくいくのは、私たちはオブジェクトを指し示すのに常にポインタを利用していて、かつ真偽値に関してはTRUEとFALSEの2つを使っているからだ...』(p.140)というようにポインタを使っているのでうまく判定される。
一方JavaScriptの場合は、異なるコンストラクタの同値判定はfalseになる。

class BooleanClass {
    constructor(value) {
        this.value = value
    }
}

const a = new BooleanClass(false);
const b = new BooleanClass(false);

console.log(a,b); // BooleanClass { value: false } BooleanClass { value: false }
console.log(a == b); // false


なので、判定の際はvalueまでみにいって判定した。

if (operator == '==') {
    return new Boolean(left.value == right.value);
}

p.171から、『このインタプリタはGoのガベージコレクタ(GC)を使っている』という記述があったが、今回も何も意識せずにJavaScriptGCを使っていることになる。
メモリ管理 - JavaScript | MDN

4.4の配列の実装をしている途中で、何が原因かわからないバグにも遭遇した。

結果的にはparserのparseIndexExpression関数の第一引数にt: Tokenを足したらなおった。
なおった理由もよくわかっていない。


最後に、p.207で配列の独自のビルトインとしてrestを構築する際、新しく割り当てられた配列を返さないといけなかった。

newElements := make([]object.Object, length-1, length-1)
copy(newElements, arr.Elements[1:length])
return &object.Array{Elements: newElements}

Goの場合はcopyで配列のdeep copyはできるけれど、JavaScriptの場合は調べてみると結構やっかいだった。
What is the most efficient way to deep clone an object in JavaScript? - Stack Overflow

結果的にJSON.parse/JSON.stringifyを使えばうまくいった。

const newElements = JSON.parse(JSON.stringify(args[0].elements)).slice(
    1,
    length,
);

return new Array(newElements);

本にはテストコードがなかったけど、ちゃんとかいた。

it('testRest2', () => {
    const input = `let a = [1, 2, 3, 4]; rest(rest(rest(a)));`;
    const evaluated = testEval(input);

    expect(evaluated.elements.length).toBe(1);
    expect(evaluated.elements[0].value).toBe(4);
});


という感じでGoとTypeScriptの差異に躓きながらも最後まで実装を進めていった。
本の最後のハッシュの章がまだバグがあって動かないから、また1年後にでもやるかもしれない。

1年前のコードを読みはじめたとき、よくこんな難解なコードを書いていたなと思ったから、コードを追う能力とかは1年前とさほど変わっていないと実感した。
今回やったのは、前回構築したParserを元に評価する、ということだから前回ほど大きな学びはなかったけど、いい復習になったし、言語の差異の壁を自力で乗り越えて本を1冊やり切ったことは自信にもなった。
ASTがいろんなところに使われていることも認識できるようになってきた。
graphql-validation-complexity から学ぶGraphQLのAST走査 - 経験は何よりも饒舌
が、まだGoやTypeScriptの仕様そのものがどう構築されているのかとか、GCとかまだよくわからないことの方が多いので後々詰めていけばいいと思っている。