kbigwheelのプログラミング・ソフトウェア技術系ブログ

プログラミング・ソフトウェア技術系のことを書きます

Terraform meetup tokyo#3 参加してきました

お疲れさまです、2019/11/01から株式会社Speeeの方でSREとして飯を食わせていただいています! 新しい会社ではインフラストラクチャの構成管理がTerraformということで、Terraformについていろいろ勉強していくうちに流れでTerraform meetup tokyo#3 - connpassにてLT発表することになりました1

本記事では各発表の僕の感想を書いていこうと思います。

Journey to Terraform Enterprise

Terraform EnterpriseはTerraformのノウハウへ料金を払っていると思っている。Terraformの専門家を1人雇うよりこちらのほうが経済的

という趣旨の発言や、Root Module(tfstate)を統合していった、それによる不便は特に感じてないなどが印象に残っています。

terraform と cdkの違い / oracle

docs.google.com

cdkとの比較。cdkは最近流れがきているんじゃないかとも感じています。

前回のアンケート結果でCI/CDしてるのが3割しかなかったので、初心者向けに手軽にCI/CDできるよってお話 / dehio3

Github Actions + Terraformの入門のような感じ。弊社でもTerraform CIは回せていないのでこれを参考に導入させていただきます!

Deep Dive HCL / Keke

Go言語とtfの相互変換性について。tfコンフィギュレーションの自動生成の方法。思った以上に簡単でした。 モジュール以上の共通化・柔軟性を実現したいときはこれを使う感じ?

インフラもオブジェクト図を描いてドメイン分割してからコーディングに進もう / Toshihiko Nozaki

個人的にはこの発表がMVPでした。 話の進め方が上手くて聞いていて非常に理解しやすかったです。 今までインフラを表現するのにシステム構成図で満足していたんですがUMLを使うのもありかなと思いました。 今までそういった発想がなかったためまだその価値も理解できていない感じ。

あとたしかこの発表だと思うのですが1つのサービスでも各要素をライフサイクルごとにRoot Module(tfstate)へ分割しているとおっしゃっていたのは面白かったです。たしかによくVPC / subnetなどネットワークレベルとEC2/ECSなどインスタンス・サービスでRoot moduleを分割するのですが、それを言語化するとライフサイクルごとと言えるのかもしれません。

tfstate分割粒度の種類とそのメリット・デメリットを考えてみた / k_bigwheel

拙作ですが私の発表です。 後のworld cafeで話を聞いてみると2-Bパターンが比較的多くworkspace利用者が若干少ないようでしたがどのパターンもそれぞれ一定数の利用者がいるようでした。 また各々がよいと考えている手法もバラバラです。

突撃!隣のTerraform

これがある意味最もインパクトのある発表でした。 メルカリの社内共通モジュールを作ってQAシェルでYes/Noを選択していくと各チーム向けのマイクロサービスtfコンフィギュレーションセットができるという構成は強いと感じました。 そのまま真似できるものではないものの、社内モジュールの作成・共有の必要性を強く感じました。


このあとのworld cafeも非常に良かったのですが割愛。 あの進め方は懇親会の方式として非常に良いと思いますので他のイベントでも広がっていったらいいなと思います。


  1. ちなみに流れというのは一般参加枠がいっぱいでLT枠が余っていたというのが理由です

「GraphQLは90%のウェブサービス開発者にはまだ時期尚早ではないか」に対する僕のアンサー

数ヶ月前にAWS AppSyncを使ってGraphQL APIを実装していました。 実装を進めるうえでGraphQLの詳細についていろいろ疑問や疑念が浮かんだのでいろいろ調べていました。

その中でQiitaに書かれた以下の記事が非常によかったのでそこで挙げられた疑念や欠点の指摘について2019/04時点でわかる範囲でアンサーしようと思います(主に僕の思考の整理のために 😋)。

GraphQLは90%のウェブサービス開発者にはまだ時期尚早ではないか - Qiita

免責

先に断っておくと、この記事は元記事の批判ではありません。 言及先の記事は2017/07時点での周辺ライブラリの状況や不完全な情報提供を踏まえると非常に良い記事で優れた洞察を示しています。 本記事ではそこから2年経った今の状況の反映しつつ、GraphQL APIを実装する上で僕が持った知見・意見を書きます。

元文への言及と僕の意見

LSUDsとSSKDs

一方、社内マイクロサービスにアクセスする時にはまずデータ量を減らすことに対するインセンティブが減ります。つまりGraphQLのメリットは弱まります。また、使う対象もある程度は把握できるので、最初からそこまで大量の使うかどうかも分からないデータをレスポンスに乗せない、ということもできます。

完全に同意できます。ただ、ある程度使う対象がわかっているとはいえそれらのマイクロサービスを使った新しいサービスを作るときはGraphQLで最初から書いていたほうが余計な手間が少なくて済みます。

最初からそこまで大量の使うかどうかも分からないデータをレスポンスに乗せない、ということもできます。

これをやってしまうと将来作れるサービスの可能性を狭めてしまうかもしれないわけですね。

当然ながらGraphQLでAPIを実装することはSSKDs向けの固定的なREST APIと比べてコストがかかるためROI次第ですが、僕はマイクロサービスの利点は複数の社内マイクロサービスを組み合わせて今までなかったソリューション・サービスが低コストで作れること、新アイデアの低リスク化だと思っているのでSSKDs向けのAPIでもGraphQLでそれらの未来に投資することは結構ありだと思っています。

なお、RESTに対して「サイズが小さくできる」というのが圧倒的な強みみたいに言われますが、RESTだって同じことをやっちゃいけない、ということはありません。GoogleAPIはfieldsというパラメータを付けることで結果に含まれるレスポンスのカラムを絞る機能を提供しています。

いわゆるfieldsパラメータですね。多くのREST APIでサポートされています。(これはかなり複雑な方ですがelasticsearchの例)

これも書かれている通りだと思います。それを踏まえてRESTに対するGraphQLのメリットを挙げると、APIごとにフォーマット・実装が違うfieldsパラメータを仕様化したことと概ね1階層のfieldsしか指定できないfieldsパラメータが多い中でJSONのネストされた階層構造でもfieldsをわかりやすく統制された方法で指定できるようにしたことだと思います1。 そのメリットがGraphQLを導入することによる複雑性の増加のデメリットと比較して割に合うかはまた別の問題ですが・・・。

他の某所のチャットで話題になったところによると、バッチでまとめてもってくるのもメリットとしてあげられるけど、SSKDsならBFF: Backend for Frontend使えばいいよね、という話もありました。また、ウェブブラウザからリクエストを送信するときにも、multipart formを使うバッチリクエストをGoogleは提供していたりします。Microsoftガイドラインでも見かけた気がしたけどソースがみあたりませんでした。

これに関してもRESTでも同じことできるよねはその通りだと思います。 RESTに対してGraphQLを使うメリットを挙げるとそのバルクリクエストの方式が公開された仕様として定まっていること、つまり実装しやすく知識の使い回しができるので利用もしやすい、という点が1つあります。

もう1つのメリットがリクエスト-レスポンスレイテンシーの低減です。 これはBFFにはできません。

こちらの例を用いて説明します。

REST APIのデメリットとしてこのページでは複数の親子関係のあるリソースの例が用いられています。 BFFを使った場合、クライアントのブラウザないしネイティブアプリからのリクエストは1つで済みますが、BFFがこの3種類のリソースへのリクエストを肩代わりすることになります。

この際REST APIがhttpベースのAPIでありネットワーク的な距離を考えるとどうしても1リクエストに付き最低でも数msのレイテンシーが発生します。 これはhttp/2のようなHTTPリクエストを束ねることができる技術でも解消できません。 なぜなら2度目、3度目のクエリでどのidのリソースを取得するかは1度目のリクエストの結果をparseしないとわからないからです。 GraphQLはそのIDをlookupしていく過程をサーバサイドで肩代わりしてくれるわけですね2

そのためネットワークレイテンシが大きいケース、親子関係が長いケースほどGraphQLを使えば行って帰っての回数を減らすことができます。結果的に描画の高速化につながるでしょう。

クラサバ時代に戻るデメリットとメリット

ではサーバー側は、というとSQLのエンジン相当を各サーバーの実装者が書かないといけない、ということです。パーサーはあるでしょう(少なくともNode.jsには)。クエリーを見てストレージエンジンにアクセスして結果を整形して返すところは実装が必要です。

これに関しては僕も結構大変そうだなーと感じていたのですが、Resolverというデファクトスタンダード的な仕組みがあります。 これが結構便利で例えばAppSyncだとDynamoDBのResolverはこんな感じになります。

{
    "version": "2017-02-28",
    "operation": "Query",
    "index": "todoid-index",
    "query": {
        "expression": "todoid = :todoid",
        "expressionValues": {
            ":todoid": {
                "S": "$context.source.id"
            }
        }
    }
}

DynamoDBのクエリ文法がxxxxなので一見長いように見えますが、これSQLで言うとSELECT * from Todo where todoid = ?に過ぎません。 fieldのフィルタリングなどは自動的にやってくれます3

詳細に寄って、個別のfieldごとにResolverを書くこともできます。上の方のREST APIの例で言うとフォロワー全員の名前を文字列の配列として返す場合などをイメージしてください。トップレベルのリソースのデータを取ってくるクエリとそのフォロワー名を取ってくるクエリを別々のResolverにできます4

DBへのクエリの効率化のため、JOINを使ったクエリを一度だけ発行してそれをpackingする場合はこんなに簡単には行きませんが、このResolverという考え方・仕組みはうまく複雑性の増加を緩和してGraphQL APIの実装を理解しやすく取り回しやすいものにしていると思います。

GraphQLが圧倒的な勝者になる条件が1つあります。それはGraphQLをネイティブで実装したミドルウェアやデータベースエンジンが登場し、サーバーコードを実装しなくてもGraphQLが簡単に扱えるようになることです。SQLを使うRDBのGraphQL版です。

2019/04現在、ほぼほぼそれに当たるものが登場しつつあります。 AppSyncはDynamoDBやMySQL(Aurora Serverless)に対して簡単かつ権限管理機能もついたGraphQLアクセッサとして機能していますし、今日名前を初めて知ったんですがArrangoDBというものもネイティブでGraphQLをサポートしているようです

データベースをインターネットに晒す格好にはなりますが・・・

前述のAppSyncなどではユーザー認証とアクセスできるデータを組み合わせてそこそこ細かく制御できるので(いわゆる認可機能)、サーバレスでDBエンドポイントを公開することもありになってきたんではないでしょうか5

まとめ

作っているサーバーのユーザーはSSKDsでしょうか?それなら今は使う必要はないと思います。利用しているウェブサービスにアクセスするためのクライアントとしての利用だけで我慢しておくほうがいいんじゃないでしょうか?あるいは簡単に使えるミドルウェアやストレージエンジンが出てくるまで待つほうがいいでしょう。

前述の通りすでに対応するストレージエンジンやDBラッパー、GraphQLのBFFサービスなどが出てきています。 Scalaでもsangriaライブラリはすでに十分なスターを集めていますし、2019/04現在十分に成熟しつつあると言っていいんじゃないでしょうか。

僕の周りではGraphQLを擁護する声は無かったけど、課題は「サーバー開発工数(複雑さがここに集中してメンテナンスとか大変かも?というのも含めて)」の一点なので、ここを論破すれば状況は変わりますので、反論エントリーお待ちしています!

ここは上でも書きましたが一からAPIサーバを実装する場合は比較的辛いままだと思います。 AppSyncのようにマネージドサービスにGraphQLの皮をかぶせたり組み合わせ可能にしたりするサービスが使えれば非常に楽かと。あとDBにGraphQLエンドポイントがある場合も使いやすいでしょうね(その場合は独自で認証・認可の仕組みが必要そうですが)。

ここから蛇足: 1週間GraphQLを調べつつ実装した感想

僕のGraphQLに対する感情はガートナー ハイプサイクルをたどって今ちょうど幻滅期を抜けたあたりです。 実際に実装せず上辺だけ調べていた時期はREST APIであやふやなあれこれの仕様(ページングやエラーハンドリング)なども合わせて定めてくれるのかと思っていましたが実際に触ってみるとfieldの絞り込みと引数、あと型定義以外はほとんど何も定められておらず結構がっかりしました。 そこを超えて単なるクエリのためのDSLとして捉えるとそこまで悪いものでもないかなと今は思っている感じです。

今後の社内マイクロサービスでGraphQLをどの程度使いたいかというとまだ微妙な温度感で、多数のリソースの種類があってしかもそれに親子関係があり、フロントなどから一度のクエリでjoinして取ってきたいという要望がある場合はGraphQLを使ってもいいと思います。 リソースの種類が少なく相互関係性が希薄なAPIなら6REST APIやgrpcのようなgraphQL以外の選択肢も十分ありです。

たまにGraphQLはネクストRESTだ、REST APIはオワコンになる的な論調で記事を書いているのを見かけますがそうは思えません。ステータスコードなどに与えるエラーの仕様がない、相変わらずhttpヘッダはGraphQLの場合も使う必要があるなどRESTのほうが優れていると感じる点も多いです7

まとめると、GraphQLを使うときはサーバサイドでJOINできる、ネストされて構造化されたデータのfield絞り込みができるという機能を有効活用できるときにしましょう。 そのときもGraphQLをあくまでクエリのためのDSLだと捉えればREST APIからスムーズに移行したりうまく既存のREST APIと協調することができるでしょう。


  1. 実際GraphQLの仕様の中核はこれだと思います。AliasやVariableなんかは別になくてもなんとでもなるので。むしろあれいるのでしょうか・・・

  2. 元記事ではそれも踏まえてクラサバとあとで批判されているわけですが

  3. まだ利用していませんがscalaのgraphqlライブラリsangriaも同様の仕組みのはず

  4. まあ今話しているようなtype定義は実は良くないんですけどね。普通に小fieldとしてfollowerオブジェクト自体を持って、そのfolloerのnameフィールドを定義してやったほうがいいです

  5. 余談ですがこの辺Firestore, realtime databaseのようなfirebase系DBの影響が見えるような気がします

  6. そもそもAPIサービスで相互関係が希薄な複数のAPIを提供すること自体がほぼほぼありえないといえばありえませんが

  7. たぶん実際にGraphQLのAPIを実装した・使ったことがないかGraphQLの未来に過剰な妄想を書いているか、アオリ記事書いてPV稼ぐのが目的の人間が書いているのでしょう

JavaScriptのAsync/Awaitにかかえていた違和感

JavaScriptのAsync/Awaitの仕様を数年前に見たときからなにか違和感を感じていたのだが、以下の記事で改めて全体を見通した時にその違和感の元に気づいた。

【初心者向け】JavaScriptの非同期処理を理解する callback、Promiseそしてasync/awaitへ - Qiita

Promiseの結果を取得するためにawaitが必要なことは直感的にわかる。 問題はasyncだ。これは単に返り値がPromiseであることを示すマーカーであり、返り値の型を明示する必要がないJavaScriptの思想とはそぐわない気がする。 これは例えばPythonのyieldとよく似ている。yieldは返り値の型をGeneratorにするが、その関数へasyncのような修飾子をつけることはない。

幸いにも疑問点に気づけば回答はネットに転がっていた。

どうやらawaitが予約後でなかったことと、parserの解析が簡単であることからasyncが用いられているようだ。 その理由に納得はしないものの、とりあえずの回答が得られたのはよかった。

正しい意思決定(判断)の価値

disclaimer

以下の文章は私個人の意見であり、特定の企業や人物を揶揄する目的のものではありません。

本文

僕はプログラミングが好きでずっとプログラミングをやっていたせいか、エンジニアで手を動かさない仕事の価値というものが長いことよくわからなかった。 8年ぐらいのエンジニア生活の中で、合理的で効果的な意思決定がなされたシーンを実感してこなかったこともその一因かもしれない1

だからいわゆるアーキテクトや上流過程というものを結構長いこと軽視していたのだけど、以前こんなことがあった。 過去のある時点でAWS上にEC2で動くアプリケーションを作ったのだけど、実はそれに相当するサービスが開発開始当時からAWSに存在した。 そちらを使えば内製するより運用費が安く、4~6人月を投資する必要がなく、保守の手間も全くかからなかったことが発覚したのだ。

言い換えればこのアプリケーションを自分たちで作ると判断した人間は数千万をドブに捨てたわけだ(AWSについての知識がなかったがために)。 だから、(もちろん考え方にもよるが)このときAWSのサービスを知っておりそれを使うという判断が出来たら数千万円を使わずに済んだことになる。

これに気づいたとき、僕はこの意思決定には数千万円分の価値があったと言えるのだろうか?とふと思った。

こんなことがあってから、僕は意思決定についての価値をもう少し考えるようになった。 現在自分が行おうとしている意思決定がどれくらいのコストを生むか(一時的コスト・継続的コスト)、それが将来的にどれくらいの利益を生むか(短期的な利益・長期的な利益)。

そうして考えながら仕事中に意思決定をしていると、この意思決定というものが思っていた以上に危険なことがわかった。もちろん会社がどの分野・どの部署・どのプロダクトに投資するのかといったことは重要な意思決定なのは自明だが、僕のような平のエンジニアでさえ、何気ない判断で会社に長期に渡って数千万、数億の損失を生む意思決定ができてしまう。

ソフトウェアエンジニアの間違った意思決定の多くの場合現金ではなく、将来のエンジニアリングコスト(人・時間)であるため計測しづらい。結果的に多くの会社でそういった損失は計測を行わず振り返りも反省もなされない2。そのため大手IT企業などでも正しい判断基準を持たずランダムウォークするようなアーキテクト・マネージャが散見され、数億や数十億以上のコストを見えずに垂れ流したりあり得たはずのサービスの可能性を潰したりしている。

そう考えると、もし意思決定を適切・慎重に正しく行えるものがいてその人物が継続的に意思決定を行えば会社はたくさんの潜在的損失を回避できることになる。 僕は従来マネージャや管理職・アーキテクトといった立場の人間を軽視していたが、上のような人物がいればその利益は計り知れない。

それこそ業務時間の90%でコードを書いて残り10%で間違った意思決定をしてしまうプレイングマネージャーよりも、業務時間の98%寝ていても残りの2%の時間で正しい意思決定をしてくれる優秀なマネージャーははるかに会社へ貢献していることになる。

だから、僕は相変わらずコーディングが好きなことは変わらないけども、最近は自分の意思決定そのものや自分以外の意思決定する人物・役職のことを見直している。 果たしてその意思決定(者)が本当に正しく、長期的に見て正解だったと言えるかどうかを。


  1. むしろ記憶に残っているのは知識の足りていない人間が間違った方向に意思決定してしまった場面ばかりだ

  2. 余談だが、そのため一部のエンジニアは意識的か無意識的かわからないが間違った意思決定のツケを払う数年後には転職していなくなってしまうようなことが常態化している。ウェブ業界では特に顕著だ

会社の開発用PCをMacBook ProからデスクトップPC(ubuntu)へ切り替えた

tl;dr

  • 会社で使うPCをMBPからデスクトップPC(ubuntu)へ切り替えた
  • dockerがよりシンプル・高速に走るlinuxが今は良い
  • 自宅もubuntuなのでショートカットキーなどを揃えたかった背景も

もう少し長い背景説明

MacBookは素晴らしい。

まずbrewの信頼性が高い。 イケてる開発ツールが多い。WIndows, Linuxでも提供しているツールも多いがSequel ProなどMacでしか提供されていない素晴らしいツールも多い。 なにより利用者が多い & LinuxWindowsのようにディストリビューション/バージョン間の差異が少ないことから多くのノウハウがウェブ上にあり、周りに使っている人間が多いためヘルプも頼みやすい。 見た目もイケてるし、ミッションコントロールワークスペース間の移動などUIも洗練されている1

なぜやめるか。

一つは自宅で使用しているubuntuとショートカットキーやUIが違うことによる操作への最適化限界がきになったからだ。 ubuntuOSXではワークスペースの移動のキーなど多くのOS標準操作方法が違う。 そのためショートカットキーを覚えることが難しく、毎回設定を参照するようなことをしていた。 また、ubuntuではCtrlが使われるケースでMacはMetaキーを使うため、ブラウザ操作などショートカットを多用するシーンにおいて間違ったキーを押すことが多かった。 OSXのUIは明らかにubuntuより優れているため自宅のデスクトップをiMacないしMac Proにすることも考えたが、コストパフォーマンス面でPC/AT互換機と比べて格段に悪くまたMac Proが年単位で放置された過去を鑑みるとアップデートサイクルをこちらで制御しにくい点も欠点となり、自宅でOSXを使うことは断念した。

となると可能なら会社で使うPCをubuntuにしたいところだったのだが、ずっと諦めていた。 というのは以下の点で会社所属のエンジニアにMacBookを渡すことは合理的であるためだ。

  1. 会議室など自席以外でプロジェクタ・大型モニタを使う時にノートブックは有用
  2. MBPなどapple製品はブランドイメージが高くエンジニア受けも比較的良い
  3. エンジニア全体でMBPに統一するとトラブルシュートや(情シスなどによる)サポートが簡単
  4. 開発チームとしてもPCをMacBookに統一すると環境差がなくなり開発しやすい

が、先日MacBookによるコンパイルの重さ・HDDへのアクセスの遅さ・メモリの足りなさに辟易としているときに気がついた。

  1. うちの会社はMac, ubuntu, windowsバラバラ
  2. 退職者の比較的高性能なデスクトップPCが余っている
  3. 会社に私用のMBA持っていっているので会議用はそれで事足りる
  4. ローカル環境での開発にDockerを利用しているため開発環境の差が吸収できている
  5. CPU使用率を見ているとdockerの特にIO部分で負荷が高いが、これはDocker for Maclinux VM内でdockerを動かしているため。linuxで直接dockerを動かすとこの負荷はかなり軽減できる(はず)

以上のような理由でむしろubuntu(linux)を使わない理由がないことに気がついた2

現在ほぼubuntuへの移行が終わった状態だが、メモリやCPUなどが相対的に強化されたこともあり非常に快適に感じている。 特にDocker周りの動作が非常に軽く、レスポンスが早くなった。

MacBook Proからubuntuに移ったデメリット

当初は特にubuntu移行のデメリットはないかと思っていた(UIが劣るのは織り込み済み). が、最近徐々に手を出し始めていたxcodeによるiOSアプリ開発ができないことは少し頭が痛い。 手元のMBAは非力で開発にまったく使えないため、この点に関しては何らかの対策を打つ必要がある。

一般的な新卒ソフトウェア系エンジニアにとってUbuntu(Linux)は第一選択肢になるか

たぶんならない。僕がubuntuを使えるのは所属している会社がスタートアップで各々の裁量が大きいこと、特にソフトウェアエンジニア内でMacBookに揃えようという方針がないこと、ubuntuの経験がそこそこあり他人を頼らなくても自力で大抵の問題は解決できることなどが背景にある。

新卒や大企業のソフトウェアエンジニアの場合、いろんな要因でapple製品に統一したかったりするメリットがある。そもそもMacBookが強い会社は新卒もMacBookを使ってくれたほうが教えやすいし教えてもらいやすい。

ただ、今後長いことソフトウェア技術の核となるdockerがmac上では遅いというのは軽視できないデメリットで、Dockerを効率的に使う視点から長期的には開発PCでもlinuxの利用が増えていく可能性はあると思う3


  1. ubuntuではウィンドウ毎にワークスペースを分けて管理することができない。ショートカットも統一された方針を持たず、キーバインドのコンフリクトがしょっちゅう発生する

  2. 新卒のときにwindows機でのweb開発に苦しみ、MBPでの快適さに感動した経験から視野が狭くなっていたのだと思う

  3. linuxはもう少しUI・UXがなんとかならないとエンジニア間ですら支持が広がらないかもしれないが

@kmizuさんのimplicitlyについての記事を読みながら思い返したこと

この記事を読んでていくつか感じたことと思い出したことをツールドフランス第15日目観戦しながら書き出します。 特にまとまってない。

記事中の黒魔術という表現についての個人的な補足

僕の勘違いでなければ、@kmizuさんは implicitly が静的に(コンパイル時に)一意に決定する、しかもそれがメタプログラミングやマクロ・リフレクションなどのコンパイル処理の外側ではなくコンパイル処理(= ファーストレベルの言語仕様1)として決まる点を指して黒魔術ではない、と行っていると思いました。

ただ、implicitlyを黒魔術的だと認識しているような人にはそこら辺の文脈・ニュアンスが伝わりづらい気がして非常に良い記事だけに少し惜しく感じました。

implicit parameterと型クラスの親子関係

おそらく、 implicit parameterという概念(もしくは、その元ネタである型クラス)

implicit parameterって型クラスから派生した概念でしたっけ?と少し引っかかりました。

implicitは特に型クラスを意識せず実装されたけれど、後々型クラスにも使えることがわかったために型クラスの表現方法としてimplicit + context boundが利用されている、とどこかで読んだような (もしかしたら説明を単純にするためにあえてこう書いているのかも)。

2019/07/26 追記

@kmizuさんにその後詳しく教えていただいたのですが、広く一般には知られていないものの紛れもなくimplicit parameterは型クラスのための機能として作られたとのこと。

詳細はこちら。

implicitlyを理解することの困難さ

元の記事は簡潔にまとまったスクロール3画面分ほどの記事なんですが、僕はここで説明されていることを完全に理解できるようになったのは真剣にScalaを触り続けて3,4年経った後だったと思います。

この記事の中ではimplicitlyに到達するまでの道のりがだいたい以下のように提示されています2

  1. implicitの使い方・作用
  2. context boundの意味
  3. implicitlyの使い方・作用

Haskellなどのバックグラウンドのない、Javaを触ったことのある程度の人間(だいたい10年前の僕)がもうちょっと補足するとこうなります。

題目 僕が理解した時期(Scala使い始めからの年数) 理解にかかる労力
implicitの使い方・作用 1~2年 めっちゃ大変
context boundの意味 3~4年 型クラスを理解していると一瞬。 理解していないと使い方・概念がさっぱりわからない
型クラスの概念 4~6年 大変。数回の挫折をはさみつつ足掛け5年かけてすごいHaskellたのしく学ぼう!独習 Scalaz — 独習 Scalazで理解した
implicitlyの使い方 1 ~ 2年 それ自体は死ぬほど理解が単純。
型クラスとしての使い方に気づいたのは型クラスを理解した後だった

上に書いていますが、僕が元の記事をだいたい理解できるようになったのは型クラスをぼんやり理解し始めた4年目以降でした。

こう書くとお前どんだけ頭悪いんだよと思われるかもしれませんが、実際のところ3つの職場で一緒にScalaを書いた同僚諸氏で確実に僕より早く・より深く上を理解していたのは10人中2,3人程度でした。 勉強への熱意や理解の早さはともかく、実際にScalaを使っている人でもかなりの人がimplicitやcontext boundなどをよくわからず使っている、またはそれらを知らなくても十分Scalaを使えていると思います。

implicit は本当にわかりにくい

改めて考えるとscalaのimplicitは本当にわかりにくいと思います。これはimplicitに多数の側面やシンプルでない仕様があるからだと思います。 以下implicitのわかりにくい点。

  1. 文字通りの暗黙的な関数パラメータという側面
    • sttpのbackendのような、メソッド呼び出しの中で本質的ではないパラメータをメソッド呼び出しへ書かないことでよりメソッドの本質に集中する機能
    • implicitで渡されるパラメータは環境・コンテキストなどのイメージ
    • 多用により可読性の低下を招くと言われていたimplicitはこの用法のイメージ
  2. 型クラスとしての活用の側面
  3. implicitという修飾子がメソッドの外側・内側両方で作用する点3
  4. implicitが探索するスコープのルール
    • 暗黙のパラメータ解決優先順位 | eed3si9nがよくまとまっていますが、継承、import、local宣言、パッケージオブジェクトなど片手に余る様々な方法でimplicitの値は渡すことができます
    • Scala熟練者が多用するにも関わらず初心者が知らない概念の一つに暗黙のスコープ (implicit scope)があります

      2) 探している implicit の型に少しでも関係のある様々なコンパニオンオブジェクトやパッケージオブジェクトから成る暗黙のスコープ (implicit scope) (具体的には、型のパッケージオブジェクト、型そのもののコンパニオンオブジェクト、もしあれば型の型コンストラクタのコンパニオン、もしあれば型の型パラメータのコンパニオン、型の親型や親トレイトのコンパニオンなど)。

結論

何が言いたいか。

1. implicit / context bound / 型クラス / implicitly わからないのは当たり前

それらがすぐにわからないからと言って、自分が頭が悪いと思ったりScalaに絶望しなくていもいいと思います。これらの概念は広範に普及しているプログラミング言語の中でもかなり難しい概念ではないかと。

2. implicit なんて全く使わなくても better java としてそこそこ使える

過去にimplicitもFutureもモナドも使わないScalaによるAPIサーバをScala初心者の人たちと一緒に書きましたが、Future・モナド・implicitを半端な理解で乱用したアプリケーションより10倍良いコードでした このように単なるbetter javaとしてscalaを使うことは全然ありだと思います4

3. implicitly が黒魔術に見えている人はimplicitly以前にimplicitそのものや型クラス、型クラスを実現するためのcontext boundというsyntax sugarという概念がわかっていない

ここで話が最初の記事に戻ってきます。 元々の記事は非常によくまとまっていて内容は正しいのだけど、最低でもScala数年触っているような人じゃないとあれ読んですぐ理解するのは難しいんじゃないかと思いました。

4. 型クラスはちゃんと理解したほうがいい

implicitはScala 3でなくなるそうですが、型クラスの概念は多少変遷しうるもののおそらく今後20年, 30年以上プログラミング技術の中で使用される可能性が高いです。 すごいHaskellから逃げずに(!?)頑張って勉強しておきましょう。それにより得られるリターンは本当に大きいと思いますし、実際大きかったです(プログラミングの表現の幅が広がった)。


  1. これ、もうちょっと適切な言い回しがある気がする

  2. 実際にimplicitlyだけを読み解くならこの全道筋を理解する必要はないけど、記事中で言及されているようなcontext boundのsyntax sugarと組み合わせた用法を理解するにはたぶんすべてを理解する必要があるでしょう

  3. 参考: implicitが内向き外向き両方の意味になってしまっている。

  4. 最もこのようなケースではJavaでいい気もするのでケースバイケース。特にScalaはリクルーティングがしづらくて・・・

ヘビーカロリーな問題の解決方法

この前、久しぶりに非常にヘビーカロリーなコードを書いた。 僕は普段コーディングをしている日はScalaの場合テスト含めて100~300行程度のコードを書いているのだけど、機能まで書いていたコードは5~10日で100コード程度しか書いていなかった。 というのも内容がhttp clientの低レベルな実装とAWS V4 signatureの実装に関するコードで問題がかなり難しかったため。 この問題、そもそも自分で解決できるかどうかギリギリレベルの難易度だったのだが、なんとか解決することはできた。

ここでは後のためにその解決方法を言語化しておく。

1. 何が問題なのかをはっきりさせる

今回の場合、根本的に何が問題なのか、何ができないのか、何をやればいいのかを調べだすのに丸2日分ぐらいの時間がかかった。 資料が少ないためでもあったが、まず問題の原因をはっきりさせることは基本であり絶対。 ここがあやふやなママ試行錯誤しても効率は上がらない。

2. 取りうる手段を列挙・検討する

取れうる手段を検討する。 今回の場合は既存のライブラリを頼ることが難しいことがある程度調べてわかった。 その結果、妥協して定期作業が必要な認証にするかそれともきちんと自分で実装するかの選択肢が出た。 最終的に後者を選んだ。

上長やチームへ説明するためにもトレードオフできる選択肢を出すことは大事。 それで自分がしようと思っていた以外の選択肢が選ばれたり新しい選択肢が出ることもままある。

3. 実装したつもりが動かない。

まず絶対に動く例を作る。例えば今回のケースではpythonのサンプルコードが会った。 まずそれを動くようにして、徐々に動かないコードと近づけていって、どこで動かなくなるかを調べた。