サンダーボルト

相手モンスターを全て破壊する。

GraphQLでInt型引数にStringを渡しても動作するが、Bool型引数にString型を渡したらエラー

概要

Goで実装しているGraphQLサーバーがあって、そのQueryの引数にInt型とBool型を要求していて、それぞれ "1000" と "true" といった値を渡したときに、Intは正しく動いて、Boolは message: "cannot use string as Boolean" というエラーメッセージが返ってきた。

この違いは正しいのか?意図されている動きなのか?どこが原因なのか?を探った記事です。

環境

バージョン
Go 1.13.7
gqlgen 0.10.2

Intの引数にString型を与えてもうまく変換されていることの調査

gqlgenで生成されるgenerated.goから、ライブラリのソースコードをたどっていくとここへ行き着いた

github.com

UnmarshalInt関数の中で以下のように変換している。なるほど。

return strconv.Atoi(v)

意図してStringをIntに変換しているんですね。

BoolにString型を与えてもうまく動かないことの調査

エラーメッセージはmessage: "cannot use string as Boolean"だったのだが、まず気になったのはそもそも👆でintは変換しているので、boolでも同じようなメソッドがないかと思って調べてみた。そしたらあった!

github.com

return strings.ToLower(v) == "true", nil

ガッツリ変換してた笑

で、デバッグ止めようと思ってもここまでたどり着いてなかったんですよね。で、

github.com

このあたり👆にブレークポイント置いて調査してみたところ、、、見つけた!

github.com

gqlgenが依存している、gqlparserというライブラリの中でIntはStringを許可しているのに、BoolはStringを許可していませんでした。そして最初に見たエラーメッセージが生成されているのも確認できました。

この動作で正しいのだろうか?

と思ったので仕様書を見てみました。

Int

GraphQL

Input Coercion When expected as an input type, only integer input values are accepted. All other input values, including strings with numeric content, must raise a query error indicating an incorrect type. If the integer input value represents a value less than -231 or greater than or equal to 231, a query error should be raised.

Note: Numeric integer values larger than 32‐bit should either use String or a custom‐defined Scalar type, as not all platforms and transports support encoding integer numbers larger than 32‐bit.

数値を表す奴は数値型じゃないとエラーを出さなきゃダメ、Stringもエラー。 ・・・でも、全ての環境で32bit Intより大きい値を扱えるわけじゃないし、そういうデカい値扱うときは一旦Stringでも良い。

って感じでしょうか。なので、一旦Stringで受け取って、32bitより大きい値だったらエラーにする、という実装も許しているようです。

Bool

GraphQL

Input Coercion

When expected as an input type, only boolean input values are accepted. All other input values must raise a query error indicating an incorrect type.

Booleanしかダメ!という方針みたいです。

ということで、IntとBoolの動作の違いは仕様書通りの作りになっていると言えそうです。

なるほどなぁ〜

補足

  • 読む仕様書が違う
  • 読む部分が違う

とかあったら優しく教えていただけると幸いです🙇

Activity Log(2020年4月 第1週)

この記事は何?

技術に関する個人的な学びやアウトプットをまとめたもの。

インプット

特筆するものはなし。

アウトプット

ブログ

開発

  • FlutterのProvider, ChangeNotifier, StateNotifierあたりを勉強して、それらを使った最小構成サンプルを実装した

  • 個人で作成中のWishesアプリについて、ブロックしているユーザーのブロックを解除する機能を実装し、masterにマージした。

    • この機能の実装には、ProviderとStateNotifierを利用した。freezedは使っていないが、たしかにcopyWith等の便利メソッドが欲しくなってきたので導入を検討中
  • 同じくWishesアプリについて、管理画面がなく、ユーザー対応が面倒だったので管理画面の作成にとりかかり、とりあえずAngularMaterialを使って画面を表示するまで。
    • とりあえず素早くやりたかったのでAngular + Firebase Admin SDKを利用することにした。セキュリティ等も面倒なのでローカルPCで起動して利用する予定。

まとめ

  • 先週はインプット多めでアウトプット少なめだったが、真逆の内容になった。
  • 来週ももう作るものが決まっているのでインプット少なめでアウトプット多めになりそうな予感。

deep-equalを使うとエラー:Uncaught ReferenceError: global is not defined

概要

Angularで開発しているときに、オブジェクトの中身まで比較して等価かどうかを判定したくなり、調べると以下が出てきた。

typescript-jp.gitbook.io

これによれば、

このようなチェックを行うには、deep-equal npmパッケージを使用します。

とのことだったので、npm i deep-equal して利用してみたところ見事に Uncaught ReferenceError: global is not defined エラーが出たのでその調査と解決の記事。

環境

ライブラリ バージョン
@angular/core 9.0.6
deep-equal 2.0.1

エラー詳細

ログは以下の通り

index.js:3 Uncaught ReferenceError: global is not defined
    at Object../node_modules/has-symbols/index.js (index.js:3)
    at __webpack_require__ (bootstrap:79)
    at Object../node_modules/es-abstract/GetIntrinsic.js (GetIntrinsic.js:39)
    at __webpack_require__ (bootstrap:79)
    at Object../node_modules/es-abstract/helpers/callBind.js (callBind.js:5)
    at __webpack_require__ (bootstrap:79)
    at Object../node_modules/regexp.prototype.flags/index.js (index.js:4)
    at __webpack_require__ (bootstrap:79)
    at Object../node_modules/deep-equal/index.js (index.js:5)
    at __webpack_require__ (bootstrap:79)

エラーを吐いている has-symbols/index.js はこちら

github.com

var origSymbol = global.Symbol;

たしかに、globalを使おうとしてる。ログからもわかるんですが、deep-equalが依存しているようです。(npm ls has-symbols した結果👇)

f:id:nao_666:20200403120544p:plain
npm ls has-symbols

解決策

ググると、「とりあえず polyfills.tsに(window as any).global = window;って書けばOK😅」みたいなのが出てくるんですが、もう少しだけ、JS初心者向けに説明します。

まず、Uncaught ReferenceError: global is not defined と出てくるのはブラウザ上のJSエンジンにてglobalが無いということです。Chromeのconsoleでglobalと打てば同じエラーを再現できます。

f:id:nao_666:20200403120913p:plain
`global` をConsoleで実行した結果

で、どうしてこういうことが起きるかというと、has-symbolsというライブラリがNode.js環境にはあるglobalという変数が存在する前提で作られているが、ブラウザのJSエンジンにはglobalという変数が無いからなんですね。

で、そのglobalというものの実態はブラウザのグローバルオブジェクトである、windowと同じで良いらしい(ここきちんと調べてない😅)ので、それをglobalとして使えるように、window直下に配置してあげる必要がある、ということですね。

で、polyfills.tsはTypeScriptなので、

window.global = window;

ではglobalなんてフィールド無いぞ!ってエラーが出ますから、一旦anyにキャストしてあげて

(window as any).global = window;

ってのをpolyfills.tsに書けば良いという話ですね。

雑にConsoleで試すとこういうこと👇です。

f:id:nao_666:20200403122339p:plain

エラーが出なくなりました。

FlutterのProviderまわりについて整理したい

これは何?

Flutterでアプリを作っているのですが、今まで自分はsetState + StatefulWidgetしか使っていませんでした。で、色々調べたら今はProviderを使うのが良い感じらしいことが分かりました。

そして、Providerについて色々調べていると、

  • Provider
  • ChangeNotifier
  • ChangeNotifierProvider
  • StateNotifier
  • StateNotifierProvider

といった言葉が目に付きました。え、"Provider" だけでは状態管理は良い感じにならないの??Providerはライブラリ?クラス?単体としての役目は何??ってグチャグチャになってきたので一旦整理したいと思い書きました。

前提

バージョン

Flutter v1.12.13

言葉の整理

Provider

  • InheritedWidgetを便利に使えるようにwrapしたもの
  • 状態管理を特別いい感じに実装できるように作られたパッケージというわけではない
  • providerというパッケージがあり、その中にProviderというクラスが存在する

ChangeNotifier

  • 直訳すれば「変更を知らせる者」という通り、Listnerとかそこら辺の機能を持つクラス
  • Providerと組み合わせるといい感じに状態管理を実装できる
    • 「View」と「状態&ロジック」を切り離すことができる
  • これを利用するために外部ライブラリに依存する必要はない(Flutter内にある)

ChangeNotifierProvider

  • ChangeNotifierのために作られたProviderの一種(クラス)
  • providerパッケージの中に同梱されている

StateNotifier

StateNotifierProvider

  • StateNotifierのために作られたProviderの一種(抽象クラス)
  • こちらはproviderパッケージの中には入っておらず、flutter_state_notifierパッケージに入っている

まとめ

状態管理をいい感じにしたければ、ProviderとStateNotifier、そしてStateNotifierProviderを使っておけば良さそう。あとfreezedという奴もいるらしいがこれはまだ調べてない。

ChangeNotifierとStateNotifierを使った最小構成のサンプルを作ったのでよければ見てみてください。 こちらを見れば「View」と「状態&ロジック」になっているか、「View」と「状態」と「ロジック」と分かれているかがすぐわかると思います!

ChangeNotifierのサンプル

github.com

StateNotifierのサンプル

github.com

Activity Log(2020年3月 第4週)

この記事は何?

技術に関する個人的な学びやアウトプットをまとめたもの。 自分リリースノートなる取り組みに影響を受けているのである。

インプット

Flutter

npm

Angular

Linux

アウトプット

ブログ

開発物

  • 個人で作成中のWishesアプリの開発を再開し、ちょこっとソースを書き始めた
  • angular-cliをローカルでビルドしてそれを使ってng serveを実行できた
    • ngというファイルはVSCodeで実行してブレークポイントを止めることができた
    • しかし、まだビルドあたりのソースではブレークポイントは止められていない (angular devkitまわり?)
    • これをやる中でシバンやenvのトリック的用法を理解できた

まとめ

  • 久しぶりに個人アプリ(Wishes)の開発を再開した。それまでsetStateばかり使っていたのでprovider, state_notifier, freezedまわりを調査し始めた。
  • Angularをローカルでビルドしたangular-cliを使って ng serve したいという欲が出てきて、それをやっている内にシバン等を知れてよかった。
  • インプットばかりであまりソースコードを書けていないので次の週はたくさん書けるといいなぁ〜

FlutterでiOSまわりのビルドでつまったときの対応策

私はiOS弱者であるので、Podsのバージョンがグチャってなったり文法エラーが起きたり、ライブラリどうしで揉めてたらすごく困る。 なので、とりあえずどうすべきかをまとめておく。

その1

qiita.com

その2

XcodeからFlutterアプリを起動して、詳細なログを見る

その3

訳わかんなかったらとりあえずライブラリ全部最新にしちゃう

ほんとにPods嫌い...

「Go言語で作るインタプリタ」を完走した

この記事は何か?

www.oreilly.co.jp

の本編(第1章〜第4章)を読みながら実装して、実際にインタプリタを作ることができたので、本の内容や感想をまとめる。

リポジトリ

本に沿って実装してみたインタプリタを試せるリポジトリは以下です。 github.com

$ git clone https://github.com/nakir323/monkey
$ cd monkey
$ go run main.go
Hello YourName! This is the Monkey programming language!
Feel free to type in commands
>> let addFunc = fn(a, b){return a + b;}
>> addFunc(3, 4)
7

こんな感じで遊べると思います。

筆者の属性

  • 非情報系学部(理系)卒
  • プログラミング(Webエンジニア)歴8年
  • 経験した言語はJava, JavaScript, TypeScript, Dart, Go
  • Go歴は半年

なぜやろうと思ったか

  • プログラミング言語を比較するための観点が自分には不足していると思っていて、その観点が増えそうな気がしたから
  • そもそもプログラミング言語がどのように動いているかに興味があったから
  • アセンブリやメモリの使い方について詳しくなれそうだと思ったから
  • Go言語自体の勉強になりそうだと思ったから
  • 言語作ったことあるよって言ってみたかったから😅

とりあえず低レイヤーなことに興味があったのでした。 また、以前ある言語について「どうですか?」と聞かれたときに文法についてくらいしかまともに答えることができず、言語を評価する観点が少ないなぁと痛感したのでその強化もできるかな?と思いやってみました。 先に言っておくと、3つ目の

アセンブリやメモリの使い方について詳しくなれそうだと思ったから

この需要はこの本では全く満たせませんでした。その他の4つは満たせたと思います。

所要時間

2020/1/23に買って、2020/2/27に実装完了したので、だいたい1ヶ月くらい。 毎日1時間くらいやったかな?サボった日もあるのでよくわかりませんがだいたい30~40時間くらいな気がします。

内容について

章ごとのまとめ

1章:字句解析

この章では字句解析を扱う。字句解析というのは、12+345という文字列(プログラム)があったときに、

'12' '+' '345'

という3つのトークンに分けること。 fn(a,b){return a + b}だったら

'fn' '(' 'a' ',' 'b' ')' '{' 'return' 'a' '+' 'b' '}'

に分ける。つまり、文字列を前から読んで、これは識別子なのか?演算子なのか?キーワード(returnやfn)なのか?というのを判断していくことになる。 ここでは単に文節に分けていくような作業をするだけなので、文法的に誤っているかどうかのチェックはしない。

本に沿って実装すればこの字句解析ロジックはlexer.goというファイルに書くのだが、終始lexer_test.goを変更しながらテスト駆動で開発をしていく。進捗が少しずつ出るしどんどんできることが増えるようになるのが味わえて非常に楽しい。

1章の最後にはREPL環境を作る。つまり、lexer_test.goで動作確認をするだけでなく、実際にmain.goを実装してそれを実行することで対話型で字句解析器の動作を確認することができるようになる。イメージとしてはこんな感じ。実行後、let result = x + y;と入力してEnterを押したときの出力例。

$ go run main.go
>> let result = x + y;
{Type:let Literal:let}
{Type:IDENT Literal:result}
{Type:= Literal:=}
{Type:IDENT Literal:x}
{Type:+ Literal:+}
{Type:IDENT Literal:y}
{Type:; Literal:;}

2章:構文解析

2章では1章で作ることができたトークン列からAST(抽象構文木)を作成する。といっても非常に簡単で、トークン列になったものを文法に合わせて構造として保持するだけ。 例えばlet result = x + y;が先のトークン列になった後、構文解析をすると以下のような構造体になる

type LetStatement struct {
  Name Identifier // resultを表す
  Value Expression // x + yを表す
}

つまり、文法が決まるところと言っていい場所である。let x = +といった記述は1章ではエラーを吐かないが2章でエラーを吐くことになる。 2章ではreturn文とlet文の2つの文と、式を構文解析する。return文は上のlet文と同じくらいシンプル。問題は式の構文解析で、例えば add(a + b + c * d / f + g)を解析するとadd((((a + b) + ((c * d) / f)) + g))となる。つまり、3章で行う評価のために、評価順をここで決める。

=,+,-,!,*,/,<,>,==,!=といった基本的な演算子は全て網羅されている。

再帰を使ってガリガリ解析していく、結構骨の折れる章なのだ😅

3章:評価

ASTができたらそのオブジェクト(struct)を使って実際に評価を行う。最初はここで、アセンブリとかの話が出てくるのかな〜なんて思ってたんだけど全く出てこなかった。 単純に解析した構文をGoで実行して、Goに評価させるだけだった😅

まぁでも最初はそれくらいでもいいか(続編のGoで作るコンパイラにその辺載ってるらしいし)と思って読み進めた。 実際に一番動き出すのが味わえるのがここなので、ここまで来ればあとはすいすい進むと思う。

最初は数値と+や-を評価できるようになって、すぐに電卓として使える言語ができあがる。その後はifやreturn、関数を評価し一通り動く言語が出来上がる。

4章:インタプリタの拡張

ここまでで、一通り言語としては動くが、いくつか基本的な言語としての機能が足りていないので、monkey言語に機能追加をするのが4章の内容となっている。 これが非常に良い構成になっていると感じていて、ここで再度1~3章の字句解析、構文解析、評価を総復習する。そして、文字列、配列、Map、組み込み関数を実装する。

全体的な内容

文体がかなりフランクなのが特徴😅
おかたい感じの文体ではないし、話しかけてくれているような感じなので、ハンズオンの講座に参加している感覚。monkeyのソースは全て手で書いたけど、1つ1つ順を追って丁寧に教えてくれるし全然苦しくなかった。自分の場合は電子版を買ったのだが、途中からテストコードはコピペで済ますようになった。

本のレビューや書評では2章が難しいと書いてあったので結構ビビっていたけど、挫折するほどではなかった。でもたしかにかなり複雑で、めちゃくちゃprintを仕込んでデバッグしてなんとか雰囲気掴んだ、という感じ😅再帰が苦手な人だと厳しいかもしれない。でもまぁ構文解析ロジックを完全に理解するための本ではないのでもし理解できなくてもそこまで深刻に考えなくてもよいし、できないからといってやめちゃったら勿体ないです。本質はそこじゃない。

自分はコンピュータサイエンスを学生時代に学んだわけではないので、結構この本を買う前はビビっていたのですが、思っていたほど全然難しくなかったです。自分でも理解できたし、多少の言語機能を拡張するとしたらどの辺をいじれば良いのかイメージは湧いているので個人的には名著だなぁと感じています!

学んだこと

本を読んでもちろんインタプリタってどういう原理で動いているかは分かるのですが、それに付随して学んだことをいくつか。

文と式

今まで何回かググったことがあったけどたまにごっちゃになったりする言葉。今回これでもかという程これらを意識して実装したのでもう染み付いた😅普段の仕事ではifを使うが、monkeyではifなので、そういったところも理解を深めるのに役立った。

クロージャの仕組み

何回か勉強したけどこれも作ってみてよくわかりました。Wikipediaクロージャの説明は

引数以外の変数を実行時の環境ではなく、自身が定義された環境(静的スコープ)において解決することを特徴とする。

まぁそうなんだろうだけど、うーん😅という感じ。monkeyを作るときは評価の章にて関数オブジェクトを以下のように定義しました。

type Function struct {
    Parameters []*ast.Identifier // 関数の引数
    Body       *ast.BlockStatement // 関数の中身
    Env        *Environment // その関数から参照できる変数をまとめたMap
}

このようにEnvという環境情報を関数オブジェクトの中に保持しています。これこそクロージャを実現するためのもので、実装してみてなるほど、と腹落ちしました。

コンパイラも人間が作っているという事実

普通エラーが起きたりしたらどのファイルの何行目かって出ると思うんですが、monkey言語では表示されません。当たり前ですが、人がきちんと実装しているからそういった表示が出るんだよなぁと😅
単なるブラックボックスとして言語を見るのではなく、内部がどうなっているか少しイメージがつくようになったのは、結構大きいと思っています。

例えば、「○○言語って実行速度が速いらしいよ」という話を聞いたときに、今までなら「ふ〜ん、そうなんや😅」くらいで終わっていたものが、解析が速いの?評価が速いの?どういう評価方式なの?どういう工夫をしているの?と疑問が無限に湧いてくるようになったし、興味が湧くようになりました。

言語の観点

👆に似ている話かもしれません、。
今回はとりあえずシンプルな作りで言語を実装したけど、この本によれば、例えば評価のタイミングでAST を書き換える小さな最適化を行ったり(例えば使われてい ない変数束縛を削除する)、再帰や繰り返しを実行するのにより適した他の中間表現(IR; intermediate representation)に変換したりすることもあるらしい。

評価するのもGoに任せるパターンもあれば機械語に翻訳してしまう手もある。これから言語を見るときの観点(と興味)は間違いなく増えたと思う。

怯まない心

字句解析、構文解析、AST、パーサー。このあたりの言葉を聞いても怯まなくなりました✌

Goの文法について

ポインタレシーバや==による比較について、実装している中で不明点が出てきたので勉強しました。

ポインタレシーバ:The Go Playground
==による比較:The Go Playground

まとめ

買う前はめっちゃビビってたけど買ってよかった!

言語がそもそもどう動いているのか気になる、言語を評価する観点を増やしたい、って人にはおすすめです。ただ、読むだけだとそこまで理解できないと思うので、買うならきちんと実装するのが良いです。 難易度としては、とりあえずGoが書ける人なら完走は意外と易しいと思ってます。

続編に「Go言語で作るコンパイラ」という本があるのですが、英語版しかないのでかなりビビっています😅

compilerbook.com

でもいつかやってみたいです。

とりあえずインタプリタ本を完走できてよかった。お疲れ様でした🙇