Runner in the High

技術のことをかくこころみ

GolangでUnmarshallerインターフェースを実装した構造体をフィールドの型として使うと便利

例えばこんな構造体が定義されているとして

type User struct {
    Name   string `json:"name"`
    Status int    `json:"status"`
}

この構造体をJSONからUnmarshalしてマッピングする際に、以下のような要件があるとする

  • statusの取りうる範囲は1,2だけ、かつそれぞれACTIVE, INACTIVEという定数にマッピングしたい。
    • 1,2以外の数値が来たらエラーにしたい。
  • statusフィールドがないときもエラーにしたい。

User構造体を受け取ってバリデーションをする関数を作ってもよいが、以下のようにStatus型を新しく作り、それにUnmarshallerインターフェースを実装させるとグッとスマートにできる。

type User struct {
    Name   string `json:"name"`
    Status Status `json:"status"` // int型からStatus型に変更
}

Unmarshallerインターフェースを実装したStatus型

Unmarshallerインターフェースは以下のような定義になっている

type Unmarshaler interface {
    UnmarshalJSON([]byte) error
}

JSONのUnmarshalをする際、マッピング対象のフィールドにUnmarshallerインターフェースを実装したものがあると、そのフィールドへのマッピングの際に自動的に実装されたUnmarshalJSONメソッドが呼ばれるようになる。

つまり、Status型にこのUnmarshallerインターフェースを実装することで、User型がUnmarshalされる際に自動でStatus型に実装された任意のマッピング処理およびバリデーション(!)行えるようになるというわけだ。

Unmarshallerインターフェースを用いて要件を実装したStatus型は以下になる。

type Status int

const (
    ACTIVE Status = iota + 1
    INACTIVE
)

// Unmashaller
func (status *Status) UnmarshalJSON(data []byte) error {
    var value int

    if err := json.Unmarshal(data, &value); err != nil {
        return fmt.Errorf("Failed decoding status: %s", err.Error())
    }

    switch value {
    case 1:
        *status = ACTIVE
    case 2:
        *status = INACTIVE
    default:
        return fmt.Errorf("Invalid status: %d", value)
    }

    return nil
}

statusの取りうる範囲をバリデーションできているか見てみる

上の実装を終えた上で、Status型をフィールドに持つUser型をJSONからマッピングしてみるとこうなる

func main() {
    src := `{ "name": "Jonathan", "status": 3 }`

    var user User
 
    if err := json.Unmarshal([]byte(src), &user); err != nil {
        panic(err) // panic: Invalid status: 3
    }

    // ...
}

statusに3が与えられているのはUnmarshal時にマッピングエラーになる! すばらしい

statusフィールドが存在しない場合にどうするか

実はこれだけではまだ、以下のようにstatus型が抜け落ちているJSONマッピングに対するエラー検知ができていない

{ "name": "Jonathan" }

上のJSONマッピングするとどういう挙動になるかというと以下のようになる。

func main() {
    src := `{ "name": "Jonathan" }`

    var user User
 
    if err := json.Unmarshal([]byte(src), &user); err != nil {
        panic(err)
    }

    fmt.Printf("%#v\n", user) // main.User{Name:"Jonathan", Status:0}
}

つまりint型の初期値である0が代入される。

自分の場合は以下の IsEmpty のようなレシーバメソッドを生やして、仮にUnmarshalが成功したとしても、必ず直後にIsEmptyを事前条件チェックとして呼ぶようにしているが、若干微妙ではある。

type Status int
 

const (
    ACTIVE Status = iota + 1
    INACTIVE
)

func (status Status) IsEmpty() bool {
    return status == 0
}

// ...

func main() {
    src := `{ "name": "Jonathan" }`

    var user User
 
    if err := json.Unmarshal([]byte(src), &user); err != nil {
        panic(err)
    }
 
    if user.Status.IsEmpty() {
        panic("status is empty")
    }

    fmt.Printf("%#v\n", user)
}

本当はUnmarshalJSONメソッドの中でどうにかしたいが、Unmarshal対象のフィールドがない場合には当たり前だが呼ばれない。ここはちょっと苦しい。

Opinionatedなライブラリとチーム開発

 GitHubなどで作者がライブラリ(やフレームワーク)をopinionatedであると形容しているのを見ることがある。Opinionatedというのは直訳すると「意固地な」「意志のかたい」のような雰囲気になるが、意固地なライブラリというのは正直意味が通らない。ではどういう意味なのかというと、そのライブラリが、作者によって強く方向付けられていることを意味する。言い換えれば、利用者がある種のルールを強制されるものだ。

 フロントエンドのはなしをするが、たとえばElmはとてもopinionatedな言語(このケースはライブラリではないが)であると言える。創造神エヴァンによって、言語自体にSPA開発用のフレームワークが内蔵されているし、JSによくあるような裏技やハックを使うことが言語仕様上できなくなっている。TEAというアーキテクチャによって、アプリケーションの構造やデータの流れが統一されている。これはまさに彼自身の思想そのものだ。

 他のものでいうと、Vue.jsも比較的フレームワークという観点では微妙にopinionatedだと言えるが、細かい部分ではそうではない。一方、Reactはビューのみに責務を絞っているという点でless opinionatedに見える。ReduxはJavaScriptなライブラリの中では最もopinionatedだろう。

ライブラリと思想

 ちゃんとした意図や背景があって作られたものには、公式のページかどこかに丹精込めて作者が思想を説明しているページがあるケースが多い。たとえばReduxのMotivationROM.rbのCore Conceptsはすごくいい例で、既存の解決策やアプリケーション・アーキテクチャのどこがイケてないのかを説明した上で、自分たちの思想を実装したライブラリがどう優れているのかを説明している。

 思想が弱いライブラリというのは、使っているうちに思わぬところで綻びが出るものが多い。とくに誰彼構わずいい顔しようとするタイプのものに顕著な印象がある。オールインワンだとか、初心者に優しいと謳うもの、easyやsimpleなどのフレーズを使うものが怪しい。パフォーマンスが優れていることをアピールして、肝心な思想を煙に巻くケースも多い(もちろん、パフォーマンスに優れていることがそのライブラリ自体の思想ならおかしくはないが)

 Less opinionatedなライブラリは利用者にルールを強いない代わりに、ルールを作りを委ねてくる。小規模な開発では問題にならないが、チームが大きくなる頃には必ずコンセンサスゲームをすることになる。ライブラリを使う我々側に、それなりの思想を持つことが期待される。しかもチーム開発ともなれば、チーム全体で思想が共有されている必要がある。運用に乗せるのも頑張らねばならない。これがまた、人が増えたときに骨が折れるのだが...

ElmのSlackチャンネルが情報収集をするのに便利

世界中のElmエンジニアが集まるSlackワークスペースがあり、情報収集をするには非常によい。

あまり日本のElm界隈では知られてないのかな? と思ったのでメモ程度に周知しておく。以下のページからSlackのワークスペースに入れる。基本的に全部の会話が英語なので、英語が読み書きできるとなおよい。

elmlang.herokuapp.com

様々なチャンネルがあるが、基本的には #general#begineers に入っておけば良いと思う。質問に対する回答は、参加者人数が多いだけあってめっちゃはやいし、とくにディスカッションが活発でおもしろい。 Elmのカンファレンス情報は #conferences にまとまっているし、作ってみた系の告知やニュースは #news-and-links にどんどん流れてくる。

その他にもローカルなミートアップに関するチャンネルなど腐るほどチャンネルがあるので好きなのに入ればよいと思う。たいてい、あまりメジャーじゃない言語やフレームワークに関しては、日本語情報に触れているうちは最新の情報や尖ったコンテンツにはアクセスできない。なので、こういうグローバルなSlackワークスペースにいるほうが学びが多い。

Elm Europe 2019 にスピーカーとして参加した

2019/6/27-28にかけて開催されていた Elm Europe 2019 というカンファレンスにスピーカーとして参加した

f:id:IzumiSy:20190630225553p:plain

このような海外カンファレンスへの参加は、だいぶ昔にサンフランシスコへ行ったとき以来だし、とくにスピーカーとして登壇するというのは人生初だった。英語でトークをやるというのは非常に緊張する体験だったけれど、率直に言ってめちゃくちゃ楽しかった。

詳しいカンファレンスの内容などはおそらく所属している会社のブログの方で書くはずなので、こっちではもう少しパーソナルなことを書こうと思う。

CFPの応募

Elm Europe は日本のちょっと大きめのカンファレンスと同じで、スピーカーを CFP で募集している。3月ごろに、会社の同僚から募集しているということを知らされ、雑な CFP をサブミットした。このとき、たしかインターンのイベントかなにかがあり、社内で酒を飲んでいたのでかなり適当な英文を書いた。

後日、朝起きて携帯でメールチェックをすると Elm Europe から以下の内容でメールが届いており、腰を抜かした。

f:id:IzumiSy:20190630231524p:plain

CFP の応募から当日までほぼ2,3ヶ月ほどの時間があったにも関わらず、結局スライドはギリギリまで作っていた気がする。

一方で、他の参加者とは Discord のチャンネルで細々としたやりとりを(もちろん英語で)していたが、みんなスライドづくりを始めるのが非常に早い。1ヶ月くらい前には、みんな「もう資料はできてるよ!」というようなことを言っていて、すごくペースが早いな、と思った。

日本からフランスへ

当日は日本の夜11時に羽田の国際便に乗ってフランスへ向かった。途中、機内でなんらかの旅客トラブルがあり、乗務員が「お客様の中にお医者様か看護師の方はいらっしゃいませんか」というアナウンスをしていて、初めてドラマや映画でしか見たことのなかった体験をした。また、前に座っているフランス人カップルがものすごい勢いで機内でイチャイチャしていて、カルチャーを感じた。

朝4時にフランスのシャルルドゴール空港へ到着。スピーカーのホテルはその日は15時からしかチェックインできないということで、どうにかそれまでの11時間を過ごさねばならない... しかし Elm Europe の Discord でいろいろ話しているうちに、オーガナイザーのアパートの部屋へ行ってもいいということで、朝7時ごろにパリの街へ繰り出した。

自分が滞在していた期間、パリはずっと晴れで朝は暑すぎもせず寒すぎもせず非常にちょうどよい気温であった。夜のパリは非常に危険らしいが、朝のパリは非常に趣がある。

f:id:IzumiSy:20190626064414j:plain:w550

というわけでオーガナイザーの住んでいるアパートの住所までテクテク駅から歩き、30分ほど待つも一向に Discord で返事がない。さすがにしびれを切らし、もう自分で会場まで向かってしまおう、ということでその場をあとにした。

会場へ

f:id:IzumiSy:20190626083317j:plain:w550

Elm Europe の会場は Efrei Paris という、いわゆる日本でいうところのコンピューター系の専門学校のようなところだった。

校舎のなかは、こんな感じのキレイめな雰囲気になっていて非常によい。 f:id:IzumiSy:20190626083537j:plain:w550

この日はカンファレンスの前日で、会場では Elm と GraphQL を使ったアプリケーションを開発するワークショップが開催されていた。なんと8時間にも及ぶ長いワークショップとのこと。なお、このワークショップで講師をしていたのは elm-graphql や elm-typescript-interop を作っている人だった。こういう人にサクッと会えるのが海外のカンファレンスのいいところだ。

しばらくすると、お昼ごはんとして軽い食事が出てきた。 f:id:IzumiSy:20190626132744j:plain:w550

こちらは、準備中の会場 f:id:IzumiSy:20190626142959j:plain:w550

お昼を過ぎたあたりから、天候の具合がおかしくなってくる。どう考えても暑すぎるのだ。真夏の日本と同じか、それ以上に暑い。半ズボンを持ってきていなかったことを後悔した。日本に帰国してから知ったのだが、どうやら僕が滞在していた週のフランスは記録的な猛暑に見舞われていたらしい。どうりで暑いわけだ...

フランスなどヨーロッパ地域の建造物にはエアコンがないのが普通らしく、このあと向かったスピーカー用のホテルも三ッ星ホテルの癖に部屋には扇風機しかなかった。夜になると、すこしは気温が下がり過ごしやすくなるものの、真っ昼間にエアコン無しで部屋の中にいようものなら、あっという間に脱水して倒れてしまうんではないかと思う。スピーカーホテルは自分含め三人で一部屋という割り振りになっており、なんだか語学留学みたいでおもしろかった。

19時頃からスピーカー限定でディナーが開催され、昼には会えなかった参加者のみんなといろいろな話をする機会があった。丁度隣に座っていた人が SoundCloud のフロントエンドエンジニアで、なんと日本の Elm 勉強会にも参加したことがあるとのことですごく偶然を感じた。この会食には Elm 界隈の超有名なエンジニアであるリチャード・フェルドマンも参加しており(彼は初日のキートーク枠だった)個人的にはとうとう Youtube でしか見たことなかった人が目の前に...! と思ったものの、正直緊張して握手と挨拶しかできなかった。

発表当日

とうとう発表当日を迎える。

会場にはこんな感じののぼりが立てられていて、そこに自分が作っているプロダクトのロゴがあるというのは正直に言ってなんともいえない感動があった。 f:id:IzumiSy:20190627170617j:plain:w550

朝は比較的ひんやりとした天気で、若干半袖だと寒いくらいだった。 f:id:IzumiSy:20190627170658j:plain:w550

iPhoneの紛失

実は発表当日、大きなトラブルに見舞われていた。というのも、スピーカーホテルのルームメイト達と会場にUberで向かう中、なんとポケットから iPhone が滑り落ち、そのままタクシーが行ってしまったのだ。これまで日本で携帯なんか落としたことなんてなかったのにとうとうやっちまった... という失意の念に打ちひしがれながら初日を迎えることになった。

Uber を呼んでくれたルームメイトがドライバーに電話をかけるも、全く出る気配がない。仕方ないのでボイス・メッセージを残してくれたが、これもおそらく気休めにしかならないだろうな、と思った。

しかし、こんなことで凹んでられない。とにかくいまは自分の発表に集中しなくては! と気持ちを立て直す。

自分の番が回ってくる

自分ではナチュラルな笑顔を心がけたつもりだったが、緊張しすぎて引きつった笑顔になってしまっている... 発表前に接続テストがなぜかできず、自分のスライドがまともに映るかも不安だったが、もうここまで来たらやるしかない。

発表中はもう無我夢中だったので、実際どういう感じで自分が話していたのかは全く記憶にない。途中から若干余裕が出てきて、顔を上げて会場を見回したりできたような気がする。発表の直前に、自分の "Review" の発音がちょっとわかりづらいと言われ、正しい発音は日本で言うところの "ウィヴュー" だと教わったが、実際にプレゼンでそのフレーズを話した記憶がない。

下の Age of Empire の壁ユニットを使った型システムのスライドのページはちょっとウケたようで良かった。

発表が終わるとリチャードがステージのところまで来てくれていて「面白かったよ!」と言ってくれた。

こちらからは「僕らのチームも、あなたのカンファレンスでのトークrtfeldman/elm-spa-example を Elm 学習のリソースとして使っていて、すごくインスピレーションをもらっています!」というようなことを伝えたりした。彼は発表中に以下のようなツイートもしてくれていて、わざわざフランスまで来て登壇発表したかいがあったな... という嬉しさがあった。

このツイートは同僚たちにも観測されており、弊社の Elm チャンネルでは「リチャード・フェルドマンがツイートしてる!」と謎の盛り上がりを見せていてウケた。僕らからすれば本当に Youtube の中の存在であった彼が、このようなツイートをしてくれたことには、非常な感動があった。

携帯が帰ってくる

発表の直後、 iPhone のリモートロックの機能を有効にするとスマホ上にメッセージを出せると知り、自分のラップトップからスマホをロックした。

自分は無駄なお金がかからないようにこまめに SIM のローミングを切るようにしていたが、無くしたときはたまたまローミングを有効にしたままだった。スマホの位置情報をチェックすると、どうやらパリの北部にいる。動きが比較的早いところをみると、まだ Uber タクシーの中に残っているようだった。

ダメ押しで端末のメッセージに会場となっている学校の住所を追加する。これでだめだったらもうダメだ、と思いながらあとはただ帰ってくることを祈りながら他の発表を聞いていると、なんと自分の携帯がいつの間にやら学校の教務課(?)へ届けられていた。まさかこんな異国の地でしらない人間の温かみに触れられるとは思わず、とても感動であった。

ルームメイトの二人に「携帯が帰ってきた!」と伝えると、「オイオイマジかよ、信じらんねーぜ。最高の一日だな!」みたいなテンションで、やっぱりこんなふうな落としものが帰ってくるというのはなかなかにレアなことなんだなと再認識した。

感想

Elmの開発者は全世界的に見ても非常に少ない。それだけに、コミュニティがすごく親密で、新しい人に対して開いている。 Elm 本体の開発者を擁する NoRedInk のエンジニア達も大抵会うことができるし、すごくフレンドリーだ。海外の開発者コミュニティに足を踏み入れるのは、確かにこれが初めてだったし、ちょっと英語が話せるとはいえ普段は日本語しか話さない自分が馴染めるのか非常に不安な部分があった。しかし Elm Europe は非常に多様なカルチャーのエンジニア達で構成されていたし、Elmが好きだという気持ちがあれば誰もが楽しめるし、歓迎される場であると思う。

主催者のフランス人である Assus は我々と同じ非ネイティブ・スピーカーで、正直彼の英語は僕から見ても流暢であるとは言えなかった。けれども、参加者のひとりは彼の存在がコミュニティにおける Stress Remover であると言っていた。副次的な効果であるとは思うが、彼自身が Elm Europe のモデレータであるということ、そしてまた彼のキャラクターが Elm Europe の親密で誰もを受け入れるような雰囲気を作っているようにも見えた。このような多様性がコミュニティの中で感じられることは、非常に素晴らしいことだと思う。

下は、アフターパーティで鍵盤ハーモニカを演奏する Assus の写真。こちらも上手いのか下手なのかよく分からなかったが、おもしろかった。

f:id:IzumiSy:20190627185909j:plain:w550

また、今回スピーカーとして参加したことによって、おそらくただの Attendee としての参加では出会わなかったであろう人との出会いもあった。大学を卒業してから、おそらくもう英語を使う機会は無いだろうなと思っていたが、思いがけずこのような形で自分の英語力が生きる機会があり、とても嬉しかった。文化や母語は違えど、プログラミング言語という共通のコンテキストを通してこのような交流ができるというのは、我々ソフトウェア・エンジニアならではではないかと思う。渡航するまでは途方もない緊張感でナーバスだったが、終わってみると想像の200倍は楽しかった。

次はどのカンファレンスで話そうかなぁ。

dayjsと謎の挙動

年と月だけの文字列をDateへ変換すると2月だけおかしくなる

console.log(dayjs('201901', 'YYYYMM').toDate()); // Tue Jan 29 2019 00:00:00 GMT+0900 (日本標準時)
console.log(dayjs('201902', 'YYYYMM').toDate()); // Fri Mar 01 2019 00:00:00 GMT+0900 (日本標準時)
console.log(dayjs('201903', 'YYYYMM').toDate()); // Fri Mar 29 2019 00:00:00 GMT+0900 (日本標準時)
console.log(dayjs('201904', 'YYYYMM').toDate()); // Mon Apr 29 2019 00:00:00 GMT+0900 (日本標準時)

なんで?

github.com