この記事では、F# を気まぐれに触る開発者の一人として、感じたことを雑多に書いてみます。F# を知らない人や、少し触ってみた人の参考になれば幸いです。
始めに断っておくと、私は関数型言語に詳しいわけでもないし、F#もそんなに使っているわけではありません。気が向いたときにたまに使ってみる程度です。ですが、そういうレベルだからこそ言えることもあるのではないかなと思います。
使い心地
ざっくりした感想として、以下のように感じます。
-
なかなか良い言語だと思う。普段使う言語とは違う書き心地で、書いていて楽しさがある。
-
だけど、少なくとも自分の場合、使いどころはあまり無い。業務で使う言語ではないので、用途は雑用の小物に限られるが、それは Python や Go で十分なことが多い。
-
小物に .NET は重たい気がする。 F# 自体は割とライトに書ける言語だけど、付属するランタイムが大きい。
-
困るレベルではないけど、起動にちょっと時間がかかるのと、ランタイム付属のシングルバイナリにした際のサイズの大きさが気になる。
-
これは .NET の問題ではなく、あくまで自分の用途と合わないだけ。.NET は大きなものを作るのには向いていると思う。けど、そういう多人数での開発であれば C# を使う方が良いと思う。
-
重たいとはいいつつ、基盤に .NET があるのは F# の強みだと思います。標準ライブラリだけで HTTP 通信とか JSON の読み書きとかができますからね。例えば Rust だと、標準ライブラリを最小限にする文化があるため、乱数や正規表現でも外部ライブラリを使います。それに比べれば、 F# は標準機能でできることが多い言語だと言えます。
あと、小さなものを F# スクリプトで書くというのは案外悪くないと思います。
話題を最近見かける気がする
世の中のメジャーな話題ではないし、気のせいかもしれませんが、F# 関連の話題が最近になって出てきている印象があります。
-
書籍『関数型ドメインモデリング — ドメイン駆動設計とF#でソフトウェアの複雑さに立ち向かおう— 』の日本語版 が 2024 年に出版
-
技術ブログ『F# for Fun and Profit』の日本語訳サイトが登場
- 公開がいつ頃かは分からないですが、私が F# を知ったばかりのころ (2025年の年初) には無かった気がするので、かなり最近と思われます。
F# for Fun and Profit は有名な技術ブログで、 F# の良さや F# で書くコツなどを書いたものです。2012年ごろから書かれているので、コンテンツとしては古いのですが、最近になって日本語訳が出たようです。また、このブログの著者の Scott Wlaschin さんは、書籍『関数型ドメインモデリング』の著者でもあります。
私は参加してないですが、2025年6月に 関数型まつり というイベントが東京で行われ、そこで Scott Wlaschin さんによるリモートでのトークがあったようです。
言語としてのF#
F# の特徴
F# は高度な型抽象を追求する言語というよりも、ドメインをデータとして表現し、それを関数で変換していく言語だと感じました。
F# はロジックを型で表現する力が強く、それを関数で繋ぐのが得意な言語です。一方で高度な抽象化はせず、簡潔さを好むように思います。 本格的な開発に使った経験が無いからかもしれませんが、個人的な感覚としては、型まわりは Rust の方が複雑だと感じます。
ドメインを Option や Discriminated Union を使ってレコード型 (いわゆるデータクラス) で表現し、それをパイプラインで繋いで、パターンマッチできれいに処理する。それが F# らしさなのかなと思います。
型表現
関数型言語というと型による抽象化が多いのかなと思っていたのですが、実は F# はそうでもないなと思います。Option/Result や Discriminated Union があるくらいで、ファンクターやモナドのような抽象度の高いものはあまり使いません。高度な抽象化のためではなく、ドメインをデータとして表現するために型を使う言語という感じがします。
Haskell などの言語では、例えば List や Option, Result などの型について共通部分を見出し、それをファンクターやモナドといった概念で抽象化することを行います。そういう、計算の構造や操作を抽象化するところに面白さを見出す人にとっては、 F# は物足りないかもしれません。
Rust は型が複雑化しがちですが、これは Haskell のような抽象化のためというよりも、効率性やメモリ安全性のためという部分が大きいです。ガベージコレクションがあったり、動的ディスパッチを使う方が自然だったりする言語では必要のないものも多いです。
Scala との比較
Scala は Java VM で動く関数型言語で、同じく巨大ランタイムである .NET CLR で動く F# とは立ち位置が近いように思います。
ネット上の議論を見ると、たまに「Scala と F# はどちらの方がより関数型的か?」といったもの見かけます。これは人によって意見が分かれるのですが、
-
F# の方がより関数を中心とした言語である
-
Scala の方が型による抽象化が強い
といった特徴があり、どちらを重視するかによりそうです。詳しくはないですが、 Scala はより OOP 寄りのハイブリッド言語で、型クラスを使った高度な抽象化を行う文化があると言えそうです。一方 F# はパイプラインを使った関数中心のスタイルで、抽象化よりは簡潔さを好むと言えそうです。
F# でプログラミングしていて思うこと
以下は私が F# を書いていて感じたことです。内容的には、F# に触れたことが無い人には伝わりづらいと思います。
関数のシグニチャは明示した方が良い
F# は型推論が強い言語ですが、関数の引数や戻り値は型を明示した方が良いことも多いと感じました。その方がIDE上で補間が効きやすい、IDEの動作が軽いなどのメリットがあるためです。
エラーは全て Result にする必要はない
Rust ではエラーは全て Result で統一されていますが、 F# では例外も使います。私は Rust から F# に来たので、はじめは .NET の例外は Result に変換して使うのが筋かと思っていたのですが、そうでも無いなと思いました。
Fun and Profit でも 鉄道指向プログラミングに反対する という記事でそのことに触れています。
実際、雑用であればエラーハンドリングはさほど重要ではありません。動かしてみて問題があれば直す、という程度で十分です。そういう場合は、失敗した箇所がすぐに分かる例外の方が楽だったりします。
私の中の結論として、
- 自作のロジックの中でエラーを表現したい場合に Result を使う
- 小規模なものであれば Result のエラー型は
stringかexn(.NET でいうException型) で十分
と思っています。エラー型については、大規模なものであればしっかりと定義した方が良さそうですが、小さいものであれば雑で良いと思います。Rust でいう anyhow で十分、みたいな感じです。
コンピュテーション式は FsToolkit.ErrorHandling が便利
F# にはコンピュテーション式という便利な構文があるのですが、標準では Option/Result を扱うためのものがありません。そのため自作するか外部ライブラリのものを使うことになりますが、この用途では FsToolkit.ErrorHandling が良いと思いました。
後述する FSharpPlus には、より抽象的な monad というコンピュテーション式があります。これは名前通りモナドと呼ばれる構造を抽象的に扱うためのもので、 List や Option, Result などに対して汎用的に使えるのですが、これを F# で実現するには高度な型推論が必要となるため、IDEの動作が重くなりがちです。
自分が書くコードの中であれば、ここは option, ここは result というのは分かるので、抽象的なものを使う必要はあまり無いと感じました。また、 FsToolkit.ErrorHandling は Result + Option, Result + Task といった組み合わせに対してのコンピュテーション式も用意されているため、使い勝手が良いと思います。
FSharpPlus は重ため
FSharpPlus は F# でより関数型的なプログラミングをするためのライブラリです。強力なライブラリなのですが、サイズが割と大きめです。
この記事を執筆している時点でのバージョンでは、アセンブリサイズは以下の通りでした。
- FSharp.Core.dll: 2.4 MB
- FsToolkit.ErrorHandling.dll: 1.7 MB
- FSharpPlus.dll: 11.1 MB
比較できるものではないですが、Go で書いた hello world プログラムだとランタイム込みの exe で 2.5 MB くらいなので、ライブラリ単体でこのサイズというのは比較的大きいなと感じます。
抽象化としては面白く、関数型プログラミングの雰囲気を味わうには良いライブラリだと思います。ただ、小さなツールを書く用途では FsToolkit.ErrorHandling で十分なことが多いとも感じました。
関数型言語を使うのだから抽象的な概念にも触れてみたい、という人にはおすすめです。
オペレーターについて
バインド (>>=) のようなオペレーターを使うか、という点については私も迷うところです。 Result.bind や Option.bind で十分だとも思うし、オペレーターの方が簡潔だとも思います。
例えば以下のコードだと、計算の繋ぎ部分を記号に置き換えることで、名前として現れるのがビジネスロジック (validateName, validateEMail) だけになります。
// Result.bind を使う場合
let validateRequest request =
request
|> validateName
|> Result.bind validateEmail
// >>= を使う場合
let validateRequest request =
request
|> validateName
>>= validateEmail
// こういう書き方も可
let validateRequest =
validateName >=> validateEmail
どちらが良いかは好みの問題だと思います。Rust でも and_then を書くし、Result.bind で良い気もします。
もし使うのであれば、オペレーターは FsToolkit.ErrorHandling よりも FSharpPlus の方が使いやすいです。FsToolkit.ErrorHandling だと、例えば Option用 のオペレーターと Result 用のオペレーターを同じファイル内で同時に使うことができない、といった制約があります。
FsToolkit.ErrorHandling だと、オペレーターは型ごとに用意されています。そのため以下のように書いた場合、 Result 名前空間内にある >>= は、後から open した Option 名前空間の >>= によって上書きされ、このファイル内では使えなくなります。
open FsToolkit.ErrorHandling.Operator.Result
open FsToolkit.ErrorHandling.Operator.Option
// 同名の関数や記号がある場合、最後に open したものが使われる。
// そのため、このファイルでは >>= は option型 にしか使えない。
FSharpPlus のオペレーターは抽象化されているため、より汎用的に使えます。体感ですが、monad コンピュテーション式に比べれば、オペレーターの方は IDE の動作への影響も小さい気がします。ですので、こちらは使っても良いかなと思います。
おわりに
関数型言語は難しいと思われがちですが、F# はそうでもないと思います。よく使われる言語とシンタックスが異なる部分が多いため、とっつきにくさはありますが、関数型言語の中では分かりやすい方だと思います。
なかなか使いどころは見つからないですが、もし興味があれば、触ってみるのも面白いと思います。