仕事で F# 使ってます。最近はほとんど F# ばかり書いていて、時々 C# と XAML を書く感じです(といいつつ先日は超久しぶりにC++を書いていましたが)。
自分がどんな風にF#を使っているか、Q&A方式でまとめてみようと思います。もし誰かの参考になれば嬉しい。
関数型のすごい人なの?
いいえ、ぜんぜん。
仕事でF#を使っていると書くと関数型プログラミングのエキスパートと思われるかもしれませんが、計算機科学方面(?)はサッパリでお世辞にも F# 力が高いとは言えません。Haskell知りませんし、λ計算も分かりませんし、圏論も分かりませんし、モナドも利用はしているけど分かったような分からないような。今のところ関数型プログラミングそのものを突き詰めて勉強しようという強い意志や計画は持ち合わせていません(意識低い><)。
(言い訳すると、興味はあるんですが今のところなかなか優先順位の上位に上がってこなくて…。他にも勉強したいこととかやりたいこととか、あとやらなくちゃいけないこととか、ボソボソ)
どうしてこの記事を書いたの?
一言で書くと「関数型は(F#は)頭が良くて計算機科学に強い人じゃないと使えないというイメージを払拭したい」です。そういうイメージがどれくらいあるのか分かりませんが、自分は始める前はそういうイメージを持っていたので。
次の2つの命題は同時に成立しうるものだと思うのです。
- 計算機科学に詳しくない人でも F# のメリットは受けられる
- 計算機科学に詳しい人は F# のメリットをより多く引き出せる
これを伝えるために、私みたいな特に計算機科学方面に強くない人間が実際に使っている例を(勇気を出して)晒してみようと思いました。「私ごときが書いていいのだろうか」なんて思ったりもする一方で「私ごときだからこそ書けることもあるのかも」とも思った次第です。
F#でどんなプログラム作ってるの?
- WPFによるデスクトップ・アプリケーションです。
- 製造業のお客様に 3D CAD 系の形状処理アプリケーションを開発するお仕事がメインです。
- 受託開発がメインです。
- 自分でゴリゴリとアルゴリズムを作りこむ部分が結構あります。
- ただしUI成分も結構あります。CADなので、マウスでいろいろ操作するので。
- 内製のOpenGLを使ったフレームワークをベースにして開発しています。フレームワーク部分はC#で作られている部分が多く、その一部をF#でラップして使っています。
- 関数型というと金融関係の例が多いみたいですが、金融とは一切関係ありません。
- F# + XAML でGUIプログラミングしています。普通にできます。
一番最初はどうやって導入した?
今の会社を立ち上げて約3年が経ちました。私は経営者ではありませんが、立ち上げ時からのメンバーです(立場は従業員)。
会社を立ち上げた当時、「新しい言語を始めるなら今このタイミングしかない!」と思ってF#を始めました。が、ちょっとこれは一般に参考に出来る情報ではないので(苦笑)、もうちょっと経緯を書いてみます。
最初は社内ライブラリから
- 自分は社内で使うライブラリやフレームワークを担当する立場。
- ほとんど一方的に「ライブラリF#で書くことにしたわ。C#からも呼べるから別に困らんでしょ?」と宣言。
- メンバーも割と柔軟で「ふーん?よく分からんけどC#から呼べるのなら別にいいよ」ていう感じ。
次に受託案件に導入
- 開発メンバーは自分も合わせて二人のプロジェクト。
- 自分が低レイヤー側担当、相方がUI側担当。
- 相方に「俺はF#で書くわ。そっちはC#でいいからさ。」と一方的に宣言。
- MVVMで言うと、ModelがF#でViewとViewModelがC#(とXAML)。
- プロジェクトが進むにつれて、結局相方も徐々にF#のコードを読み書きせざるを得ない状況に(狙い通り)。
- こうして相方もいつの間にかF#に入信(狙い通り)。
- 彼も今となっては「いっそのことViewModel側も全部F#に置き換えたいくらい」とボヤくまでに成長しました(狙い通り)。
- 開発人数は少人数ですが、もう一年半以上開発を続けているプロジェクトなのでそこそこの規模。
F#の実務利用を検討したい人へ
末尾再帰だとかカリー化だとかモナドだとか、そういうのは案外「後で勉強すればいいこと」な気がします。
それよりも、実務利用に当たってまず気になるのはC#との相互運用性ではないでしょうか。
導入プロジェクトでていきなりすべてをF#で書くという選択は難しいことが多いと思いますから、たぶん最初はC#と併用することになるでしょう。その場合、F#のコードがC#から呼び出すときにはどう見えるのか、予め確認しておいたほうがいいのではないかと思います。
学習コストが高いんじゃないの?
次の2つに分解して書いてみます。
- (a) F# 言語の文法の学習コスト
- (b) 関数型らしい(F#らしい)書き方や設計の学習コスト
(a) 文法の学習コストについて
- 思っているほど大したことないですよ!
- コードの見た目の印象が「キモい」と感じますか?
- C系の言語と見た目の印象が結構違うので、心理的抵抗を感じやすいかも。
- 心理的抵抗を感じているせいで、学習コストを過大評価しているかも。
- 見た目の問題は書いてりゃ慣れます。それだけの問題です。
- 慣れれば心理的抵抗が減って、(文法は)案外すんなりと学習が進みます。
- 最初は = で代入しようとしたり(正しくは <-)、== で比較しようとしたり(正しくは =)してイライラすると思いますが(笑)、単なる慣れの問題と言い聞かせて心を落ち着かせましょう。
(b) F#らしい書き方の学習コストについて
- これは、まあ、コストかかる。最初は(今も)あーでもないこーでもないと悩んだりします。
- 単なる文法の学習にとどまらない新しいスタイルの習得ですから、コストが掛からないはずがない。あきらめましょう。
- この学習コストは、代わりに得られるコードのメンテナンス性の向上によって回収できる、と信じましょう。
- 「実務での利用は関数型のスタイルを習得してから」などと考えているといつまでたっても導入できません。文法を覚えてC#等と同等のコードが書けるようになったら準備は整ったと思いましょう。
- F#にはF#のアフォーダンスがある、と感じます。F#を書き続けていれば、F#が自然とF#らしいコードに導いてくれます。いつの間にか、徐々に自分のコーディングスタイルが変わっていきます。そういう意味では、例えばC#で関数型のスタイルを学ぶよりもF#で学ぶほうが学習コストは低い、かもしれません。
- C#でLINQを自在に操っている人なら学習はスムーズだと思います。
※ なお、私が「関数型らしい書き方」が出来ているかどうかは不明。
教育コストが高いんじゃないの?
学習コストと似ていますが、例えば新しいメンバーを会社に迎え入れた時の教育コストが気になる人もいるかもしれません。
- 教育コストは、代わりに得られるコードのメンテナンス性によって回収できる、と信じています。
- コードレビューはとても楽です。F# に慣れていない人のコードは、'mutable' で grep するだけで怪しいコードが簡単に見つかります。冗談みたいな本当の話です。
- F#は邪悪なコードが書きにくい気がします。中途入社の方の一人は「F#を書いていると強制的にきれいなコードを書かされる。養成ギプスをはめているようだ。」と言っていました。この効果を考慮するとコードレビューのコストが減っていることになりますね。
- ちなみに弊社はエンジニアが5人で、(程度の差はありますが)全員F#を書いています。
- うち3名は中途採用で、もちろん最初は全員F#は知りませんでした。
- 中途採用のうち2名は40歳以上の方で、F#はおろかC#も知りませんでした。
- それでもやれば何とかなります。
パフォーマンスは?
- きちんと計測したわけじゃないので、あくまで主観的な感想レベルの話しか書けません。話半分に読んでください(いや、半分も信じないでください)。
- パフォーマンスはC#と比べてるとちょっと落ちるかも…?という気がしています。
- 以前C#で実装したものと同じようなアルゴリズムをF#で実装した時に、前よりもちょっと遅くなったな、と思ったことがあったので。
- ただし、一字一句C#のコードを直訳したわけではないので、公正な比較にはなっていません。
- F#だとラムダ式、カリー化、レコード型、etc をホイホイと気軽に使ってしまうので、その辺りの影響があるのかも?
- 私の業務の特性および私の価値観においては、パフォーマンスよりもロジックの書きやすさや読みやすさを重視したいと考えているので、パフォーマンスについてはあまり問題視していません。(もちろん速いほうが嬉しいですが)
- いずれにせよ計測もせずにパフォーマンスを語るのは良くないので、この辺にしておきます。
F# の何が気に入っているの?
もちろん気に入っているところは沢山あるのですが、ここでは敢えてあまり語られることがない2つの特長に絞って紹介します。
並び順を強制されるところ
- F#は前方参照が基本です。
- 前方で定義済みの関数しか呼び出せません。
- 前方で定義済みの型しか使えません。
- ソースファイルも依存順に並べる必要があります。
- C#の場合はソースファイルは勝手にABC順にソートされますね。
- F#では依存順になるように自分で並び順を制御する必要があります。
- つまり全てがトポロジカルソートされます。
- ソースファイル、type、module、関数、すべてが依存順にトポロジカルソートされた順に並びます。
- これにより、ソリューション全体のマクロな構造を把握するのが楽になります。
- これは好き嫌いが別れるようです。
- 私はこの制約がとても気に入っています。
- しかし前方しか参照できないのは時として窮屈です。そのためこの制約を嫌う人もいるようです。
- チーム開発で威力を発揮します。
- 並び順の制約は、他人のコードを読むときには便利です。
- ありがちな不適切な設計のひとつに、高レイヤーに配置するべき機能を低レイヤーのモジュールや型に実装してしまう、というのがあります。F#の並び順の制約は、こういった不適切な位置への機能追加を(ある程度は)強制的に排除できます。
let の中に let を入れ子に出来るところ
例えばこう書けます。
let path =
let dir = @"c:\hoge\piyo\"
let name = "fizzbuzz.fs"
Path.Combine (dir, name)
要するに、以降で参照することがない dir や name といった変数を小さいスコープに閉じ込めることが出来るというわけです。
単純で小さなことですが、これが私はとても気に入っています。この特長があるために、長い関数をいちいち小さな関数に切り分けなくても処理の構造を表現することが出来ます。とてもお手軽に使える割に、可読性やメンテナンス性の向上の効果が大きいと思います。
F#の学び方のコツってある?
- F#の文法に戸惑うことがあっても、出来るだけ「好意的に」「前向きに」解釈すること、です。
- 例えば上で紹介した「並び順を強制される」仕様は最初は窮屈に感じるでしょう。しかしこの窮屈さを短絡的にデメリットと捉えないで、その窮屈さが生み出すメリットを想像してみてください。
- 別の例としては、C系の言語に慣れていると関数呼び出しは f(x, y) という形式が見慣れていますから、F# の f x y という形式になかなか慣れない(関数呼び出しに見えなくて気持ち悪く感じる)かと思います。しかしこれは決して奇をてらった文法というわけではなく、カリー化や部分適用との整合性のために必要な合理的な設計です。
- for や while のループで break や continue が使えないのも最初は戸惑うでしょう。これは for/while の代わりに Seq モジュール等をきちんと使いこなせば解決しますし、可読性・メンテナンス性も向上します。
- 最初は戸惑っても、背景には合理的な理由があることが多いので拒絶反応を起こさずに受け入れることが学習のコツかと思います。
F#が向いてないプロジェクトってある?
- 分かりません><
- もしかすると、既存のライブラリを組み合わせる部分が多くて自前でゴリゴリ作りこむ部分が少ないプロジェクトには向かないかも?
- 既存のライブラリはC#であることが多いと思うので、C#製のライブラリを叩くだけのアプリケーションだとF#のメリットを活かす場面があまりなさそうなので。
immutable な変数しか使わないの?
- いいえ、(少なくとも私は)mutable な変数も使っています。
- WPF(GUIライブラリ)がmutableに(ステートフルに)設計されている時点で、完全にすべてを参照透明に作ることは不可能なんじゃないかと思います、たぶん。
- ただしWPFにはBindingという素晴らしい機構が備わっていますから、これを活用して出来るだけ Reactive な設計を(一応)目指しています。
データ構造やアーキテクチャ的な構造について
- できるだけ immutable あるいは reactive な構造にします。
- ここは簡単に妥協しないで割と頑張るほうが良い気がします。
- そうはいっても、やっぱり妥協しちゃうことはあります…。まだまだ修行が足りません。
関数の実装について
- ローカル変数として mutable を使うのは、別にいいんじゃないでしょうか。
- その関数が外部から見て参照透明な仕様になっていることは大事ですが、内部の実装に mutable を使っているかどうかは大勢に影響しません。
- for 文でループを回すほうが自然なロジックは for 文を使えばいいと思います。
immutable な発想について
- 初めて関数型の考え方に触れた時に思ったこと。
- んなもん机上の空論だろ。
- アプリケーションの状態遷移を表現できなきゃ意味無いじゃん。全部 immutable になんて出来るわけ無いでしょ。
- 関数型推しの人ってコンソールプログラムしか作ってないんじゃねーの。
- (今思うとなんて酷いdisだ…)
- 今の私の考え方。
- もちろん状態遷移は必要なので、完全に immutable は無理。
- でも限界ギリギリまで immutable を追求した設計をイメージすることは大事。
- GUIアプリケーションは、「ユーザーの操作」と、それに応じた「状態遷移」の連続で成り立っている。
- 「現在の状態」と「ユーザーの操作」の2つを引数として入力すると「新しい状態」を返す状態遷移関数をイメージする。
- 理想的には、「状態」を表すデータ構造は immutable な「値」として定義できるはず。
- 理想的には、状態遷移関数は参照透明な関数として書けるはず。
- 理想的には、モデルの状態遷移に応じて reactive に GUI の状態も更新されるようにViewModelやBindingが作れるはず。
- この理想的な(究極的な)設計をイメージした上で、落とし所を探る。
- 「落とし所」とか書いてる時点で察しがつくように、まだ追求しきれてない。
- これが正解と言いたいわけじゃなくて、現時点での私の到達点はこんな感じ、という紹介です。
F# に不満ってある?
- もうちょっとコンパイルが速いと嬉しい。
- もうちょっと Visual Studio のサポートが厚いと嬉しい。
- ウォッチウィンドウでC#の文法を書かないといけない
-
'
付きの変数がウォッチできない - などなど
- (考えていたらただの愚痴っぽくなってきたのでこの辺でやめておきます)
最後に
- もうちょっと F# ユーザー増えないかな。
- 特に、実務で使うユーザーが増えて欲しいな。