コーディング · プログラミング言語
F#入門:初心者向けチュートリアル
結論
F#は、.NET向けの関数型を第一とする静的型付け言語です。ほとんどの型を推論してくれ、波かっこの代わりにインデントを使い、イミュータブルなデータ、パターンマッチ、パイプライン演算子|>を普通の書き方としつつ、.NETライブラリにもすべてアクセスできます。.NET SDKをインストールし、dotnet fsi hello.fsxでスクリプトを実行します。
F#とは何か、誰が使うのか
F#はMicrosoft ResearchのDon Symeが設計した言語で、現在はMicrosoftとF# Software Foundationのコミュニティが保守するオープンソースの言語です。(OCamlなどと同じ)ML系の言語に属し、Windows、macOS、Linuxの.NET上で動きます。
F#は、金融モデリング、データ分析、ドメインの比重が大きい業務ロジック、Webバックエンド、スクリプトに使われています。C#と同じ.NETの中間言語にコンパイルされるため、F#のプロジェクトはどのNuGetパッケージも使え、C#のコードから呼び出すこともできます。
F#のインストールとhello, worldの実行
Microsoftから.NET SDKをインストールします。F#は含まれているので、別にインストールする必要はありません。ちょっと試すだけなら、拡張子.fsxのスクリプトファイルを書き、F# Interactive(dotnet fsi)で実行します。
// hello.fsx
printfn "Hello, world!"dotnet fsi hello.fsx
# Hello, world!
dotnet new console -lang "F#" -o HelloApp
cd HelloApp
dotnet run2つ目のコマンド群は、.fsprojファイルとProgram.fsを持つ本格的なコンソールプロジェクトを作ります。プロジェクトでは、.fsprojに列挙するファイルの順序が重要です。ファイルは、それより上にあるファイルのコードしか使えません。
値と基本の型
letは名前を値に束縛します。束縛は、mutableと指定しない限りイミュータブルです。変更可能にした場合は、=ではなく<-で変更します。
| 型 | 例 | 補足 |
|---|---|---|
| int | 42 | 32ビット整数 |
| float | 3.14 | 64ビット浮動小数点数(.NETのdouble) |
| decimal | 19.99m | 正確な10進数。金額に向いています |
| string | "hello" | 変更できない.NETの文字列 |
| char | 'a' | 1文字 |
| bool | true, false | 論理値 |
| unit | () | 「値なし」。voidのようなもの |
let name = "Ana"
let age = 30
let mutable count = 0
count <- count + 1
printfn "%s is %d, count = %d" name age count
// Ana is 30, count = 1
let total = float age + 0.5 // explicit conversion neededF#は数値を暗黙に変換しません。1 + 2.0はコンパイルエラーです。float、int、stringなどの関数で変換してください。
制御構文と関数
ifは値を返す式なので、2つの分岐は同じ型でなければなりません。ループ(for、while)もありますが、ほとんどのコードはコレクションの関数を使います。
let sign n =
if n > 0 then "positive"
elif n < 0 then "negative"
else "zero"
for i in 1 .. 3 do
printfn "%d squared is %d" i (i * i)
let add x y = x + y // int -> int -> int
let add5 = add 5 // partial application
printfn "%d" (add5 10) // 15
let square = fun x -> x * x
[1; 2; 3; 4]
|> List.filter (fun x -> x % 2 = 0)
|> List.map square
|> printfn "%A" // [4; 16]引数はカンマではなく空白で区切ります。どの関数も一度に1つの引数を受け取り(カリー化)、add 5は新しい関数を返します。パイプライン演算子x |> fはf xを意味し、データの変換を上から下へ書けます。インデントには意味があります。関数やループの本体は、その下にインデントして書かなければなりません。
コレクション・レコード・オプション
| コレクション | リテラル | 補足 |
|---|---|---|
| List | [1; 2; 3] | イミュータブルな連結リスト。デフォルトの選択肢です |
| Array | [| 1; 2; 3 |] | 固定サイズで、要素は変更可能。添字アクセスが高速です |
| Sequence | seq { 1 .. 10 } | 遅延評価で、必要になったときに計算されます |
| Map | Map ["a", 1; "b", 2] | イミュータブルな辞書 |
| Set | set [1; 2; 2] | 重複のない値のイミュータブルな集合 |
type Person = { Name: string; Age: int }
let ana = { Name = "Ana"; Age = 30 }
let older = { ana with Age = 31 } // copy with one change
let people = [ ana; { Name = "Ben"; Age = 17 } ]
let adult = people |> List.tryFind (fun p -> p.Age >= 18)
match adult with
| Some p -> printfn "First adult: %s" p.Name
| None -> printfn "No adults"
// First adult: Anaレコードは、名前付きフィールドと構造的等価性を持つイミュータブルな型です。F#は、自前の型にnullを持ちません。値がない場合はOptionで表し、Some valueかNoneです。List.tryFindなどの関数はオプションを返し、コンパイラが両方のケースの処理を強制します。
特徴的な機能:判別共用体とパターンマッチ
判別共用体は、名前付きの複数のケースのうちちょうど1つであり、各ケースが独自のデータを持つ値を表します。matchでケースを調べ、書き忘れがあるとコンパイラが警告します。
type Shape =
| Circle of radius: float
| Rectangle of width: float * height: float
let area shape =
match shape with
| Circle r -> System.Math.PI * r * r
| Rectangle (w, h) -> w * h
[ Circle 1.0; Rectangle (2.0, 3.0) ]
|> List.map area
|> List.iter (printfn "%.2f")
// 3.14
// 6.00ほかによく知られた機能に、型推論(型注釈をほとんど書きません)、単位の扱い(float<m>はfloat<s>に足せません)、非同期コード向けのtask { ... }やasync { ... }といった計算式があります。
エラー処理
想定内の失敗には、F#らしくResult(Ok valueまたはError info)を返します。想定外の失敗には、try ... with、failwith、raiseで.NETの例外が使えます。
let parseAge (s: string) =
match System.Int32.TryParse(s) with
| true, n when n >= 0 -> Ok n
| true, _ -> Error "Age must not be negative"
| false, _ -> Error "Not a number"
match parseAge "abc" with
| Ok n -> printfn "Age %d" n
| Error msg -> printfn "Error: %s" msg
// Error: Not a number
let safeDivide a b =
try Some (a / b)
with :? System.DivideByZeroException -> None
printfn "%A" (safeDivide 10 0) // NoneTryParseは.NETではoutパラメーターを持ちますが、F#ではタプル(success, value)に変換されるので、直接マッチできます。
小さな完成プログラム:単語の出現頻度
let text = "the cat and the hat and the bat"
text.Split(' ')
|> Array.countBy id
|> Array.sortByDescending snd
|> Array.truncate 2
|> Array.iter (fun (word, n) -> printfn "%s %d" word n)
// the 3
// and 2Array.countBy idは同じ単語をまとめて(word, count)のペアを返します。sndは並べ替えのために各ペアの2番目の要素を取り出し、結果はタプルを分解するラムダで出力されます。
次に学ぶこと
- Microsoft Learn:「Tour of F#」と言語リファレンスを含むF#のドキュメント。
dotnet fsiで練習する。コードを貼り付けるとすぐに結果が見えます。入力の終わりは;;です。List、Array、Seq、Option、Resultの各モジュールを覚える。日常のコードの大半はこれらで作られます。- 基本に慣れたら、NuGetにあるF#のWebフレームワークやライブラリを見てみる。
よくある質問
F#はWindows専用ですか?
いいえ。F#はクロスプラットフォームの.NET上で動くので、Windows、macOS、Linuxでプログラムをビルドして実行できます。
F#とC#の違いは何ですか?
どちらも.NET上で動き、同じライブラリを使えます。C#はオブジェクト指向を第一に関数型の機能を加えた言語で、F#は関数型を第一に、イミュータブルなデータ、型推論、パターンマッチをデフォルトにした言語です。
F#の|>は何を意味しますか?
x |> fはパイプライン演算子で、f xを意味します。左の値を右の関数の最後の引数として渡すので、変換の連なりを上から下へ読めます。
F#はOCamlと関係がありますか?
はい。F#はML系に属し、中核の構文はOCamlに近いのですが、.NET向けに作られていて、デフォルトでインデントに意味があり、計算式などの独自の機能を持ちます。