yn2011's blog

技術メモ

そもそも reflection とは何なのか

Go 言語を勉強していて reflection って何なんだろ、となったので調べた内容を書いておく。

そもそも reflection とは何なのか

wikipediaによると

情報工学においてリフレクション (reflection) とは、プログラムの実行過程でプログラム自身の構造を読み取ったり書き換えたりする技術のことを指す。

よく分からない。以下では reflection の概念について書いていく。(Go 言語の reflection パッケージ固有の話ではない)

自己書き換えコード

プログラムの実行過程でプログラム自身の構造を読み書きするというのはどういうことか。1つの例として自己書き換えコードがある。

プログラムはプロセッサが処理可能な機械語に変換されて動作する。例えば C言語の場合だと以下のような変換になる。

C言語 -> アセンブリ言語 -> 機械語

より変換の下流に位置している、アセンブリ言語機械語のコードの内容をプログラムから書き換えることができれば、プログラム(ソースコード)に記述された処理とは見かけ上異なる処理*1を実行させることができる。そのようなプログラムを自己書き換えコードと呼ぶ(らしい)

例えば、C言語アセンブリ言語で書かれたプログラムで命令が格納されているメモリを直接操作すると、その前にプログラム内で書かれた命令は意味をなさなくなる。具体例としては、自己書き換えコード(self-modifying code) 等を参照。

自己書き換えをしたいモチベーションとしては色々あるみたいで自分は低レイヤーな部分に明るくないのであまり理解できていない。とりあえず wikipedia には色々載っていた。

Brian Cantwell Smith は、このアセンブリ言語の自己書き換えコードの発想(メタプログラミング)を高級言語にも導入しようとした。これが reflection という概念の元になっているらしい。(Procedural reflection in programming languages がその論文っぽい。読んではない。)

reflection はメタプログラミング

上述のような背景があるので、メタプログラミングをしたいから reflection がある、と考えても良さそう。

メタプログラミング

ところでメタプログラミングとは何か。定義は色々ありそうだが、メタなプログラミング、プログラムをプログラムする(ある言語がある言語を読み書きする)というイメージで良いっぽい。

例えば eval に渡す文字列を組み立てるプログラムは、まさにプログラムをプログラムが書いているのでメタプログラミングの1つになると思う。 また、SQL の組み立てもそうだし、TypeScript も JavaScriptメタプログラミングになると思う。

先程の自己書き換えコードがメタプログラミングに含まれるかは微妙かもしれない(C言語アセンブリ言語(機械語)を読み書きしていると言えるのかもしれない)

reflection を実装し、プログラム自身の構造を読み書きしたい背景として、このメタプログラミング(特に、操作する言語と読み書き対象の言語が一致している場合)に役立つからというものがあると思う。 自分は reflection の概念を Go 言語で初めて知ったが、他の多くの言語にも実装されている。例えば JavaJavaScript にもある。

なぜメタプログラミングしたいのか

じゃあなぜメタプログラミングしたいのか。特に、操作する言語と読み書き対象の言語が一致しているメタプログラミングは何が目的なのか。

TypeScript 等、別の言語を生成するならまだしも、なぜ同じ言語を取り扱うメタプログラミングが必要とされるのか。

実行時にしか分からない情報を取得したい・その情報を使った処理を行いたい

例えば、Go の場合は以下がある。

  • interface 型の変数の値の型*2
  • 構造体タグ (タグ名や値は任意の値を設定でき、構文がおかしい場合は実行時エラーになる)
  • json 等の外部データ (を Go の型システムの世界に変換したい)

また、ジェネリックに近いことも reflect パッケージを使えば実現できる(コード例はWho needs generics? Use ... instead!の 5. Use reflection を参照)

ジェネリックのためにreflect パッケージを使うとそれなりに面倒な感じにはなるっぽいが、元々の言語仕様にないことを実現できてしまうのはメタプログラミングの力だと思う。

Go 言語の reflection について

色々と reflection の概念について書いてきた。実際の Go の reflect パッケージの取り扱い方については以下の資料が役に立ちそうなのでリンク。

その他参考

*1:プログラムは記述された通りにしか動かないのであくまで見かけ上

*2:一応 reflect を使わなくてもtype assertion で特定の型かどうかの判定はできる

Go の型システム周りについてのメモ

Go の型システムについて、今理解していることを書く。もしかしたら間違っているかもしれない。

Go の型システム

  • 型同士に階層がない(サブタイプ・スーパタイプのような関係がない)
    • 型は名前によって区別される。階層がないので、ある型が要求された場合にその型のサブタイプが許容されるようなことはない。
    • ただし、例外としてinterface 型の解決は形状(実装するメソッド)が一致していれば許容
  • 型は named type と unnamed type に分けられる

型の割当可能性

同一の型同士の割当は以下のルールに従う。

同一の型とは、以下を満たす型を指す。

  • named type 同士は名前が一致している(したがって型の実体が一致している)
  • unnamed type と named type は型の実体が一致している
    • unnamed type 同士も同様

以下の表では、同一の型同士であっても割当不可な場合について☓を記載している。

\ named type unnamed type
named type
unnamed type

(例) 2行目3列の場合は☓なので割当できない

// named type to unnamed type
hoge1 := "hoge1"
type a string 
var hoge2 a
hoge2 = "hoge2"
hoge1 = hoge2 // error : cannot use hoge2 (type a) as type string in assignment

ただし、interface 型の場合は例外で形状(実装するメソッド)が一致しているかどうかだけが問題になる

(例)

type c struct{}
func(p c) hoge(){}

func main(){

type hoge interface{hoge()}
var b1 hoge
var b2 interface{hoge()}

b1 = c{} // ok , named type to named type
b2 = c{} // ok , named type to unnamed type
}

複雑な型について

例えば、map[string]interface{}map[string]int は割当可能か?

型に階層関係の存在する言語(型システム)に慣れていると、これは割当可能ではないかと感じるが Go の型には階層関係がないので割当できない。互いに独立した無名型でしかない。

例えばTypeScriptの場合は map 型は以下のように割当できる。

const a = (b : Map<string, any>) => {};
const c = new Map<string, number>([['hoge', 1]])

a(c); // ok

TypeScript は、複雑な型(オブジェクト、クラス、配列、関数の戻り値)等はそのプロパティに関して全て共変である(=サブタイプ or 同じ型を要求すうる)とのこと*1

Go の場合は型に階層自体がないので、変性という概念もないのかもしれない。

Go は duck typing (あるいは structural typing) なのか

そもそも duck typing とは何か、その明確な定義があまりよく分かっていない。duck typing は動的型付言語に対して言う言葉らしいので、duck typing ではなく structural typing という言葉が正しいらしい。

例えば TypeScript は structural typing と言われ、いわゆる named type (名前型) はサポートしていないので以下が通ってしまう。

type a = string
const b = (c :a) => {};
b('hoge'); // ok 

Go は interface 型については structural typing だが、上でも書いたとおり名前型が使えるという点では完全な structural typing な言語とは言えないのでないか? その言語が structural typing である、というとき、それはどういう意味で使われているんだろうか?

Does Golang use structural typing, nominal typing, or both?

上記の回答によると、Go に関しては両方ということになるらしい。まあ確かにそうである。

これも Go が行っている試みの1つなのかもしれない

There are many things in the Go language and libraries that differ from modern practices, simply because we feel it's sometimes worth trying a different approach.

Why does Go not have assertions? より引用

参考

Named and Unnamed Types

*1:プログラミングTypeScript P122

シェルスクリプトの任意の箇所で git の branch 名をあいまい検索するエイリアスが便利

環境

モチベーション

git pushgit checkout など、branch 名を タイプしないといけないときに正確に branch 名を入力するのが面倒くさい

結果

B と書かれている箇所にあいまい検索で見つけた branch 名を挿入する

f:id:pokuwagata:20200523224610g:plain

設定

.zshrc に以下のエイリアスを置く

alias -g B='$(git branch -a | fzf)'
  • fzf のインストールは必要
  • -g オプションでコマンドの先頭以外でもエイリアスが効くようにする

その他

remote にしか branch がない場合は git checkout -t B すると 対象を upstream に指定しつつローカルに同名の branch を作れる

GraphQL と Apollo Client / Server を学ぶためにやったこと

GraphQL と Apollo Client / Server を学んだので何をしたかを書いておく。

GraphQL

読んだ本

最初に「初めての GraphQL」で概要を掴んだ。ちなみにこの本は Apollo Client / Server についても書かれている。

併せて、設計パターンや実際の開発で課題となるテーマを把握した。(最初は気づかなかったが、設計ガイドは一部が「初めての GraphQL」 の付録にもなっている)

実装

「初めてのGraphQL」には簡単なWeb アプリを開発するチュートリアルがあるので、それを写経した。多分不完全。

GitHub - pokuwagata/photo-share-api

感想

  • GraphQL + TypeScript はフロントとバックエンドの分業に良さそう。ただし適切なschema の設計と継続的な改善が必要で、経験がないと辛いことも多そう。
  • クエリの書き方をよく忘れる

Apollo Client / Server

GraphQL の実装として、Apollo Client / Server がデファクトらしいので GraphQL を学ぶなら Apollo も必須となる。

読んだ本 / ドキュメント

Apollo については「初めての GraphQL」で概要は掴んだが、実際に手を動かすときに公式のドキュメントを参照することが多かった。

apollo-tooling は個人的に苦手な codegen という schema と クエリから TypeScript の型定義を生成するコマンドと格闘したためよく読んだ。

実装

既存のポートフォリオに無理やり BFF 層を追加して、一部のリクエストを GraphQL に置換してみた。logging も少し試した。

0 → 1 を少しやってみたかったので所謂 todo-mvc を 実装しようとした。単純に全てのリクエストを Apollo Server で処理するだけだとあまり面白くないので、 apollo-client で local state managemet を試してみた。

Client / Server の両方で schema を定義して TypeScript の型定義を生成して... という流れを経験できた。そして apollo-client の local state management は辛いという結論に達して挫折した。

あと Graph Manager というサービスも触ってみた。課金しないと全然試せることが少ないけど、本番運用するなら必須の機能が簡単に使えるっぽい。

感想

graphql-code-generator は TypeScript 使うならほぼ必須。linter は linter としての機能というより error が分かるのが嬉しい。VSCode をちゃんと設定すればエディター上でも分かるようになるんだろうか。

graphql-modules は良さそうだけど、設計がこの ライブラリ にロックインされそうな印象がある。

graphql-config は使っていないが、GraphQL 関連のライブラリとかエディタ毎に設定ファイルを独立して書かなきゃいけない傾向があり、辛いので一括で管理できるようにしてくれるっぽい。確かに設定ファイルどんどん増えてくるのは分かる。

  • schema と 型定義の自動生成により、関数型プログラミングのスタイルで開発できる。

    • 最初に問題を解決するための入力と出力の型を定義して、その型を満たす実装を埋めていくというスタイルの開発が容易に行える(少なくともApollo Server側は)
    • また、型定義を元に VSCode が補完・エラー表示してくれないと Reactと Apollo を使って実装していくのはそもそも現実的なのか?という印象もある
  • React の Component に GraphQL のクエリを紐付けて管理するのはコンポーネント指向感あって良い。ただし複雑な Web アプリケーションだと色々辛い部分もあるんだろうな...

  • 正直 GraphQL の導入は Apollo Server の実装よりもフロント側の方が考えなきゃいけないことが沢山あって大変そうという気持ちになった(まあ今回のサーバー側は REST API の中継しかしてないからかもしれないけど...)

AtCoder Beginner Contest に参加し始めた

AtCoder が毎週開催している AtCoder Beginner Contest (以下ABC)に参加するようになって 1ヶ月程度経ったので何かその辺りのことを書いておく。

今の成績

Rating 132 の灰色 f:id:pokuwagata:20200505212852p:plain

なぜやるか

  • 新しい趣味が欲しかった
  • 実装力の強化
  • 有給消化 + GW で25連休中のため比較的時間がある*1

始める前の実力

コンテストに関係しそうなやつだと

  • 競技プログラミング未経験
  • 高校数学は大体分かる(ただし整数問題は分からない*2)、大学の数学はほぼ忘れた
  • 電子・情報系の学部にいた。ただしコンピュータ・サイエンスはそんなに身に付いていない*3
    • 計算量・ビッグオー記法の概念・数学的な定義は何となく分かる
  • 業務でコード書くエンジニアとしての経験は3年ぐらい
    • C++ は書いたことない
    • 業務はフロントエンドを担当することも多く、複雑なアルゴリズムの実装が要求されたことはない

ABC 初参加前にやったこと

  • AtCoder に限らず、所謂競技プログラミングC++ が主流とのことだったので、まずは C++ の環境構築と学習から始めた(ちなみに、この選択は正しくて、ほぼ全てのAtCoder 関連の記事・解説放送、書籍は C++ を前提にしているので C++ やったことなくても学習した方が絶対効率よく学べる)

環境構築

VSCode の場合について書く。以下が揃えば良い感じに C++ でコンテストに参加できる。

code-runner (C++17 実行) の settings.json

  "code-runner.executorMap": {
    "cpp": "cd $dir && g++ -std=c++1z $fileName && ./a.out",
  },

AOJ ITP1 を解いた

自分の場合は C++ をまったく知らなかったので、レッドコーダーが教える、競プロ・AtCoder上達のガイドライン【初級編:競プロを始めよう】 でオススメされている通りに AOJ の ITP1コースを解いて練習しておくことにした。

愚直に9割ぐらい解いたんですが、注意点としては

  • 解答例は C++14 or 17 を見るようにする (標準ライブラリに慣れる)
  • ある程度プログラミング経験がある場合は全部やらなくていいと思う。 とりあえずA,B 問題に取り組んで解説放送聞いたり提出されたコードを読んだほうが実践的かも
  • ITP1 では標準入力の受け取りに慣れるのが1番大事かも

ABCに初めて参加した

自分はこの時点でA, B 問題までは解けた。コンテスト自体の知識は参加して覚えたほうが早い。(提出方法、問題の形式、誤答するとペナルティが発生する等)

スニペットを用意した

ABC の解説放送のライブコーディングで使われているスニペットを参考にした。自分の場合は start とタイプすると以下が挿入されるように設定している。

  "start-template": {
    "prefix": "start-template",
    "body": [
      "#include <bits/stdc++.h>",
      "#define rep(i,n) for(int i=0; i < (n); i++)",
      "#define lower(s) transform(s.begin(), s.end(), s.begin(), ::tolower)",
      "using namespace std;",
      "using ll = long long;",
      "",
      "int main() {",
      "  $0",
      "  return 0;",
      "}"
    ],
    "description": "basic"
  },

いちいちテンプレートファイルを準備して問題毎にコピーして新しく書き始めるよりも早いと思う。

コンテストで学んだこと

主に数値の取り扱い。Webアプリケーションを書いているだけだと意識することが少ない(気がする)

でもコンテストでは必須の知識だと思う。(A, B 問題でそこを狙ってくるテストケースや問題設定が多いので)

  • int (符号付き32bit 整数) の最大値は 231 -1 で、大体 2.14 * 109 で 、10桁
  • long int (符号付き64bit 整数)の最大値は 263 -1 で、大体 9.22 * 1018 で 、19桁
  • 除算は小数部切り捨て(例 5/2 = 2)
    • 切り上げする場合は (a+b-1) / b説明
  • 最終的な計算結果はオーバーフローしなくても計算途中の評価がオーバーフローすると駄目(例 a * b - ca * b がオーバーフロー)

あと、AtCoder のコンテストで C++ で解答する場合は計算機の性能は大体 108 step / sec ぐらいと想定すると良いみたいです。(例えば O(N) でも 1018 step だと 2sec 以内には絶対に収まらない)

ここに書いてた

今やっていること

週に4時間もコンテスト関連には時間使えていないと思う。土日にコンテストに参加して次のコンテストまでに解説放送聞いて実装してみたりぐらいが限界。

ABC に参加する

忘れない限りは毎週参加している。C, D問題は実戦で学ぶ感じになっている。C問題は慣れれば解けるようになってくる印象。実はそんなに高度なことを要求していないことが多い。

解説放送を聞く

神コンテンツ。これがなければ ABC 止めてたかも。模範解答自体はコンテスト終了と同時に公開されるが、大抵読んでも理解に時間がかかるか何も得られないので、最近は始めから解説放送を聞いている。(これは自分の読解力の問題もある)

D 問題以降は正直ちゃんとアルゴリズム等の実装経験積まないと対応できないことが明らかなんだけど、それをする時間をまとめて取れなさそうなので 解説放送に出てきたものから順に学んでいこうかと思っている。まあいつかは D で要求される範囲全部カバーできるようになるんじゃないかな...

今後の目標

  • コンテストに参加し続ける(後5回ぐらい参加で茶色になりそう)
  • C, D 問題の正答率を上げる
  • 楽しむ

*1:とはいえ次の業務で使う技術や知識のキャッチアップの方が優先なので、余暇を全て費やすことはできないが

*2:整数問題の対策が必要なのは当時は東大ぐらいだったのでやってない。今もそうかもだが

*3:C 言語でダイクストラとかバブルソートとか実装する演習があった記憶はある。当時は理解していた。今は実装は無理

購入後の mac にインストールするアプリ一覧

転職時や新規購入の際にお役立ち

そういえば、Chrome って初めから入っているんだっけ...?

VSCode で Go を書くための環境構築

環境

ゴール

  • 高速な補完、フォーマット、シンタックスハイライトが VSCode で動作する
  • vim-go の :GoImport のようにモジュールを選択して import できる
  • VSCodeデバッグできる(標準入力を伴う場合は入力した上でデバッグしたい)

手順

  • VSCodemicrosoft/vscode-goをインストールする
  • (gopls がローカルにインストールされていない場合は)コマンドパレットから Go: Install/Update tools で gopls を選択
  • settings.jsongo.useLanguageServe を true に設定する
  • フォーマットはデフォルトが goreturns だが、変更する場合は settings.jsongo.formatTool に値を設定する

これで標準入力を伴うデバッグ以外のゴールは達成。めちゃくちゃ簡単だ...

標準入力を伴うデバッグ

これはデバッグに使用する delve が標準入力に対応していないので vscode-go でも対応できないとのことだが、ワークアラウンドが共有されていた。(Cannot debug programs which read from STDIN

追加する内容はこのコメント参照

  • tasks.jsondelve を起動するタスクを追加する
  • launch.json に設定を追加し、起動済みの delve に attachしてデバッグができるようにする

以上を行うと、デバッグ前に1度タスクを実行するという手間は必要ではあるものの、デバッグ開始後に標準入力を受け付けるようになる。

その他

こんなハマり方をする人はいないかもしれないが、注意しましょう。

ちなみにログがないと言っていましたが、ちゃんと出力されていました。( mac なら Library/Application\ Support/Code/logs/ 配下)

あと、自分は Go なら Vim で書けばいいと思っていた派なんですが、フロントエンドは VSCode が圧倒的に向いているし、並行してバックエンドを書きたい場合もあるので VSCode でいいかなという気持ちになりました。