Squareに転職しました
11月からSquareで働いています。POSレジを作るのが嫌になったと言ってユビレジを辞めてSiderに転職したのが2016年、その3年後にまたレジの会社で働くことになるとは……(レジは作ってませんが。) *1
アメリカに行くのかというとそんなことはなくて、東京で日本法人の従業員として働いています。Squareが日本でサービス開始したのは2013年のことですが、それ以来東京のオフィスには開発者がいない状況でした。募集もしてなかったのですが、募集があるかどうかの確認もせずに知り合いづてに応募した人間がいて、それが私です……入社してから気づいたのですが、最近、全社を挙げてSFから世界中に分散していくというのを進めているそうで、その流れに気づかないうちに乗っていたのかもしれません。
今回仕事を探すに当たっては「RubyとSteepの開発に時間を使えるようにしたい」「社内でRubyの型に関するテスト・実験がしたいので、それなりに人数がいてRubyで書かれたコードに投資し続けていく意思があるチーム」という希望がありました。Squareでは、
などをやっていくという話になっていて、つまりRubyコミッタとしてお給料の一部をもらっていることになるので、これからはパートタイムコミッタを名乗っていきたいと思います。
入社までの経緯
去年Fog City Ruby MeetupでSteepの話をしたときに、ShannonやBrandonなどSquareの開発者と会っていて、それ以降もRubyConfやRubyKaigiなどで何回か顔を合わせていたこともあり、今年の夏に転職をしようと思ったときに相談してみたのがきっかけです。その後、社内で調整してもらうのに少し時間がかかったようですが、面接などして入社となりました。
面接は、
- コードを書く
- ホワイトボードにお絵かきしながら技術的な話をするやつ
- これまでの経験などについて話をするやつ
などの種類があって、最初にリモートで1.と3.を一時間ずつ、次にまたリモートで全部で5時間、という流れだったはずです。2ラウンド目の5時間の方は、多分普通はSF本社でやるんだと思いますが、私の場合はリモートで3日かけてやりました。コードを書くのは、お題をもらって小さなRubyプログラムをペアプログラミングするもので、アルゴリズムについて詳しく聞かれたりとかいう感じではありませんでした。*2
今のところの感想など
入社して1ヶ月くらいなので、まだまだよくわかってないのですが、
- それなりにでかい会社なので開発者も多く、CIサービスが自前のものだったりいろいろと社内向けのツールがそろっていたり、いろいろと投資できるんだなーと思う
- アメリカとミーティングをすることがありますが(マネージャとの1 on 1とか、開発者との話とか)、地球の形の都合から午前中にビデオ会議になります
- マネージャがいていろいろと世話を焼いてもらえる *3
など、興味深いいろいろがあります。
また、Webサイトの採用情報はまだ出ていないのですが、東京オフィスで引き続きエンジニアを採用していくという話をしていますので、興味があるようでしたら私に声をかけていただければ。私はRubyを書いていますが、それ以外でもJavaかiOSかAndroidが書ければ、なんとかなるのではないかと思います。
英語はかなり厳しいことはあって苦労はあります。マネージャにも、「こいつは少しだけだけど日本語がわかるから、なにかあったら相談しろ」とSFで人を紹介してもらったりとか、1 on 1で喋っててもうまく通じないのでSlackに書いてもらったりとか、申し訳ない気持ちがある……*4
TypeScriptのanyみたいな「なんでも代入できる型」を分類する
安全でないもの - void * (C)
いろいろあるんだろうけど、Cから始めよう。void*
にはなんでもキャストできるし、元の型に戻すこともできる。まあany
。問題は変なことをやった場合。(Cはよくわからないから誰かが例を書いてくれ。)
キャストによって変な型が得られた場合の挙動は未定義なんだと思う。(知らないから、誰か仕様を調べてくれ。)未定義というのは、コンパイラとか計算機は何をしても良いという意味で、
- プロセスの実行を止める
- 最適化によってコードが消える
- プロセスのメモリを破壊しながら動き続ける
- 鼻から悪魔が出る
などのパターンがありうる。実際には大体SEGVで落ちると思うんだけど、それは仕様には含まれていない。
ここで「安全である」というのは「プログラムの実行中に未定義の状態にならない」という意味である。つまり、Cは安全ではない。
キャストの検査で安全になったもの - dynamic (C#)
動的型付けの言語が流行ったり、本質的に型を書くのが困難なパターンがあることが判明したりして、いくつかのプログラミング言語には「コンパイル時に型検査されない型」というのが導入された。C#で言うところの dynamic
である。
string x = ""; dynamic y = x; float z = y;
こんなプログラムはコンパイルできる(つまり静的な型検査が通る)が、実行してみるとz = y
でエラーが起きる。このことから、float
型の変数z
にstring
を代入して良いかどうかを実行時に検査していることがわかる。つまりz = y
のところでdynamic
からfloat
のキャストが挿入されていると考えて、キャストを実行時に検査していると解釈する。検査に失敗したらRuntimeBinderException
が発生する。
キャストに注目すれば、JavaのObject
とかもこのグループに入る(プログラマによる明示的なキャストは必要なので、dynamic
とはかなり書き味が違うけど、それはおいておくとして)。つまり、キャストは必ず実行時に検査されて、失敗したときになにが起きるかは言語によって定義されている。あるいは、C++のdynamic_cast
も、失敗を判定できて失敗したときの挙動が定義されているという観点でこちらに入る。
さて、キャストの失敗でなにが起きるのかは定義されるようになったのだから、未定義ではない。つまり(少なくともこの面において)C#やJavaは「安全」になった。
安全なんだけど検査が省略されるもの - any (TypeScript)
dynamic
までだと話が綺麗なんだけど、TSはまた厳しいところをついていて、ちょっと難しい。
TSではany
から他の型へのキャストは実行時に検査されない。じゃあ安全じゃないのかっていうと、安全である。つまり、JavaScriptとして実行されるので、キャストの失敗を見過ごすことによって発生する問題は、どちらにしてもJSのランタイムによって保護されているのだ。どうせ安全なんだから、キャストの際の型検査を省略しても良いという発想。これは、かなり強烈な性質で、Java以降のプログラミング言語にかなり広く共有されている「キャストのような静的な型検査では問題を検査できない操作には、実行時のチェックで補完しよう」という戦略から離れていることを意味する。すごい。
逆に言うと、この「キャストの検査」を諦めたことによって、TypeScriptではかなり柔軟な型が書けるようになっている。仮に、キャストの検査をやる方向でデザインするとすると、JSのランタイムで表現できる型しか使えないことになるので、けっこう厳しい。最近はクラスが入ったので良いんだけど、少し前まではobject
とnumber
とstring
とarray
と……くらいしかなかった。「この式の型はobject
です(number
とかstring
ではないけど、どういうメソッドがあるのかはわかりません)」という型付けは、あまり実用的ではない。TSでは、オブジェクトの型もかなり詳細に書けるようになっていて、まあ現実的ではない感じのランタイム型検査になる。(もちろんやればできる。 )
そういうわけで、any
からのキャストで検査を省略するというのはまあ現実的な判断だと思うんだけど、意外性はあるのだった。
ちなみに、any
がなくてもTSの配列はcovariantなので、その点で型検査は健全ではない。
const xs: string[] = [] const ys: (string | number)[] = xs // 本当はこの代入ができてはいけない ys.push(1) const s: string = xs[0] // 本当はnumber
(どうせ一回any
を介せば代入できてしまうんだから、まあ気にせずにcovariantにするのが正しいのだろう。)
宣伝
特にここで書いた中ではTypeScriptに関係が深いのですが、「動的型付けの言語に型を付けようとする人類の試み」について一回話がしたいなーと思っていて、せっかくなので富山Ruby会議でやることにしました。
いろいろ人類が試行錯誤した歴史を私が理解している限りで解説して、「じゃあRuby (Steep) でどうすんの?」という話をします。みなさまふるってご参加ください。
GrillRB 2019にいってきた
こちらです。
話は大体いつものやつですが、ちょっと型を付けてプログラミングしていくことに踏み込んだ内容でした。*1
このGrillRBというのは、ポーランドのグロツワフという都市で開催されるカンファレンスで、大きな特徴として「会場が屋外」「提供される食事がバーベキュー」という2点があります。
トークをしているとだんだん肉が焼ける匂いが漂ってくるという、楽しい体験ができました。屋外で話す、ということでスクリーンがかなり厳しいのではないかと想像しましたが、テントの中にテレビが二台あってまあ余裕でした。
小さい街で、いかにもヨーロッパという見た目で、楽しかったのですが、まあ建物を眺めているとすぐに飽きます。美術館に行ってみましたが、「昔の絵は大体キリストかマリア」「時代が少し下がると偉い人のポートレートになる」ということを学びました。
上の写真は小人で、なんか好き勝手にその辺の建物を作る人が増やしていくらしいです。これはPC屋さんの前なのでキーボードを持っています。
楽しいので、来年以降、皆さんも参加されると良いと思います。(なお、スピーカー含めて大体の参加者はポーランドかヨーロッパからで「バスできた」みたいな感じで、「飛行機で14時間くらいかかって移動してるんだけど……」という難しい気持ちになれます。)
*1:あと例によって日本人がいなかったので、その辺を意識した導入になっている。
Sorbetについて
昨日のAsakusa.rbに行ったらSorbetについて聞かれたので、少し話したんだけど、ちょっときちんと確認しないまま話したらぐだぐだだったので、もう少しまとめておく。
全体的な話として、Sorbetはとても良くできているので、これで良いと思う人はそれで良いと思います。
$ srb --version Sorbet typechecker 0.4.4306 git 31ef906fe5eb572865f2b5d3ea5805793554c676 built on 2019-06-25 06:45:18 GMT with debug symbols
Rubyと少し合わないところはあるので、その辺を挙げる。
1. サブタイピング
いわゆるDuck typingというやつが、まあ良くない。
sig { params(arg: Eachable).void } def push_from(arg) arg.each do |x| @array << x end end
みたいなの。この引数のEachable
が特定のクラスなら良いんだけど、each
が定義された自作のクラスを渡したりArray
を渡したりするとちょっと難しいことになる。
なるんだけど、workaroundは用意されているので、それで良い人はそれで良いと思う。
# .rbiに欲しいメソッドが定義されたモジュールを定義する module Eachable sig { params(blk: T.proc.params(x: String).void).void } def each; end end # .rbにモジュールを定義する(このファイルは型検査しない) # typed: false module Eachable # これはincludeできればいいので空で良い end # 別に.rbにクラスなどを定義する class A # このincludeによってAのインスタンスはEachableになった include Eachable def each(&blk) yield "foo" yield "bar" yield "baz" end end push_from A.new # これがokになる
動かしてないんだけど、これでいけるはず。
「既存のクラスについても後からinclude
することでEachable
にできる」というのがポイントなんだけど、Array
でやろうとしたら型が難しかったので諦めた……
2. 型の構文
好みの問題なので、これが良いと思う人も世の中にはいるのかもしれない。
結構強烈なのは
def initialize(x:) @x = T.let(x, String) end
のような「T.let
メソッドでインスタンス変数の型を書く」だと思う。
3. Overloading
できないみたい。Sorbetの.rbi
には複数のsig
を一つのdef
に対して付けることができないみたいだ。
Array#map
とかはどうなっているのかというと、標準ライブラリは特別扱いになっているようにみ見える。 # typed: __STDLIB_INTERNAL
などと書いてある。
で、これだと困るような気がするのは
def fetch_people if block_given? yield people.get(id) else people.get(id) end end
みたいな「ブロックがあったらyield
して返り値を返す」パターンで、これ僕は結構書くので、ちょっと苦しいと思う。
4. Runtime
ライブラリに使おうとすると、Rubyコードの実行に言語内DSLのためのランタイムが必要なので、まあそれどうなのという。
ランタイムがあることの利点はあって、ランタイムの型検査ができるようになるし、Typed Structsみたいな便利ライブラリもあるので、なかなか難しいところだと思う。
感想
僕は、
- 絶対にRuntimeの挙動を変えたくないし、gemにはRuntimeの依存関係を増やしたくない
- 型の構文が苦しい(何日かすれば慣れるかもしれないけど)
- Overloadingが全然書けないのはさすがに問題では
- 実はStructural subtypingにこだわるつもりはないんだけど、今のworkaroundに必要な構文はさすがに苦しいと思う。
くらいの意見があります。
JSONやYAMLのデータ構造をチェックするライブラリStrongJSONのご紹介
JSONやYAMLを使うと、かなり複雑なデータが作成できますが、これが意図通りの形式になっているかを確認するのは自明ではありません。XMLにはXML Schemaがありますが、そんな感じのものがJSONにも欲しかったので、作りました。
こんな感じで使います。
Schema = StrongJSON.new do let :phone, object(phone: string) let :email, object(email: string) let :contact, enum(phone, email) let :person, object(name: string, contacts: array(contact)) end json = Schema.person.coerce(JSON.parse(input, symbolize_names: true)) # symbolize_namesが必要です
最新バージョンは1.1とかで、最近「人間にも読めるエラーメッセージ」を実現しましたので、みなさまご活用ください。例えばテストにある例なんですが、
TypeError at $.items[0].price: expected=numeric, value=[] "price" expected to be numeric 0 expected to be item "items" expected to be items $ expected to be checkout Where: item = { "name": string, "count": numeric, "price": numeric } items = array(item) checkout = { "items": items, "change": optional(number), "type": enum(1, symbol), "customer": optional( { "name": string, "id": string, "birthday": string, "gender": enum("man", "woman", "other"), "phone": string } ) }
とか出せるので、少し頑張れば人間にも読めるんじゃないかなあ。そのままアプリケーションに組み込んで使っている例としては、 sider/goodcheckがあります。そのままアプリケーションに組み込めば良いということで、YAMLファイルとかで設定を書かせるときに、validationできて便利ではないかと思います。
JSON Schema
さて、ところで世の中にはJSON Schemaというものがあることをご存じの人がいるでしょう。JSON Schemaではダメなのかというと、まあ別に良いんですけど、
- SchemaもJSONになっていて欲しい気持ちが別にない → Ruby DSLでSchemaを書くようにした
- 「数字」とか「文字列」とかそういう感じがテストしたいのであって、「電話番号」みたいなデータ型はいらない
という感じです。
YAMLでは
RubyのYAMLライブラリだと、まあ大体JSONみたいなデータ構造になれるので、それでいけます。JSON以外のデータ構造もできたと思いますが、そういうのはダメです。
どういう名前なんこれ??
RailsのStrong parametersからの借用です。Strong parametersは、クエリパラメータみたいな構造は上手く処理できるのですが、JSONみたいなのを扱おうとすると幸せな感じではないので作りました。
が、実はこのライブラリは5年くらい前から作っていて、当時のStrong parametersと今のstrong parametersの違いとかは知らないので、もしかしたらRailsので良いのかもしれません。
型はつくの?
つきます。JSONは、まあ色々あるんで any
にしちゃうのが良いと思いますが、そいつからレコード型のデータ、例えば { name: String, contacts: Array[contact] }
に変換すると思ってください。変換できないものはエラーになる。
一方で、この型付けが一番便利かというとそんなことはなくて「頑張れば型がつくと強弁できる」くらいの程度で、まあSteepを直さないといけないですねえ……
Balkan Rubyに行ってきた
- 去年 https://2018.balkanruby.com
- 今年のも年番号がつくと予想 https://2019.balkanruby.com (まだないけど)
Genadiが誘ってくれたので、Balkan Rubyに行ってきたのだった。話はいつものやつ。
RubyKaigiでMatzとか id:ku-ma-me とかが話してたんだけど、大体そんな感じになっているはずです。私の言葉になっているけど、内容的には新しいことはありません。
トークの話はこのくらいにして、Balkan Rubyが良かった話をしようと思う。
オーガナイザが手厚い
ホテルを取ってくれるし、空港からホテルも手配してくれているし、ホテルについたらSIMもあるし、ご飯もあるしで、特になにも考えずにトークの内容だけ準備してSofiaに行けば良いので楽だった*1。2日前の午後にSofiaについたんだけど、そのまま既にSofia入りしているスピーカと晩ご飯になっていた。その流れで、だいたい毎食スピーカ同士でご飯を食べていた。
これはRakiaという酒でブルガリア人はこれを飲ませてくる。強い(40〜50%)ので注意すること。
なんでこんなにスピーカの世話が手厚いんだ、と聞いてみたら「スピーカの数が少ないから」などと言われたので、まあ確かにね、という感じ。
Sofiaが楽しい
キリル文字とか建物とかにロシアとかソビエトみたいな感じがあって、ヨーロッパとも違って楽しい。と言いつつ、私にとって初のヨーロッパの国だったので他のヨーロッパ地域のことは良くわからん。町並みは綺麗で、晴れていると遠くに山が見えて大変に楽しい。公園には人々がいて、小さい子どもとかが遊んでたりするので、平和で良い気持ちになれる。特に夜遅くまで明るいので(緯度のせいか経度のせいかはわからない)、カンファレンスが終わって外に出たときに「まだ4時くらいかな??」と思ったら実は7時過ぎだったりするので危ない。
Balkan Rubyの参加者は大体ブルガリア人なんだけど、スピーカはヨーロッパ各国からで、それに加えてアメリカ・アジアが数人という顔ぶれだった。こんなにヨーロッパ人が周りにいることはあんまりなかったので、ちょっと不思議な感じだった。
Balkan Rubyが終わった日曜日は英語で無料の市内ツアー(2時間)があって、そういうのに行ってた人もいる。
ラーメン屋があったのでうっかり入ってしまったが、会計のタイミングでカード支払いができないことが発覚し、ATMまで現金を下ろしに走った。それ以外は全部カード支払いでいけたから、油断していた。
Siderで使っているTSLintの設定
今のところこんなんです。
{ "extends": [ "tslint:latest", "tslint-immutable" ], "rules": { "array-type": false, "arrow-parens": false, "interface-name": false, "interface-over-type-literal": false, "max-classes-per-file": false, "max-line-length": false, "member-access": false, "object-literal-key-quotes": [true, "as-needed"], "object-literal-shorthand": false, "object-literal-sort-keys": false, "ordered-imports": false, "quotemark": false, "semicolon": false, "trailing-comma": false, "variable-name": [ true, "ban-keywords", "check-format", "allow-leading-underscore", "allow-pascal-case" ], "whitespace": false, "no-angle-bracket-type-assertion": false, "no-shadowed-variable": false, "no-namespace": false, "readonly-keyword": [true, "ignore-class"], "no-trailing-whitespace": false, "no-implicit-dependencies": [true, ["i18n-js"]], "no-submodule-imports": false } }
特に解説はないのですが、
- スタイル関連は基本的に無効
- なんか怒られる度に無効にしていく
==
って書くと怒るやつは欲しい
みたいな雑な設定でやっています。
一つだけ有効にしているプラグインがあって、tslint-immutableです。これは我々がreduxを使っているからで、stateのためのinterfaceのattributeをimmutableにするのを忘れないようにしています。このプラグインはなかなか便利なのですが、一つ問題があって、ある種の型適用に immutable
と書きたくないという話があります。
こんなん。
const routes = { assignSeat: new ApiRoute<{orgId: number, memberId: number}>( "/api/c/gh/orgs/:orgId/members/:memberId/seat_assignments" ) } const url = routes.assignSeat.stringify({ orgId: 123, memberId: 456 })
このApiRoute
というのは、APIリクエストとかで使うURL生成のためのクラスで、 orgId
に文字列を渡したり null
が入ったりすることを防げる優れものですが、ここでいちいち readonly
と書きたくないという問題があります。良い感じのオプションがないし、どんなときに readonly
と書かなくて良いかしばらく考えたけど判定方法を思いつかないので、もう諦めてしまって誤爆が出ると無視するようにしています。(Siderを使います。)