2020年のHaskellに関する私の考え

박준규 @curry@hackers.pub
この記事は著者の許可を得てChatGPTを通じて日本語に翻訳されたものです。
- 著者: Marco Sampellegrini
- リンク: https://marcosampellegrini.com/thoughts-on-haskell-2020
2020年のHaskellに関する私の考え
2019年10月に「Stick to Simple Haskell」というタイトルで発表を行いました。その発表の準備と作成には多くの労力を費やしました。発表後、少なからぬ人々が私に近づいて、本当に良かったと言ってくれました。彼らは一般的に予想されるような人々、つまり型レベルプログラミングを決して諦めない人々ではありませんでした。むしろ私のように、そして他の多くの人々のように、日常の開発作業が少しでも楽になればと願う普通のソフトウェア開発者たちでした。
私はその発表で話したいくつかの考えがより広く広まる価値があると思います。あるいは少なくとも、もっと議論される必要があると考えています。Boring Haskell Manifestoのような動きは本当に新鮮な風です。私たちはそのようなものがもっと必要です。
この記事は私ができる小さな貢献です。
30歳
2020年にHaskellは30周年を迎えます。最初からHaskell言語の設計者たちは、Haskellが次のような目的で使用されることを望んでいました:
- 関数型プログラミングの教育
- プログラミング言語研究における革新と発展
- 実際のアプリケーションおよび大規模システムの構築
このうち教育は一旦置いておいて、残りの二つに焦点を当ててみましょう。
プログラミング言語研究者たちはHaskellを愛しています。なぜならGHCを拡張(Extension)を通じて簡単に拡張できるからです。GHCは新しいアイデアを試すための完璧な遊び場です。最近ではファイルを開くと拡張機能のリストが壁のように積み上がっているのを見るのはかなり一般的なことです。
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
これらの拡張は言語と型システムを根本的に変化させます。その結果、はるかに複雑で強力な何かを扱うことになります。あなたはますます依存型(Dependent Types)に近づいていきます。より多くの情報を型に含めることができるようになるため、コンパイル時により多くの保証を得ることができます。私たちは通常これを「ファンシーな型(Fancy types)」と呼んでいます。
それにもかかわらず、「Haskellという言語自体」は1998年以降大きく変化していません。非常に大まかで不正確に表現すると、拡張機能をまったく有効にせずにGHCを実行すると、Haskell98というものを得ることになります。この場合、単純なデータ型、型クラス、怠惰で純粋な関数型コアを使用することになります。
Haskellでアプリケーションを作る
プログラミング言語研究者たちはファンシーな型(Fancy types)に全力を注ぎます。しかし私はPL研究者ではありません。私はソフトウェア開発者です。先に述べたように、Haskell設計者の目標の一つは産業現場で、実際のアプリケーション開発にHaskellを使用させることでした。では、ファンシーな型で武装したHaskellは実際のサービス開発に適しているでしょうか?
紙の上では良く見える
実際のサービス開発は論文を書くこととはまったく異なります。
研究論文は一度発表されれば、もはやメンテナンスする必要はありません。論文に登場するコードサンプルで示される技法やトリックはPDF上では素晴らしく見えますが、実際のコードベースにどのような影響を与えるかはまったく別の問題です。研究者たちは実験的な機能や検証されていない拡張機能に依存することもあります。遅いコンパイル時間も大きな問題にはなりません。
包括性(Inclusivity)
サービス開発はチームワークです。
私は様々な背景を持つ人々と一緒に働きたいと思っています。Haskellを扱うために博士号が必須条件となるチームで働きたくはありません。誰でもアクセスできるコードベースが欲しいのです。ほとんどのビジネスロジックはロケットサイエンスではありません。アプリケーションを必要以上に複雑にする理由はありません。
より実用的な観点から見ると、Haskellに投資する企業の立場では、参入障壁を下げることでより多くの人材プールが開かれます。
わずかな利益(Marginal Benefits)
ファンシーな型はより多くの保証を与え、より安全で正確なコードを書くことができるようにします。ではなぜそのようなものを諦めようとするのでしょうか?
私はその利益がそれほど劇的ではないと考えています。一般的なプログラミング言語からHaskellに移行すること自体がすでに大きなジャンプです。コンパイラが正しいコードを書くのを助けてくれるだけでも十分に大きな利益があります。もちろん、ファンシーな型を使用すればそれよりも少し正確になるかもしれません。x%だけ自信を持てるかもしれません。しかしその複雑さは果たしてそれだけの価値があるのでしょうか?
現実的な観点から見ると、私たちは型システムだけがすべてではないという事実を認識する必要があります。型は本当に強力なツールですが、私たちが使用できる唯一のツールではありません。型があるからといってテストをしなくても良いわけではありません。
シンプルHaskell(Simple Haskell)
私は1998年以降に起こったすべてのことを無視しようというわけではありません。
Haskell98は良い基盤です。しかし改善することができます。核心的なアイデアは型システムをあるがままに受け入れ、ユーザビリティ(ergonomics)に集中することです。型システム自体を複雑にするのではなく、言語をより扱いやすくすることです。
これは結局、ジェネリクス(Generics)を活用し、使いやすい拡張機能を適切に有効化することに要約できます。
ジェネリクスはHaskell98にはありません。ジェネリクスはボイラープレートコードを減らすのに非常に優れています。例えば、generic-lensを使用するとJSONインスタンスやレンズ(lens)を自動的に派生(derive)させることができます。
いくつかの拡張機能は害にならず、むしろHaskellをはるかに扱いやすくしてくれます。
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeApplications #-}
アプリケーションアーキテクチャ
ほとんどのアプリケーションは次のように書けば良いでしょう:
app :: Env -> IO ()
ここでEnv
は依存性注入コンテナ(dependency injection container)を表します。
data Env = Env
{ usersCache :: TVar [User]
, postgresConnection :: PG.Connection
, log :: Severity -> Text -> IO ()
, fetchUser :: UserId -> IO (Maybe User)
, storeFile :: Filename -> ByteString -> IO (Either Text ())
}
私が何をEnv
に含めるべきかを判断する基準は次のとおりです:
- そのリソースがアプリケーション全体で共有されるリソースであるか?(例:Postgres接続)
- 別の実装を提供する必要があるか?(例:テスト時のモック(mock)実装)
もし型が大きくなりすぎたら、次のようにさらに細分化できます:
data UserService
= UserService
{ fetchUser :: UserId -> IO (Maybe User)
, updateUser :: User -> IO (Either UserServiceError ())
, deleteUser :: UserId -> IO (Either UserServiceError ())
}
data Env = Env
{ userService :: UserService
, ...
}
アプリケーションの規模が大きくなれば、ReaderT
デザインパターンを使用してリファクタリングすることができます。
2020年のHaskell
Haskellはプログラミング言語研究者とソフトウェア開発者という異なる二つの集団を両方満足させようと努力したにもかかわらず、成功を収めました。30年が経った今、両方の集団はいまだにHaskellを使用しています。しかし彼らの要求はもはやこれ以上異なることができないほど違います。
ソフトウェア開発者として、私たちはアプリケーション作成に有用なHaskellの機能が何かを理解する必要があります。より重要なのは、有害または非効果的な機能が何かも知るべきだという点です。PL研究者とソフトウェア開発者は同じツールを共有していますが、だからといってファンシーな型(Fancy types)を無条件に受け入れる理由はありません。
Haskell開発者として、私たちはすべての問題が論文のテーマになる必要はないということを認識すべきです。選択肢は次のとおりです:
- 型レベルで美しい解決策を見つけるために4時間かけてクエストに挑戦するか
- それとも10分だけ投資して退屈だが確実な方法で解決し、場合によってはテストも書くか
包括性(Inclusivity)のために少しの型安全性を犠牲にすることも問題ありません。テストを通じてソフトウェアが正しく動作することを十分に確認できるからです。これがまさにシンプルHaskell(Simple Haskell)の核心です。退屈なHaskell(Boring Haskell)という表現の方がより適切かもしれません。
シンプルで退屈なHaskellを書くことは楽しいことです。具体的なコードがもたらす解放感は大きいです。抽象化も良いですが、退屈なHaskellの方が良いです。
初心者が次にあなたに質問したら:
- Servantを勧めないでください。Scottyも立派に機能します。型安全ルーティングは後でも大丈夫です。
- どんなエフェクトシステムも勧めないでください。巨大な抽象化は後でも大丈夫です。
- ElmやGoを批判しないでください。何の成果も得られません。
- Nixを勧めないでください。Stackも十分うまく動きます。あなたが
nix-build
を望み通りに動作させるのに何時間費やしたとしても。 - 彼らが博士号を持っていなくても会話を続けてください。
2020年には象牙の塔(Ivory tower)から降りて、退屈なHaskellを書きましょう。
予想外の展開として、この記事が出てから数時間も経たないうちにMatt Parsonsがジュニアコード(Junior Code)を奨励する非常に良い記事を発表しました。この記事が気に入ったなら、ぜひ読むことをお勧めします。もしこの記事が気に入らなかったなら、なおさら必読です。