cv.cm

コーディング · プログラミング言語

F#入門:初心者向けチュートリアル

約9分で読めます

結論

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 run

2つ目のコマンド群は、.fsprojファイルとProgram.fsを持つ本格的なコンソールプロジェクトを作ります。プロジェクトでは、.fsprojに列挙するファイルの順序が重要です。ファイルは、それより上にあるファイルのコードしか使えません。

値と基本の型

letは名前を値に束縛します。束縛は、mutableと指定しない限りイミュータブルです。変更可能にした場合は、=ではなく<-で変更します。

型例補足
int4232ビット整数
float3.1464ビット浮動小数点数(.NETのdouble)
decimal19.99m正確な10進数。金額に向いています
string"hello"変更できない.NETの文字列
char'a'1文字
booltrue, 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 needed

F#は数値を暗黙に変換しません。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 |]固定サイズで、要素は変更可能。添字アクセスが高速です
Sequenceseq { 1 .. 10 }遅延評価で、必要になったときに計算されます
MapMap ["a", 1; "b", 2]イミュータブルな辞書
Setset [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)   // None

TryParseは.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 2

Array.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向けに作られていて、デフォルトでインデントに意味があり、計算式などの独自の機能を持ちます。

コーディング講座の一覧