Runner in the High

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

エンジニアHubで「Elm入門と実践」を寄稿した

エンジニアHubさんというあの全日本的に有名なWebメディアでElmの記事を執筆しました。

employment.en-japan.com

これまでにTypeScriptの記事とかはあったものの、関数型AltJSの記事はたぶん今回のElmが初めてです。

なんだかんだ国内でも少しづついろんな盛り上がりがでてきているこのタイミングでエンジニアHubさんにElmの記事を書けたというのはとても神がかり的なタイミングだったな〜と思っております。 本当にすごくいい経験になったし、これでElmに興味を持つ人が増えてくれれば感無量です。

記事で使われているサンプルアプリケーションのソースコードGithubで公開しました。ぜひElmアプリケーションを開発する際には参考にして頂けるかなと。

github.com

記事の背景

今回の記事は入門というタイトルが付いているものの、実際の記事の意図としては文中にもある通り、TODOアプリ的な小さなアプリケーションから、リチャード・フェルドマンのelm-spa-exampleのような極めてリアルなプロダクトに近いElmコードの間を埋める"ラダー"のような立ち位置の記事にしたい、というものがありました。

編集の方から最初に執筆のお話を頂いたときも、やはりインターネットにおけるElmの学習リソースの偏り、つまり「書き始めるのは簡単だけれど、ある程度規模のあるアプリケーションを作るためのノウハウがまだ見つけられない」というのはすごくあるなと自分でも感じていて、それを埋められるような記事を書けたらいいだろうな、と考えながらトピックを選択しました。

もちろん、これはElmに限った話ではないんですが、それでもやはりReactやVue.jsと比べて、いかにElmアプリケーションを"大きく"育てていくかというトピックはまだまだ少ないとは僕も感じています。 そんな背景を踏まえた上で、自分の記事がElmを学ぶエンジニアたちの手助けとなるラダーになってくれれば嬉しいです。

モデルのデータをどこで計算するのか問題

余談ですが、記事を書いている際、弊社のエンジニアのレビューでサンプルアプリケーションの設計に少し悩んだこと部分がありました。 それは、画面で表示されるデータをモデルからどのタイミングで計算するのかというところです。

今回自分が寄稿したエンジニアHubのECカートアプリは、update関数がモデルに保持しているデータをもとに次のデータを計算してモデルに格納しています。 たとえば、サンプルコードにおけるCart型は以下のような定義になっており、taxesやshipping等の値はproductIdsによって従属的に決定される、という形です。

type Cart
    = Cart
        { productIds : ProductIds -- これがshippingやtaxesの値を決定する
        , shipping : Int
        , taxes : Int
        , subTotal : Int
        , total : Int
        }

モデルに計算結果が予め格納されていることのメリットは

  • update関数に計算を凝集できるためview関数をシンプルにできる
  • モデルの状態から画面が予測しやすいためデバッガビリティが高い

というものがあり、今回のサンプルコードでは個人的な推しとしてこのスタイルを採用しています。 実際に文中でもこんなことを書きました。

適切に設計されたアプリケーションのModelはViewのコードに現れます。Viewの中でModelのデータを複雑に計算しているとしたら、それはModelのデータ構造の設計を見直す必要があるでしょう。 Modelのデータに文字を付け加えたり、case文でパターンマッチによるHTMLの出し分けをしている程度になっているのが、Viewの責務としては理想です。

Elm入門と実践 - 買い物カートを作ってアーキテクチャ「TEA」を学ぶ - エンジニアHub|若手Webエンジニアのキャリアを考える!

しかし、ElmにおいてモデルというのはいわゆるSingle Source of Truthにあたるものであるため、たとえばモデルにデータAという値が格納されている場合、その値から計算される別のデータBという値が同じようにモデルに格納されているのは、実質データAだけで計算可能な値を重複して格納しているためSSoTに違反しており、結果として誤ったデータを格納する原因になるのではないかという指摘です。

たとえば、サンプルコードにおけるCart型は以下のような定義になっており、taxesやshipping等の値はproductIdsによって従属的に決定されますが、フィールドとして存在していることでコーディングミスなどで誤った値が入る可能性があるということです。 ではどうするのが良いかと言うと、これを以下のようにproductIdsだけにします。

type Cart
    = Cart ProductIds

もともと存在していたshippingやtaxesなどの値は、別途view関数などの中で計算します。Lazyなどを使っていれば実質ProductIdsが変わっていなければDOM操作は行われないため計算コストはさほど問題ありません。

正直、これには決まった答えがないのでどちらのスタイルを採用するかというのはチームで話し合ってキメをやっていくしかない、いわゆる要はバランス™としか言えないのが事実です。 個人的にはどっちを採用してもいいかなとは思っています。合意がとれればそれでOK、という感じで。

結論

Elmって最高だね!

基礎からわかる Elm

基礎からわかる Elm

  • 作者:鳥居 陽介
  • 発売日: 2019/02/27
  • メディア: 単行本(ソフトカバー)

渡米してDeveloperWeek 2020で登壇した

アメリカのDeveloperWeek2020というイベントに参加して登壇発表した。

www.izumisy.work

改めて考えると、社会人になってから初の渡米&初のアメリカでの登壇。
ヨーロッパもいいけど、やはりテックカンファレンスといえばアメリカだよね。

DeveloperWeekとはなんなのか

f:id:IzumiSy:20200224151346p:plain

上の記事でも書いたが、オークランドで開催される規模の大きなエンジニア向けのカンファレンス。 5日間のスピーカーセッション、展示会そして2日間のハッカソンが開催され、文字通り1週間を通してテッキーなイベントが満載、という感じ。

意外にも歴史があり、なんと2013年から継続して開催されている。なので、今年で約8年間開催していることになる。 カンファレンスというのは規模が大きくなればなるほどスポンサーを見つけたり参加者をコンスタントに維持したりするのが難しくなりがちなので、 そんな中で継続して開催されているのは、それだけですごいと思う。

f:id:IzumiSy:20200228113115p:plain
CFPに通るとこんな感じのメールが届く

日本からオークランド

今回の登壇では前回のelm Europeと同様、イベント開始1日前にオークランドへ到着。

現地にはアメリカ時間で朝10時くらいに到着したが、実は登壇の緊張からか機内で過ごす10時間ものあいだ一睡もできていなかった。 そんなこんなで、到着初日はホテルにチェックインしてそうそう、昼から夕方までガッツリ寝てしまい、あまりいい感じにジェットラグを修正できなかった。

翌日のイベント初日は、特に眠くもなかったせいで朝4時に起きてしまった。 軽く周辺を散歩した後、朝6時半くらいに宿泊しているホテルの食堂でシリアル、ヨーグルト、スクランブルエッグなどの朝飯をガッツリ食べ、会場へ向かうことに。

f:id:IzumiSy:20200224183156j:plain
早朝のオークランド。会場となったマリオットホテルはチャイナタウンの近くだったので、比較的アジアンな雰囲気の町並み。歩いていけるくらい近くにブルーボトルコーヒーもあってよかった。

会場の雰囲気

これは海外のカンファレンスあるあるなのだが、日本と比べて圧倒的に朝が早い。

DeveloperWeekも同じで、会場のレジストレーション・ブースは朝8時からオープンという気合の入りっぷり。意外と朝早くに行ったにも関わらず、ロビーからかなり並んでいてびっくりした。

f:id:IzumiSy:20200212111657j:plain:h500
レジストレーション・ブースの様子

f:id:IzumiSy:20200212113151j:plain:h600
SPEAKER!

こんな感じで、参加者が首からかけているバッジで各参加者区分がひとめで分かるようになっている。 スピーカー意外にはスポンサー、ブース出展者、プレミアムパス、スタッフなどの区分があるようだった。

海外のカンファレンスの参加者というのは大半が会社から金を出してもらって参加している。 そのせいなのかは分からないが、参加者の大半が一番高いプレミアムパスを身に着けていた。

スピーカーセッションが行われるステージは会場全体で大小合わせて5-7つほどあり、常にすべての会場でなんらかのセッションが行われている形になっていた。 1セッションあたりが大体25分、長いものだと50分程度になるため、必然的にある時間帯で聞きたいセッションを排他的にひとつ選ばないといけなくなる。 個人的には聞きたいセッションが同じ時間になっていることも多く、これはかなり難儀した。

f:id:IzumiSy:20200212120020j:plain
自分が登壇したGrand Ballroom Aというステージ。このステージで主にJavaScript関連のトークが行われていた。

f:id:IzumiSy:20200212175356j:plain
最も大きいMain Stageと呼ばれる登壇会場。ここではキートークから、対談形式のトークなど人がたくさん集まりそうなセッションが開催されていた。

スピーカーにも比較的大御所の参加者が多く、 有名所では Atlassian, Paypal, Spotify, Amazon あたりの世界的に著名なテック企業から様々なスピーカーが参加しているという感じ。 トークの内容も多様で、MLOpsからマイクロサービス、チーム開発からReactの実装パターンまで多岐にわたる。

f:id:IzumiSy:20200213091010j:plain
Expoホールのマップ。このホールは2日目から開場し、朝から晩までずっと騒がしかった。

自分のトークに関して

自分のセッションではElmに関してちょろっと説明し、実際に弊社でElmを使ったプロダクト開発をやってどんな感じであるかをざっくり話した。

トークの資料そのものは関数型プログラミングカンファレンス2019で使ったものにもう少しJS的なエッセンスを足したものである。 資料自体はすでに英語で作成されたものだったので、リライトには特に困らなかった。 若干関数型プログラミングを知っている前提の内容が多かったため、もう少し純粋なフロントエンドな人たち向けに書き換えたものを用意した。

www.izumisy.work

機材トラブル

今回のカンファレンスは、なんと登壇の際に会場に自分のPCを持ち込むことができないというルールがあった。 そのため、事前にスライドを提出しておき当日は会場に備え付けの(あるいは公式が用意した)ラップトップを使わねばならない。

自分は登壇資料をGoogleスライドで作成していたのだが、当初提出可能なフォーマットにGoogleスライドが含まれていなかったため、事前に運営へGoogleスライドは使うことができるのか」と問い合わせていた。 数日して返ってきた運営の回答は「問題ないが、GoogleスライドのURLを貼り付けたPPTファイルを提出してくれ。」とのことだった。 なるほど、そんなURLが貼り付けられただけの資料をどのように使うのかは分からないが、とりあえずOKということで雑に提出することにした。

正直、この時点で少し当日なにか起きるかもしれないという可能性を10%ほど感じていた。 というのも、自分はトークの内容をすべてスピーカーノートに英語で全部書いておき、発表中はそれを読むだけでいいようにしているからだ。 そんなわけで、会場の機器がどういう設定になっているか分からないが、もしスピーカーノートが見れないと大変なことになってしまう。

しかし、今回は実際にその10%の確率で予想していたことが起きてしまった

(自分のセッションが始まり、ステージ上で準備中)
ぼく 「あれ、スピーカーノートが見れないんですけど」
スタッフ 「え、まじで?」

SafariGoogleスライドのスピーカーノートを開いた状態で全画面化すると、なぜかメインのモニタが真っ黒になってしまう。 なんてこった、これではトークの内容がわからないので話せない。

スタッフにヘルプを要求すると、スタッフもただならぬ事態に動揺を隠せない様子。 しばらくするとワラワラと他のスタッフも駆けつけてくる。 みんな映像機器とガチャガチャといじってくれるものの、おそらく誰もまともに会場設備のセッティングを理解していないことが伝わってくる。

ぼく 「こうなったら、スマホを見ながら話します。これでも見れるんで。」
スタッフ 「それは... やりやすい?(Is it comfortable for you?)」
ぼく 「いや、ぜんぜん... けどもうそれしかないですよね」
スタッフ 「...じゃあそうしよう」

20分尺のところ、さすがにセットアップで5-6分も過ぎはじめていて危険な空気が漂い始めていた。 こんなときのためのコンティンジェンシープランとして用意していたスマホでスピーカーノートを見るという手段を、まさか本当にやることになるとは思わなかった。

さすがに準備の間の緊張がピークだったこともあり、良くも悪くも話している間はすごく平穏な気持ちでトークができた。 この経験から、次からは絶対にスピーカーノートを頼らずに、トークの内容を暗記していくことを心に誓ったのであった。

もう二度とこんな悪夢みたいな体験はしたくない。

DeveloperWeekについて改めて感じたこと

参加してから数日経って、どうやらDeveloperWeekというイベントは存在そのものが見本市的なノリのカンファレンスなんだということが分かってきた。

みんなのトーク内容の殆どが自分たちの製品を売り込むためのセールストーク的な内容で、事実スピーカーの大半はCTOやらセールス・エンジニアというポジションである。 自分のPRO SESSIONというやつも、おそらく製品の売り込み的な意味でPROだったのかなと分かってきた。

とはいえ、資料作成時のポリシーには「スポンサー枠のスピーカーじゃない場合には自社製品のアピールとかしちゃダメよ」みたいなことも書いてあったので、自分みたいに純粋に会社とか製品関係なくトークをしていた人もいたのかもしれないが、あまり目にはつかなかった。

2015年に自分が参加したときはハッカソンしか見ていなかったのでよくわからなかったが、スピーカーセッションのほうはこんな感じでコマーシャル的な要素がとても強い。 まあ、イベント自体やトークの内容自体、おもしろくはあったが、それでも去年参加したelm Europeのようなコミュニティ感というのはマジでゼロだった。

www.izumisy.work

感想

まさか、アメリカで人生最大のピンチとも言えるタイミングを迎えることになるとは思わなかったが、これはこれである意味ものすごいスリルだったので、終わってみるとジェットコースター的な興奮があってウケた。

アメリカで登壇失敗しても日本に返ってくればチャラみたいなものなので、失敗なんてなにも怖くないというのがリアルな感想である。とはいえ、もしこれがYoutubeとかに残ってしまうと思うと... 最悪で仕方がない。

カンファレンス自体は、規模はデカいわ、いろんなトークが聞けるわ、いろんな人と英語で話せるわで楽しかったが、むしろ改めてElmのコミュニティって暖かいんだな〜と思わされた。 やっぱコミュニティと繋がりの強さって大事ね。

ドメイン・イベントについて&Ruby製のよさげなPub/Subインターフェースgemまとめ

Rubyで特にRailsを使う際に「特定のドメインの変化によって別の処理の実行をトリガする」みたいなケースでは大抵の場合コールバックが使われる。 しかし、ぶっちゃけた話コールバックはかなり結合度の高いコードになってしまいがちで、実装的にスケールさせるためにはドメイン・イベントを使うほうが健全であると言える。 martinfowler.com

以下の記事はActiveRecordのコールバックがどのようなときによろしくない感じになるかを説明しており、非常に参考になる。一言で言うと、コールバックを使うことモデル自体に副作用が埋め込まれてしまう。一方でドメイン・イベントを使うことで、副作用がなにをするものなのか(メールを送る、外部のmBaaSを更新する、モニタリングサーバへメトリクスを送信する、等)を意識しないでよくなり、疎結合になる。 techracho.bpsinc.jp

さて、ドメイン・イベントの仕組みを実装する際、もちろんオンメモリなオブザーバ・パターンの仕組みを実装してもよいが、それは開発環境でしか使えない。プロダクションだと大抵APサーバは複数台構成のはずなので、Redisあたりにキューして複数の異なるAPサーバ上のサブスクライバがイベントを拾えるようにしたい。こうしておけばQueue-based Load Levellingもできるようになるので負荷分散もできてスケールする。

もっというならGCPのときはCloud Pub/Subで、AWSのときはSQSを使ったりできるようなプラガブルな感じになっていると最高だ。そんなものを求めていくつか探してみた。

dry-rb/dry-events

github.com

  • みんな大好きdry-rbシリーズのPub/Subインターフェイス
  • おそらく今回紹介するgemの中で一番シンプル。
  • Dry::Events::Publisherというモジュールをミクスインしたクラスを作ってサブスクライバを定義すると、そのクラスに対してイベントをパブリッシュできる。これだけ。
  • 開発が比較的活発なのもいい

kris.leech/wisper_next

gitlab.com

  • 元々はwisperというPub/Subインターフェイスgemを作っていた作者による新しいライブラリ
  • WisperNext.publisherWisper.subscriberというふたつのモジュールが用意されておりそれらをミクスインしたクラスを作るスタイル
  • パブリッシャクラスに対してsubscribeメソッドでサブスクライバクラスを追加していく。
  • 特定のプレフィクスを持つイベントのみをサブスクライブできる機能もある。
  • サブスクライブをする際に同期/非同期を選択できる。

kris.leech/ma

gitlab.com

  • 上と同じ作者のPub/Subインターフェイスgem。内部的にはwisper_nextが使われている。
  • ライブラリの説明に "events as first class citizens." と書かれている通り、wisper_nextとの違いはパブリッシュ/サブスクライブするイベントをクラスとして定義できること。Ma::Eventクラスを継承したクラスをイベントとして扱うことができる。
  • それ以外にはMa.publisherMa.subscriberというモジュールが用意されており、それぞれをミクスインしたクラスを作るスタイルで特に目新しいものはない。

edisonywh/rocketman

github.com

  • 今回紹介するgemの中で一番重厚かつ拡張性が高い。
  • Rocketman::ProdcuerRocketman::Consumerのふたつのモジュールがいて、それぞれをミクスインしたクラスでemitとかon_eventみたいなメソッドが使える。まあわかりやすい。
  • Redis上のイベントもサブスクライブできるアダプタが用意されている。現状はRedisだけだが、Registryという仕組みが用意されているので自前でカスタムの外部サービス用アダプタを作ることも可能。
  • 初期設定では発行されるイベントがキューとしてメモリ上に保存され、Rocketmanのスレッドワーカーが処理していく仕組みになっている。これだとアプリケーションが死んだときキューをロストするので、もしキューを永続化したければRedisをストレージとして使うことができる。
  • 作者によるとRedis PubSubやKafkaはそのあたりのミドルウェアをメンテしたりするコストがかかるが、Rocketmanを使えば実質アプリケーションレイヤで同じことができるので、すごく楽だよ、というのが売りらしい。

総括

アプリケーション層でPub/Subしたいだけならどれでもよさそう。もっとリッチに、イベントを永続化したり複数台構成で負荷分散とかに使いたい場合はrocketmanがよさそう。GCPとかAWSあたりのPub/Sub製品のアダプタも自前で作れそうだし。ただメンテされているのかどうかが若干気になるが...

Reactive Design Patterns

Reactive Design Patterns

Elmにおける依存性逆転(DIP)の表現

この記事を読んでなるほどな〜と思ったので記事にしてみる。

medium.com

依存性逆転とは

雑にいうと実装ではなくインターフェイスに依存させ、モジュール間の依存関係を疎結合にする手法。英語ではDependency-Inversion Principleと呼ばれ、頭文字をとってDIPとすることが多い。

www.martinfowler.com

ElmではDIPをどう表現するか

一般的な静的型付け言語ではインターフェイス相当の言語機能が提供されている。たとえばScalaだとtraitだし、Javaだとinterfaceあたり。しかしElmにはそれらがない。

そこで、Elmでは型エイリアスを使ってインターフェイスっぽい表現をする

type alias Score
    = Int

type alias PersistScore msg
    = Score -> Cmd msg

上のPersistScoreインターフェイス相当になっている。Scoreを受け取り、なんらかの手段で永続化を行うCmd型に変換するインターフェイスである。

もう少し詳しく

ellieで用意されているTODOアプリを例にする。実際のコードのリンクは以下。
https://ellie-app.com/7Z52XPNmbfca1

IncrementDecrementに加えて、データの永続化を行うSaveというメッセージが新しく定義されている。Saveメッセージを受け取ると、何らかの方法で現在のcountの永続化を実行する。

type Msg
    = Increment
    | Decrement
    | Save


type alias Persist msg =
    Int -> Cmd msg


update : Persist msg -> Msg -> Model -> ( Model, Cmd msg )
update persist msg model =
    case msg of
        Increment ->
            ( { model | count = model.count + 1 }, Cmd.none )

        Decrement ->
            ( { model | count = model.count - 1 }, Cmd.none )

        Save ->
            ( model, persist model.count )

update関数は第一引数に永続化のためのアダプタを受け取るため、main関数での繋ぎこみの際に実装を差し込む。ここでは実装はlocalStorageへの永続化を行うlocalStoragePersisterになる。

port localStoragePersister : Int -> Cmd msg


main : Program () Model Msg
main =
    Browser.element
        { init = init
        , view = view
        , update = update localStoragePersister
        , subscriptions = \_ -> Sub.none
        }

この実装はupdate関数がPortに依存しないため、テストの際に以下のようなモックの関数を渡してupdate関数内部でのPort呼び出しの挙動などをテストできるようになる。Portに依存しなくなることでピュア度が増すし、テストもしやすくなる。

-- Portの実装をモックする関数

mockPersister : Int -> Cmd msg
mockPersister _ =
    Cmd.none

このようなPortの抽象化は、Portをたくさん使うプロダクトになればなるほど、意外に必要な場面が出てくる。

完全な抽象化は難しい

しかし、この抽象化はあまり完全なものであるとは言えない。

たとえば、実際にありえるかは分からないが「開発環境ではLocalStorageへ永続化するが、本番環境ではWebAPIへ永続化する」のような機能を抽象化するのは少し無理がある。

そもそも、PortはElmランタイムに処理を投げて終わりであるのに対して、HTTPリクエストの場合にはTaskの結果としてResult型を受け取る必要がある。 メッセージとしてリクエストの結果を受け取らないことが許されてないため、そもそもPortとTaskでモノが違うだろというハナシになってくる。 PortとのデータのやりとりがTaskで行えるようになればちょっとは可能性が出てくるが、あまり可能性はないと考えてよいだろう。

とにかくいろんな会社でインターンしていた学生時代

テック系の学生というと、やはりいろんな会社のインターンに参加していろんなイケてるナウい技術を触って「圧倒的成長!」を求めますよね。 自分もそうでした。

自分はかなり極端な例で、大学を1年休学してまでインターンしてコロコロと会社変えながら10社くらいは行きました。 今思うと楽しかったしお金ももらえるし最高じゃんって感じだったんですが、ぶっちゃけじゃあインターンしまくってずっと成長してたかって言われると微妙です。

会社によってはただバグ潰したりするだけのインターンもあったりして、いわゆる限界効用逓減にぶち当たってただコードをかいてるだけの学生だった気がします。 あとはやっぱ学生でコードかけるとすごいチヤホヤしてくれるのでそれが嬉しい、みたいな。

f:id:IzumiSy:20200129094151p:plain

いまでこそ社会人になって仕事でコードを書くようになったんで分かるんですが、別に起業家じゃなくともエンジニアだって自分のビジョンが必要だよなって感じます。 そうじゃないと、ただのコード生成機みたいな存在にしかなれないんだなと。

ビジョンっていうとなんか壮大ですが、たとえばプロダクトにチョー愛を注ぎたい熱意があるとか、組織を全力でイイものにしたいというアツい想いがあるとか、学生の頃は胡散臭いと思ってたあれこれが結局一番重要なんじゃないかって今は思います。

なにかに強く共感できるとか、全力で入れ込めるってある意味スキルだと思う今日このごろ。学生の頃の自分は「エンジニアとして技術をめちゃくちゃに極めたい」としか思ってなかったけど、じゃあ技術を極めた先になにがあるの?と聞かれても何も答えられなかっただろうな。

ビジョナリーカンパニー 1~4巻+特別編

ビジョナリーカンパニー 1~4巻+特別編

Elmにおける「型によるルールづけ」の考え方

Elmは静的型付言語なので、型のチカラを活かすことでコンパイラに「あっては行けない状態や組み合わせ」をチェックさせることができる。 高度な例としては前回書いた以下の幽霊型(Phantom Type)の記事があるが、もう少し簡単な例を紹介しようと思う。

www.izumisy.work

自作Error型の例

以下は自作のError型を定義した例だ。Error型はカスタムタイプになっていて、エラーのパターンを表現している。 それぞれのエラーはエラーのメッセージを持っていて、それらをtoString関数で取り出す事ができる

module Error exposing (Error, handle, toString)


type Error
    = Internal String
    | Connection String
    | Unknown String
   

type HandledError =
    HandledError String


handle : Error -> HandledError
handle error =
    case error of
        Internal msg ->
            HandledError msg

        Connection msg ->
            HandledError msg ->

        Unknown msg ->
            HandledError msg
            

toString : HandledError -> String
toString (Error msg) =
    msg

このモジュールにはルールがあり、メッセージをStringとして取り出す前には絶対にhandle関数が呼び出される必要がある。

したがって、Errorモジュールを呼び出すにあたっては、以下のようなコードはコンパイルエラーになる

import Error


-- ...


msg = 
    somethingReturningError -- Error型を返す関数
        |> Error.toString   -- Error型を文字列に変換したい(が、コンパイルエラーになる)

なぜなら、Errorモジュールの実装によればtoString関数は必ずHandleError型しか受け取れないからだ。

HandleError型はhandle関数を呼ばなければ受け取ることができない。 なので、以下のようにhandle関数の呼び出しを挟まなければtoString関数は呼び出せない。

import Error


-- ...

msg = 
    somethingReturningError -- Error型を返す関数
        |> Error.handle     -- Error型をハンドリング(←これが必須)
        |> Error.toString   -- Error型を文字列に変換したい

何がうれしいのか

この例は極端に簡潔な例で、別にhandle関数のcase...ofはtoString関数にあってもいいのでは? と思う人もいるのは納得できる。

しかし、上の例で伝えたい点は「型によってモジュールの使い方にルールを与えることができる」という点だ。例えば、Error型とは別にHandledError型がモジュールに用意されていることで、モジュール内部でHandledError型をとる関数というのは、必ずhandle関数を経由した上でしか呼ばれないということが自明になる。

関数シグニチャを見ることで、その関数がどのようなタイミングで呼ばれるかが分かるようになるし、コンパイラによるチェックも効く。そして、この仕組みをより高度に使った例が幽霊型(ファントムタイプ)だ。

実際のプロダクトにおいてどこまで型による制約を設けるかは堅牢性と複雑性のトレードオフになるが、型を自分たちのレールとして使うことの重要性は知っておいて損はない。

ElmでPhantom TypeとExtensible Recordを用いて型安全な状態遷移パターンを実装する

このDiscourseスレッドがかなり面白かった。

OPは「幽霊型(Phantom Type)を使うと特定の順序でしか型安全に状態遷移できないように実装できると思うんだけど、どうしたらいいかな?」と質問している。

discourse.elm-lang.org

実装してみる

回答者からのアイデアによると、Phantom TypeとExtensible Recordを組み合わせて実装することで型安全な状態遷移が作れる。

たとえば、以下のようなゲーム上での状態遷移のパターンが仕様としてあるとしよう。

f:id:IzumiSy:20200104151513p:plain

これを実際に今回のパターンで表現すると、このようになる。

type Transition a =
    Transition


type Allowed
    = Allowed


type Game
    = Loading (Transition { ready : Allowed }) -- ロード中
    | Ready (Transition { playing : Allowed }) -- プレイ可能
    | Playing (Transition { paused : Allowed, gameOver : Allowed }) -- プレイ中
    | Paused (Transition { playing : Allowed }) -- 一時停止
    | GameOver (Transition { ready : Allowed }) -- ゲーム終了


toReady : Transition { a | ready : Allowed } -> Game
toReady _ =
    Ready Transition


toPlaying : Transition { a | playing : Allowed } -> Game
toPlaying _ =
    Playing Transition


toPaused : Transition { a | paused : Allowed } -> Game
toPaused _ =
    Paused Transition


toGameOver : Transition { a | gameOver : Allowed } -> Game
toGameOver _ =
    GameOver Transition

最も重要な部分は、Transition型が幽霊型として次に遷移する状態の許可レコードのようなものを保持している箇所で、遷移の際に呼び出すtoReadytoPlayingなどの関数が引数としてTransitionの保持しているレコードをチェックするようになっている。

この実装表現の優れている点は

  • 型安全性が高い。
  • Game型を見るだけで「次にどういう状態への遷移が許されているか」がすぐに分かる。
  • 状態と遷移のパターンが増えたとしてもGame型を修正するだけでよいので修正範囲が少ない。

などが挙げられ、正直イイことしかない。コードとしても、ある程度Elmに習熟していればさほど難しいものでもなく理解しやすいだろう。

あとはGame型をOpaque Typeなモジュールにでもしてやれば、明示的にLoadingReadyなどのバリアントを利用できなくなるのでより安全性が増すだろう。

型の恩恵を確かめてみる。

上の型を操作するにあたって、遷移の関数ではモデルの状態と遷移の組み合わせがほぼ強制される。

update : Game -> Game
update game =
    case game of
        Loading transition ->
            { state = toReady transition }
            
        Ready transition ->
            { state = toPlaying transition }
            
        Paused transition ->
            { state = toPlaying transition }
            
        Playing transition ->
            { state = toGameOver transition }
            
        GameOver transition ->
            { state = toReady transition }

試しにLoadingからGameOverに遷移するようなコードに書き換えてみる

これはあってはいけない遷移だ。

update : Game -> Game
update game =
    case game of
        Loading transition ->
+           { state = toGameOver transition }

        Ready transition ->
            { state = toPlaying transition }

コンパイルしてみると、以下のようなエラーになる。すばらしい!

The 1st argument to `toGameOver` is not what I expect:

72|             { state = toGameOver transition }
                                     ^^^^^^^^^^
This `transition` value is a:

    Transition { ready : Allowed }

But `toGameOver` needs the 1st argument to be:

    Transition { a | gameOver : Allowed }

Hint: Seems like a record field typo. Maybe gameOver should be ready?

Hint: Can more type annotations be added? Type annotations always help me give
more specific messages, and I think they could help a lot in this case!

ellieで実際のコードも用意したので、興味があればどうぞ。

2019年を振り返ってみる

振り返っちゃいます

1月

  • 圧倒的な家賃の無駄さに耐えられず千葉への引っ越しを決めた。

2月

  • 千葉へ引っ越した。引越し業者にはアーク引越センターというところを使ったが、激安でびっくりした。なお、引っ越しの経緯は去年末のブログに書いている。 www.izumisy.work

  • あとは友達とか会社のひとたちとスノボに2回くらい行ったりした。やはりスノーシーズンと言えばスノボである。ウェアもコロンビアの新しいのを買ったので、気合が入っていた。

3月

  • 会社の同期と@sadnessOjisanとでEastgate HACKATHONに参加した。及川さんとかトレタの増井さんとかを生で見れてよかった。賞は取れなかったが、やはりハッカソンはギリギリで戦っているあの感じが良い。しかし、毎度のこと徹夜による燃え尽き感に慣れない。

  • elm Europe 2019のスピーカーに選ばれた。朝起きてスマホでメールを見たら、オーガナイザーから「あんたスピーカーに選ばれたよ、OKなら返信して」みたいなメールが英語で来ていて、まったく期待していなかったのでちょっと悩んだがすぐにOKの返信をした。こういうチャンスは行動しないほうが絶対に損をするに決まっている。まずやる精神である。

  • こんな社会人ポエムも書いていたっぽい。 www.izumisy.work

4月

  • 応用情報技術者試験を受けた。当日は千葉工業大学新習志野キャンパスが会場だったが、マジで周りに何もなくて勉強に集中できそうだなと思った。学生はとにかく勉強に集中できる環境があるのが一番だ。

  • 6月のelm Europeに向けてパスポートを作り直した。去年Web Summitに行った時点でギリギリだったのでさっさと更新すればよかったマジで。

5月

6月

  • 応用技術者試験にギリギリ1点オーバーで受かっていた。正直受けた当日は「これ落ちたな」と思ったが、結果オーライである。受かれば官軍とはまさにこのことだ。 f:id:IzumiSy:20191229220657j:plain

  • とうとうフランスへ渡りelm Europe 2019で登壇した。人生初の英語トークが海外カンファレンスでの登壇で緊張感はとんでもなかったけれど、同時に最高の体験でもあった。海外の技術コミュニティのホットな感じを身を持って体験できたのも本当に大きいと思う。これによってElmに対する思いをさらに熱くした。 www.izumisy.work

  • ブログで書いたこの記事がものすごくバズってびっくりした。 www.izumisy.work

7月

  • elm Europeに自分が登壇したのを知って、楽天のElmエンジニアである@luca_mugからDiscord上でメッセージをもらい、来年開催のElm Japanの企画について話を聞く。彼もまたElm Oslo Dayでの登壇経験があり、カンファレンスに関していろいろ情報交換をした。

  • Elm界隈で有名な@ababupdownbaさんに連絡をとり、8月のElmミートアップの開催に関して飯をたべながら話し合った。

8月

9月

  • 千葉県を台風15号が襲い、自分が住んでいる地域一帯が大規模停電した。その日はまだ比較的夏日で蒸し暑く、夜になっても35度とかそれくらい。クーラーが動かなくて死ぬかと思った。水は出るものの、さすがに裸に浴びるには冷たすぎてにっちもさっちもいかなかった。人間、電気がないとこうも脆弱なものなのかと思わされた。うちは1日と半日で復旧したが、これが1週間とか続くとマジで死人がでる。 www.nikkei.com

  • 会社のメンバーと技術書典で本を販売した。自分は1章でElmのことを書いている。初参加でかつブース側という特異なアレだったが、やはりテッキーなイベントのフェス感はなかなかイイものがある。Elmについて書いてるなんてウチだけやろ〜とおもったら、なんとmixiが無料頒布している冊子でもElmが取り上げられていた。まあ嬉しいことではあるが。 techbookfest.org

  • ブログで書いたこの記事がバズった。設計の本というのは自分も学生とかもう少し経験が浅い頃にたくさん知りたかったし、当時の自分だったらこういうのが知りたいだろうな〜と思いながら書いた。 www.izumisy.work

  • 夏休みに福岡と長崎へ旅行に行った。正直かなり弾丸ツアーだったので楽しかったと言うより疲労感のが先にきてしまった。博多や天神あたりは想像よりも街全体がコンパクトかつ成熟していて、夜もしずかでおどろいた。いつか福岡に住んでみたいと思う。長崎はハウステンボスに行ったりイージス艦を見たりしたが、路面電車がよく分からんICカードだったのと坂が多いという印象しかない。

10月

  • Architecture Nightというイベントに参加したが、かなりよかった。また開催してほしい。アーキテクチャというと、どうしても抽象的な話になってしまうことが多く勉強会のトピックとしては難しいのかもしれないが、それでもこのイベントはかなり学びがあった。 www.izumisy.work

  • 去年の抱負に書いたとおり不動産投資の本を何冊かちゃんと読んだ。結論、不動産投資はやるならガチじゃないと大ケガすると思った。そもそも不動産を扱うというのは完全に事業を自分でやっているのと同じで、そこには営業、会計、マーケティングなどビジネスをやるにあたって必要なモノすべてが詰まっている。逆に言えば不動産投資をガチでやれる人間は充分ビジネスの素養があると思う。 www.izumisy.work

11月

12月

  • なんとDeveloperWeek 2020での登壇が決まってしまった。まさかのPRO SESSIONの枠というサプライズ。応募時に選ばされたライトニング・トーク枠とは一体なんだったのか... 結局こうやって毎年海外へ何かしらで行っている気がする。しかし、やはりこういうチャンスは「まずやる」の精神で積極的に突っ込んでいきたい。 www.izumisy.work

  • オーガナイザーとして関わっているElm Japan 2020の開催が決まる。なんとElmの作者のEvanも来日するというビッグ・イベント。現時点でCFPの応募や国外からの参加者もまあまあいて、みんなElmのために日本に来るんだな〜!という感動がある。とにもかくにも日本で初めてのElmの国際カンファレンスなので必ずやみんなに楽しんでもらえるものにしたい。 elmjapan.org

所感

こう見るとかなりいろんなことをしたな〜という印象。特にフランスへ行って登壇したというのはかなり自分の中でのティッピング・ポイントになった感がある。

事実、学生の頃から「海外のイベントとかで登壇できたらきっと楽しいだろうな〜」と思っていた。特に自分は、アメリカでインターンをしていたときにDeveloperWeekのハッカソンに参加して現地でのトークを生で見ていたこともあり、ある種の憧れの気持ちは強かった。それだけに、今年のelm Europeでの登壇はすごく特別な経験になった。来年には、自分がまさか登壇するとは思ったなかったDeveloperWeekで登壇することになり、やはりこうして人生に弾みをつけていくのはテンションage↑だなと思う。

仕事の話はほとんど書かなかったが、自分が関わっているプロダクトも今がまさに成長の過程そのものという感覚があり、ソフトウェア・エンジニアとしてプロダクトの成長をよりサポートできる存在になっていきたいという意気込みがある。それは技術的な面でもそうだし、ビジネスパーソンとしての価値という点でもそう。

特にエンジニアリングをやる人間として、よりビジネス的観点からの意思決定をちゃんと行えるようになっていきたい。技術的を用いた解決だけではなく、もしもフェーズ的に必要であればマネジメントの領域にも自分を持っていきたいし、いま組織やチームが必要とする人間にどうやったら自分がなれるかを模索している。そのためだったら、去年ポルトガルに行ってWeb Summitでブースに立ったように、コードを書く以外の仕事になってもいいかなとさえ思う。

とはいえ、自分の未来のキャリアをある程度固めて考えないと、ただの器用貧乏にもなってしまいそうな雰囲気もある。とりあえず年末ゆっくり考えようと思う。

来年もとにかく楽しいことをたくさんしたいな。