🧭

いつScalaを使い、いつ使わないのか

2022/08/18に公開1

前置き

先日Twitterでこのような興味深いツイートを拝見しました。
https://twitter.com/emergent/status/1558043418001313792

なるほどと思ったので、自分が技術選定/アーキテクチャ設計において「どのような時にScalaを選択し、どのような時に選択しないのか」をあらためて言語化してみることにしました。

ちなみにこの記事タイトルは Scala福岡 2019 で講演させて頂いたものと同じタイトルですが、当時の資料は説明が無いと誤解を招く部分も多く非公開にしているため、その焼き直しも含めてこの記事を書いています。

また、ここで記載している内容は2022年8月現在の事情を元にした考えになります。言語の機能やとりまく状況などは日々変わっていくものであり、前提が変われば結論が変わることも当然にあります。あくまで現時点での意見の一つとして参考程度にして頂ければ幸いです。

また、この記事では主に言語機構や周辺ライブラリ・エコシステムといった側面からの判断を中心として記載しています。開発を行う人員については、自分を含め著者自身が集めたり依頼できるメンバー・チームを想定しています。
プロジェクトの体制や組織の人員計画などは個々のプロジェクトだけではなく、マーケティングや組織のMVV(ミッション・ビジョン・バリュー)、事業戦略、採用プール、採用コスト、教育制度などなど色々な要素が大きく関わってくるため、あまり一般化しても意味のある内容にできないと判断したためです。ご了承ください。

Scalaを有力な選択肢とする時

さて、自分がScalaを有力な選択肢の一つとする時は主として 「長期の運用および機能改善をし続ける想定のシステムを開発する時」 です。これに実行環境の要件などの制約を加味して選択肢を絞り込む形になる事が多いです。

また、システムの要件から分散処理系の実装として AkkaScio などを使用しなければ実現が難しい等、「特定のScalaライブラリに強く依存するシステムを開発する時」 も必然的に選択肢として有力になります。
もっともこれに関しては、IaaS や PaaS などのアーキテクチャの選択によって必須にならなかったり、Java や Kotlin から当該ライブラリを呼び出したりする事で、必ずしも Scala が優位な選択肢になるとは限らないケースも多い印象です。

従ってここでは「前者の長期の運用および機能改善をし続ける想定のシステムを開発する時」について掘り下げます。

長期の運用および機能改善をし続ける想定のシステム開発時

これは例えば SaaS のWebアプリケーションのバックエンドだったり、業務システムのサーバ実装だったり、あるいはスタートアップでMVPを明確にした後の機能を段階的に実現していくステップだったり等を想定しています。

ここで重要な事は 「長期に渡り改善をし続ける」 という事です。

例えば業務システムなどにおいて、一度作りきった後は追加開発に投資せずセキュリティアップデート等の保守運用しかしない、といった想定の場合はこの対象から外れます。

またスタートアップ等において、仮説検証がまだできておらずスクラップ&ビルドで仮説検証を行う場合なども当てはまらない想定です。この段階ではそもそもプログラミングに注力するより SaaS のマッシュアップや RPA などを組み合わせて仮説検証する方が賢明でしょう。

ではこの「長期に渡り改善をし続ける」という前提において何故 Scala なのかというと 「型システムの強さ」 が回答になり、何故他の型システムが強力な言語ではないのかというと 「JVMの資産活用」 が回答になります。

型システムの強力さ

近年では PHP に Type declarations が導入され、Python に Type hints が導入され、JavaScript も TypeScript が生まれ、Ruby にも RBS が導入され、と静的検査の活用がますます重要になってきています。

長期に渡り改善をし続けるという事は、プログラムを変更する回数が非常に多いという事です。この変更を加える際に、いかにミスを無くせるか、必要な変更箇所を漏れなく見つけ出す事ができるのか、制約の考慮漏れを正確に無くせるか、と言ったことを考慮し続ける必要があります。

これらを達成するための手法としては証明や様々な形式的手法が存在するかと思いますが、2022年現在において現実的なコストで行える主流な方法が型による静的検査かと思います。

従って、この型による静的検査がどれぐらい活用できるか、という事が変更の回数が多いシステムには非常に重要になります。

この点において、Scala は現在主流な多くの言語に比べて非常に高い表現力の型システムを持っています。

PHP や Python, Ruby, Go といった言語ではまだ高度な型を表現できなかったり、互換性のために妥協しなければいけない部分があります。
Java も Java 8 以降、言語機構の改善が目覚ましいですが、シンタックス的な部分の改善が多く型システムの表現力ではまだまだ Scala と大きな差があります。
Kotlin や C#, TypeScript などもシンタックス的には非常に高機能ですが、Higher Kinded Types や Abstract type members, Type classes, Opaque types などなど型システムの面で見るとやはりまだまだ Scala とギャップが大きいです。

これらの言語では型システムの表現力の差によって、Clean Architecture等の高度にモジュール化されたシステムを構築する際や、Property based Testing や Fuzz Testing など現代的なテスト技法を使いたい時などなど多くの局面で、コードの記述性が下がってしまったり、その低い記述性を補うために実行時リフレクションや動的なメタプログラミングなどを活用する必要が出てきてしまいます。その結果、静的検査が活用できる幅が少なくなりコードの変更を安全に行うことが難しくなる状況が発生しています。

また Scala は型システムだけでなく AST変換による macro や Multi Stage Programming など、メタプログラミングにおいても静的検査を活用できる仕組みを提供しています。

これにより Scala では変更の数が多いシステムにおいて圧倒的に安全で高速な改修を可能にしている訳です。

さて、そうなると Scala 以外の型システムが強力な言語、Haskell や Idris, Rust, F* などなどと言った言語ではいけないのか?というのが問いになるかと思います。

もちろんこれらの言語も有力な選択肢になりうるのですが、運用面においてJVMの既存資産が使えることが Scala という選択肢を後押しするもう一つの理由になります。

JVMの資産活用

Scala のメリットとして Java の既存ライブラリが利用できる点が挙げられることがあります。もちろんそれも大きな魅力ではあるのですが、他の型システムが強力な言語達との差別化という点で見ると、運用面で非常に大きなアドバンテージがあると考えています。

例を挙げると Java Flight Recorder や Java Mission Control などのツール群や JMX, JPLIS などの仕様・インターフェイス郡とそれを活用するサードパーティツールなどなど、JVM 上でシステムを安定稼働させるための様々な既存資産が存在しています。

システムが想定したパフォーマンスを発揮しない場合の調査・解析や、より効率的なリソース配分を検討する等など、実際にシステムを運用する際に必要となるツール群の存在は問題を解決できるできないに直結する場合もありえます。

その点で言えば、F* も .NET Core で安定稼働できる状況とかであれば強力な選択肢の一つになるかもしれませんね。 この辺は著者の .NET 環境の知識が薄いため、適切な評価になっていない可能性があります。ぜひ有識者の方から補足頂けると幸いです。

こういった実行環境でのアドバンテージから、Haskell, Idris といった型システムの強力な言語の選択肢より Scala という選択肢が有力になる場合が多いと考えています。

Scalaを選択肢から外す時

逆に上記の前提が無い場合や、他に特定の要件が存在する場合等は Scala が有力な選択肢にはならないでしょう。例えば1回使い捨てのちょっとしたデータ加工するだけといった局面では、最短で行いたいことが実現できるライブラリの有無などが選択肢として重要な要素になるかと思います。

また、実行環境の要件が制約になる時も他の選択肢が有力になりますし、プラットフォームのサポートがより強力な選択肢がある時なども型システムのメリットよりそちらを優先することもあります。

実行環境の要件が制約になる時

例えばメモリやCPUなどのリソースが高価値な環境で動かす必要がある時などですね。クライアントに配布するアプリケーションやデーモンなどを作る場合、メモリやCPUなどのリソースを極力抑える要件が存在する時があります。

JVMのGCは非常に多くの研究成果が活かされ今も常に改善が続けられていますが、それでもよりシビアにメモリ管理をする必要がある場合は Scala よりも Rust などの選択肢の方が有力になります。

また、AWS Lambda や GCP Cloud Functions などサーバーレスプラットフォーム上で動作させるアプリケーションの場合、JVM上の Scala では Class Loading 等の時間が非機能要件の基準を満たせない場合があります。 またプラットフォームの制限から前述したJVM資産の利用ができない場合もあります。

このような場合は Rust や TypeScript などの選択肢が有力になると思います。この辺は GraalVM の一般化や Scala.js でのライブラリ充足などが進んでくればまた状況も変わるかもしれません。その場合でもJVM資産が使えない制約は変わらないので相対的に他言語が有利な状況はあまり変わらないかもしれませんが。

プラットフォームのサポートがより強力な選択肢がある時

また、プラットフォームのサポートがより強力な選択肢がある時は Scala よりもそちらを優先する場合も多いです。

例えば Android アプリを開発する際には Scala よりもエコシステムやツールチェインなどから Kotlin を選択する事が多いでしょう。例えば Android のGUIに関するライブラリは Scala より Kotlin の方がより開発が活発かと思います。

他にも具体例でちょっと細かい話をすると Kotlin 1.7.10 の標準ライブラリのサイズは1.5Mに対し、Scala 2.13.8 の標準ライブラリのサイズは5.7Mだったりします。これは勿論 Scala の標準ライブラリがリッチだという事もあるのですが、Scala はコレクションフレームワークをまるきり独自で定義しているのに対し Kotlin はプラットフォームのコレクションAPIをラップする形で表現している等の差異からもこうしたサイズの違いが生まれています。もちろんその関係で Kotlin のコレクションフレームワークに Immutable は存在せず ReadOnly/Mutable の分離で表現していたり、戻り値同型の原則を実現できなかったり、と比較して機能的な差異も多いですが、モバイルデバイスという環境においてはそういった機能差を覆す優位性になる場合があります。(この辺はProGuardやR8の最適化によって影響度も変わってくるとは思いますが)

また Android と同様に iOS アプリの開発であれば Swift が有力な選択肢になるでしょうし、Androidアプリと iOSアプリをクロスビルドで開発したいニーズがあれば、React Native を TypeScript で利用したり、Xamarin を C# で利用したり等、要件によって様々な選択肢が候補になり得ると思います。

他にもプラットフォームとは少し異なりますが、Unity を使いたいとかであればやはり C# が有力な選択肢となるかと思います。Unity で F* や Rust が標準サポートされたりしたら大変面白そうですが現時点では Native Plugin で頑張るしかない状況かと思います。

終わりに

という訳で、自分が技術選定をする際にどのような指針で Scala を選択するかあるいは選択しないか、という話を書きました。

Scala はよく関数型言語だとか言われることもありますが、「関数型言語だから」という理由で使う必要は無いかと思います。あくまで目の前にある問題に対し解決できる言語機構を備えた言語を選択し適用していくのが大事だと考えています。現時点では変更の安全性確保やモジュール性の確保といった課題に対し、たまたま出自が関数型言語であった概念が効果を発揮することが多いという状況でしょう。

今後も様々な技術要素を適切に選択し、解くべき課題に対し向き合っていければと思います。

Discussion

emergentemergent

ツイートに対して丁寧にまとめていただきありがとうございます。
感想ですが、本記事の採用ケースに該当したとして、本記事スコープ外の以下を継続していくところが課題かと思いました。@gakuzzzz 様の会社サイトでScala研修などを行ってらっしゃるのを拝見して「長期の運用をするための人材」を如何に作るか、を取り組んでいらっしゃると推察いたしました。

プロジェクトの体制や組織の人員計画などは個々のプロジェクトだけではなく、マーケティングや組織のMVV(ミッション・ビジョン・バリュー)、事業戦略、採用プール、採用コスト、教育制度などなど色々な要素が大きく関わってくるため

私のことで恐縮ですが、私自身はRustを自社のプロダクトに採用し品質面などの恩恵は受けている一方で、やはりそのシステムを継続改善していくための人材という面では苦労しているところがあります。そもそものツイートでScalaのことを調べていたのは、Scalaの言語選定と継続という点で似たような課題がありそうと考えたからでした。

本記事およびツイートへの他の方からのリプライで多々クリアになりました。改めて、ありがとうございます。