F# 言語仕様ガイド

他言語の経験者が 2〜3 時間でざっと読み通せるリファレンスです。型推論・判別共用体・コンピュテーション式など F# 固有の関数型機能を重点的に解説します。

F# 8.x / .NET 8経験者向けコード例付き
01

値・型・バインディング

基礎
let バインディング基礎

let で値をバインド(デフォルトは不変)。mutable キーワードを付けると変更可能。<- で再代入する。

FSharp
let x = 42              // 不変バインディング
let mutable count = 0   // ミュータブル
count <- count + 1      // 再代入(<- を使う)
let pi: float = 3.14    // 型注釈
基本型基礎

F# の基本型: intfloatboolstringcharunit())。.NET の型と互換(intSystem.Int32)。数値リテラルには型サフィックスがある。

FSharp
let i: int    = 42
let f: float  = 3.14
let b: bool   = true
let s: string = "F#"
let u: unit   = ()    // void 相当
let c: char   = 'A'
let l: int64  = 100L  // サフィックスで型指定
型推論(Hindley–Milner)基礎

F# は Hindley–Milner 型推論を採用しており、ほぼすべての型を自動推論できる。型注釈は省略可能で、ジェネリック型も自動で推論される。

FSharp
let add a b = a + b        // a, b: int と推論
let addF (a: float) b = a + b  // float と明示
let len xs = List.length xs    // 'a list -> int
unit と副作用基礎

unit は副作用のみを持つ処理の戻り値型。printfnunit を返す。F# では副作用を型で明示的に扱う。

FSharp
printfn "Hello, %s!" "F#"   // unit を返す
let greet name: unit = printfn "Hi, %s" name
ignore (1 + 1)  // 値を捨てて unit にする
02

制御フロー

基礎
if 式基礎

F# の if は式であり値を返す。両分岐の型が一致する必要がある。

FSharp
let max a b = if a > b then a else b

let label =
    if score >= 90 then "A"
    elif score >= 70 then "B"
    else "C"
match 式基礎

F# のパターンマッチ。型・値・タプル・リスト・レコード・判別共用体などあらゆる構造でマッチできる。

FSharp
let describe x =
    match x with
    | 0            -> "zero"
    | n when n < 0 -> sprintf "negative %d" n
    | n            -> sprintf "positive %d" n
for / while基礎

命令型のループ構文。関数型スタイルでは List.map/List.iter などを優先するが、for は手続き的なコードに使う。

FSharp
for i in 1..10 do
    printfn "%d" i

for i in 10 .. -1 .. 1 do   // downto
    printfn "%d" i

let mutable i = 0
while i < 5 do
    printfn "%d" i
    i <- i + 1
セミコロンと do基礎

do ブロック内では複数の式を改行(またはセミコロン)で区切って順次実行できる。

FSharp
let processFile path =
    let content = System.IO.File.ReadAllText(path)
    printfn "Read %d chars" content.Length
    content.ToUpper()
03

関数

基礎
関数定義とカリー化基礎

F# の関数は自動でカリー化される。複数引数の関数は実際には「引数を1つとって関数を返す関数」の連鎖。

FSharp
let add a b = a + b       // int -> int -> int
let add5 = add 5          // 部分適用 → int -> int
add5 3                    // 8

// 無名関数
let double = fun x -> x * 2
let triple = (*) 3        // 演算子も部分適用可
パイプ演算子 |>基礎

|> で左辺の値を右辺の関数の最後の引数に渡す。関数合成 >> で関数を連結できる。

FSharp
"hello world"
|> String.split ' '
|> List.map (fun s -> s.[0..0].ToUpper() + s.[1..])
|> String.concat " "
// "Hello World"

let processText = String.trim >> String.toLower >> String.length
再帰関数(rec)基礎

再帰関数には let rec を使う。末尾再帰は acc 引数パターンで書くとコンパイラが最適化する。

FSharp
let rec factorial n =
    if n <= 1 then 1 else n * factorial (n - 1)

// 末尾再帰版
let factorial' n =
    let rec loop acc = function
        | 0 | 1 -> acc
        | n     -> loop (acc * n) (n - 1)
    loop 1 n
高階関数・lambda基礎

fun キーワードで無名関数(ラムダ)を定義。List.mapList.filterList.fold などに渡して使う。

FSharp
[1..5] |> List.map (fun x -> x * x)   // [1;4;9;16;25]
[1..10] |> List.filter (fun x -> x % 2 = 0)  // [2;4;6;8;10]
[1..5] |> List.fold (+) 0               // 15
04

リスト・配列・シーケンス

基礎
リスト基礎

F# リストは不変の連結リスト。[1;2;3] または 1 :: [2;3]@ で連結。List モジュールで操作。

FSharp
let xs = [1; 2; 3]         // セミコロン区切り
let ys = 0 :: xs           // [0;1;2;3](先頭追加)
let zs = xs @ [4; 5]       // [1;2;3;4;5](結合)
let h, t = List.head xs, List.tail xs
配列基礎

[|1;2;3|] でミュータブルな配列を定義。インデックスアクセスは arr.[i](または arr[i])。Array モジュールで操作。

FSharp
let arr = [| 1; 2; 3 |]
arr.[0]                    // 1
arr.[1] <- 99              // 変更可能
Array.map ((*) 2) arr      // [|2;198;6|]
Seq(シーケンス)基礎

seq { ... } で遅延評価シーケンス。IEnumerable と互換。無限シーケンスも表現できる。

FSharp
let nats = Seq.initInfinite id   // 0,1,2,...
nats |> Seq.take 5 |> Seq.toList  // [0;1;2;3;4]

let evens = seq { for i in 0..2..100 -> i }
リスト内包表記基礎

[ for x in ... -> expr ] で内包表記によるリスト生成。if でフィルタリング。

FSharp
let squares = [ for i in 1..10 -> i * i ]
// [1;4;9;16;25;36;49;64;81;100]

let evens = [ for i in 1..20 do if i % 2 = 0 then yield i ]
05

文字列・フォーマット

基礎
文字列操作基礎

F# の文字列は .NET の System.Stringsprintf で型安全なフォーマット。String モジュールで操作。

FSharp
let s = "Hello, F#!"
s.Length                         // 10
s.ToUpper()                      // "HELLO, F#!"
String.concat ", " ["a";"b";"c"] // "a, b, c"
sprintf "Pi is %.4f" System.Math.PI  // "Pi is 3.1416"
文字列補間(F# 5+)基礎

F# 5 以降は $"..." で文字列補間が使える。型安全でコンパイル時チェックが働く。

FSharp
let name = "World"
let greeting = $"Hello, {name}!"     // "Hello, World!"
let info = $"Pi = {System.Math.PI:.4f}"  // "Pi = 3.1416"
printf ファミリー基礎

型安全なフォーマット関数群。コンパイル時に書式文字列の型をチェックする。%d(整数)%s(文字列)%f(浮動小数点)%A(任意型)。

FSharp
printf "%s is %d years old
" "Alice" 30
printfn "%A" [1; 2; 3]   // [1; 2; 3](デバッグ表示)
let msg = sprintf "score: %d" 95
06

例外処理

基礎
try/with と try/finally基礎

try...with で例外を捕捉、try...finally でクリーンアップ。パターンマッチで例外の種類を区別できる。

FSharp
try
    let n = int "abc"
    n
with
| :? System.FormatException as e -> printfn "Format error: %s" e.Message; 0
| ex -> printfn "Unknown: %s" ex.Message; -1
Result<T, E>(F# 4.1+)基礎

Result<'T,'TError> で成功(Ok)か失敗(Error)を表す。Option<'T>Some/None。パターンマッチで処理する関数型スタイル。

FSharp
let divide a b =
    if b = 0 then Error "Division by zero"
    else Ok (a / b)

match divide 10 2 with
| Ok result -> printfn "Result: %d" result
| Error msg -> printfn "Error: %s" msg
failwith / invalidArg基礎

failwithSystem.Exception を発生させる便利関数。invalidArg は引数エラー用。F# では Result を使うのが推奨スタイル。

FSharp
let head = function
    | []    -> failwith "Empty list"
    | x :: _ -> x

let checkAge age =
    if age < 0 then invalidArg "age" "Must be non-negative"
    age
07

レコードと判別共用体

基礎
レコード型基礎

レコードは名前付きフィールドを持つ不変データ構造。with キーワードでコピー生成(構造的等値性も自動)。

FSharp
type Person = {
    Name: string
    Age:  int
}

let alice = { Name = "Alice"; Age = 30 }
let older = { alice with Age = 31 }    // コピー
alice = { Name = "Alice"; Age = 30 }   // true(構造的等値)
判別共用体(DU)基礎

代数的データ型(ADT)。type Shape = Circle of float | Rectangle of float * float のように定義し、パターンマッチで処理する。型安全な条件分岐。

FSharp
type Shape =
    | Circle    of radius: float
    | Rectangle of width: float * height: float
    | Triangle  of base: float * height: float

let area = function
    | Circle r        -> System.Math.PI * r * r
    | Rectangle(w, h) -> w * h
    | Triangle(b, h)  -> b * h / 2.0
Option 型基礎

Option<'T>Some value または None。null の代替。Option.mapOption.bindOption.defaultValue で関数型スタイルの操作。

FSharp
let safeDiv a b =
    if b = 0 then None else Some (a / b)

safeDiv 10 2  |> Option.map ((*) 3)   // Some 15
safeDiv 10 0  |> Option.defaultValue -1 // -1
シングルケース DU(newtype)応用

ケースが1つの判別共用体で型安全なラッパーを定義。UserIdOrderIdint で区別できる。

FSharp
type UserId  = UserId  of int
type OrderId = OrderId of int

let uid = UserId 42
let (UserId rawId) = uid   // アンラップ

// let wrong: OrderId = uid  // コンパイルエラー
08

クラスとインターフェース

基礎
クラス定義基礎

.NET との互換のためクラスも定義できる。type ClassName(args) = が基本構文。member this.Method でインスタンスメソッドを定義。

FSharp
type Counter(initial: int) =
    let mutable count = initial

    member _.Value = count
    member _.Increment() = count <- count + 1
    member _.Reset()    = count <- 0

let c = Counter(10)
c.Increment()
printfn "%d" c.Value   // 11
インターフェース基礎

.NET インターフェースを実装できる。interface IFoo with で明示的実装。

FSharp
type IShape =
    abstract Area: float

type Circle(r) =
    interface IShape with
        member _.Area = System.Math.PI * r * r

let s: IShape = Circle(5.0)
printfn "%.2f" s.Area
構造体型応用

[] アトリビュートでスタック割り当ての値型を定義。ヒープ割り当てを避けてパフォーマンスを改善できる。

FSharp
[<Struct>]
type Vector2D = { X: float; Y: float }

let v1 = { X = 1.0; Y = 2.0 }
let v2 = { X = 3.0; Y = 4.0 }
let sum = { X = v1.X + v2.X; Y = v1.Y + v2.Y }
09

ジェネリクス・型制約

応用
ジェネリック関数・型基礎

型変数は 'a'T で表記。F# は自動汎化により関数が自動的にジェネリックになる場合がある。

FSharp
let identity (x: 'a): 'a = x
let swap (a, b) = (b, a)   // 'a * 'b -> 'b * 'a

type Tree<'a> =
    | Leaf
    | Node of 'a * Tree<'a> * Tree<'a>
型制約応用

when 'T : comparison などで型制約を付けられる。比較・等値・演算子の存在を要求できる。

FSharp
let maximum (xs: 'a list when 'a : comparison) =
    List.reduce max xs

let inline addAny (a: ^T) (b: ^T) : ^T =
    a + b   // + 演算子を持つ型ならすべて OK
SRTP(静的解決型パラメータ)応用

^Tinline を使うことで、コンパイル時に型を解決し演算子を要求できる(C++ テンプレートに近い)。ゼロコスト抽象。

FSharp
let inline sumList (lst: ^T list) : ^T =
    List.fold (+) LanguagePrimitives.GenericZero lst

sumList [1; 2; 3]       // 6(int)
sumList [1.0; 2.0; 3.0] // 6.0(float)
10

コンピュテーション式

応用
コンピュテーション式とは応用

コンピュテーション式はモナドを読みやすい構文で書くための仕組み。async { ... }seq { ... }task { ... } などが代表例。

FSharp
// seq CE
let evens = seq {
    for i in 0..100 do
        if i % 2 = 0 then yield i
}

// option CE(カスタム例)
let result = option {
    let! x = Some 10
    let! y = Some 20
    return x + y
}
async ワークフロー基礎

F# の async { } は非同期ワークフロー。let! で非同期値をアンラップ、do! で副作用を実行。Async.RunSynchronously または |> Async.Start で実行。

FSharp
let fetchAsync url = async {
    use client = new System.Net.Http.HttpClient()
    let! response = client.GetStringAsync(url) |> Async.AwaitTask
    return response.Length
}

fetchAsync "https://example.com"
|> Async.RunSynchronously
|> printfn "Length: %d"
task ワークフロー(F# 6+)基礎

F# 6 以降は task { } で .NET の Task を直接扱える。C# の async/await と完全互換。

FSharp
open System.Threading.Tasks

let fetchTask url : Task<int> = task {
    use client = new System.Net.Http.HttpClient()
    let! content = client.GetStringAsync(url)
    return content.Length
}

fetchTask "https://example.com" |> _.Result |> printfn "%d"
11

型プロバイダー

応用
型プロバイダーとは応用

型プロバイダーは外部データソース(JSON・CSV・SQL・XML)から型をコンパイル時に自動生成する F# 固有の機能。型安全なデータアクセスを実現。

FSharp
// FSharp.Data の JSON 型プロバイダー
#r "nuget: FSharp.Data"
open FSharp.Data

type Weather = JsonProvider<"https://api.example.com/weather">
let data = Weather.Load("https://api.example.com/weather")
printfn "Temp: %.1f" data.Temperature  // 型安全にアクセス
CSV 型プロバイダー応用

CSV ファイルのスキーマを自動推論してコンパイル時に型を生成。列名がプロパティとして型安全に使える。

FSharp
type Stocks = CsvProvider<"stocks.csv">
let stocks = Stocks.Load("stocks.csv")
for row in stocks.Rows do
    printfn "%s: %.2f" row.Symbol row.Price
// Symbol, Price は CSV ヘッダーから自動生成された型
SQL 型プロバイダー応用

データベースのスキーマからコンパイル時に型を生成。クエリの型安全性がコンパイル時に保証される。

FSharp
// SQLProvider 例(概念)
type Sql = SqlDataProvider<"Data Source=mydb.sqlite">
let ctx = Sql.GetDataContext()
query {
    for user in ctx.Users do
    where (user.Age > 18)
    select user.Name
}
12

.NET 連携

基礎
.NET ライブラリの利用基礎

F# は .NET の全ライブラリを利用できる。open System で名前空間を開き、System.IO.File などを直接使用可能。

FSharp
open System
open System.IO

let files = Directory.GetFiles(".", "*.fs")
let content = File.ReadAllText(files[0])
printfn "%s" (DateTime.Now.ToString("yyyy-MM-dd"))
LINQ クエリ式基礎

F# の query { } コンピュテーション式で SQL ライクな LINQ クエリを書ける。IEnumerableIQueryable の両方に対応。

FSharp
let numbers = [1..20]
let result =
    query {
        for n in numbers do
        where (n % 2 = 0)
        select (n * n)
        take 5
    }
// [4; 16; 36; 64; 100]
NuGet と paket基礎

.fsproj ファイルで NuGet パッケージを管理。dotnet add package でパッケージを追加。paket は代替の依存管理ツール。

FSharp
# CLI でパッケージ追加
dotnet add package Newtonsoft.Json
dotnet add package FSharp.Data

# .fsproj に追記される
# <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
13

テスト

基礎
xUnit / NUnit基礎

.NET 標準のテストフレームワーク。[][] アトリビュートでテストを定義。dotnet test で実行。

FSharp
open Xunit

[<Fact>]
let ``add returns correct sum`` () =
    let result = 1 + 1
    Assert.Equal(2, result)

[<Theory>]
[<InlineData(1, 2, 3)>]
[<InlineData(0, 0, 0)>]
let ``add is commutative`` a b expected =
    Assert.Equal(expected, a + b)
Expecto基礎

F# 向けの軽量テストフレームワーク。関数型スタイルのテスト定義。パフォーマンステストや BDD スタイルもサポート。

FSharp
open Expecto

let tests = testList "Math" [
    test "addition" {
        Expect.equal (1 + 1) 2 "should be 2"
    }
    test "float" {
        Expect.floatClose Accuracy.high 3.14 3.14 "should be equal"
    }
]

[<EntryPoint>]
let main args = runTestsWithCLIArgs [] args tests
FsCheck(プロパティテスト)応用

FsCheck は Haskell の QuickCheck の F# 版。ランダムな入力を自動生成して性質(プロパティ)を検証する。

FSharp
open FsCheck

let revRevIsOriginal (xs: int list) =
    List.rev (List.rev xs) = xs

Check.Quick revRevIsOriginal
// Ok, passed 100 tests.
14

ビルドツール・エコシステム

基礎
dotnet CLI基礎

dotnet CLI で F# プロジェクトを管理。dotnet new console -lang F# で新規プロジェクト。

FSharp
dotnet new console -lang F# -o MyApp
dotnet build
dotnet run
dotnet test
dotnet publish -c Release
.fsproj 構成基礎

F# は.fsproj でソースファイルの順序が重要(上から順にコンパイル)。前方参照ができないため、依存される型は先に宣言する。

FSharp
<!-- .fsproj の例 -->
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <Compile Include="Types.fs" />
    <Compile Include="Logic.fs" />
    <Compile Include="Program.fs" />
  </ItemGroup>
</Project>
Ionide / REPL基礎

VS Code の Ionide 拡張が F# の主要 IDE。dotnet fsi で F# Interactive(REPL)を起動できる。スクリプトファイル(.fsx)もサポート。

FSharp
# FSI 起動
dotnet fsi

# スクリプト実行(.fsx ファイル)
dotnet fsi my_script.fsx

# nuget パッケージを FSI で使う
#r "nuget: FSharp.Data"
open FSharp.Data
15

関数型パターン・設計

応用
Railway Oriented Programming応用

Result 型を bind>>=)でチェーンするエラーハンドリングパターン。成功パスと失敗パスを「レール」に例えた設計手法。

FSharp
let (>>=) result f =
    match result with
    | Ok v    -> f v
    | Error e -> Error e

let validateAge age =
    if age >= 0 then Ok age else Error "Negative age"

let validateName name =
    if name <> "" then Ok name else Error "Empty name"

let result =
    Ok { Name = ""; Age = -1 }
    >>= (fun p -> validateName p.Name |> Result.map (fun n -> { p with Name = n }))
    >>= (fun p -> validateAge p.Age  |> Result.map (fun a -> { p with Age = a }))
F# と DDD応用

判別共用体とレコードで Value Object・Entity・集約を表現できる。型システムで不正な状態を表現不可能にする「make illegal states unrepresentable」パターン。

FSharp
// 不正状態を型で排除
type Email = private Email of string
module Email =
    let create s =
        if s.Contains("@") then Ok (Email s)
        else Error "Invalid email"
    let value (Email s) = s

// OrderStatus が型で保証される
type Order =
    | Pending  of items: string list
    | Shipped  of trackingNo: string
    | Delivered
アクティブパターン応用

(| Name |) でカスタムパターンを定義。既存の型に対して読みやすい分解ルールを追加できる。

FSharp
let (|Even|Odd|) n = if n % 2 = 0 then Even else Odd
let (|Positive|Negative|Zero|) n =
    if n > 0 then Positive
    elif n < 0 then Negative
    else Zero

match 42 with
| Even & Positive -> "even positive"
| Odd             -> "odd"
| _               -> "other"
16

パフォーマンス・Native AOT

応用
Span<T> と Memory<T>応用

.NET の Span はスタック割り当てのスライスビュー。ReadOnlySpan で文字列のゼロコピー処理が可能。

FSharp
open System

let countChars (s: ReadOnlySpan<char>) (target: char) =
    let mutable count = 0
    for c in s do
        if c = target then count <- count + 1
    count

let text: ReadOnlySpan<char> = "hello world".AsSpan()
countChars text 'l'   // 3
Native AOT 発行応用

.NET 8 の Native AOT で F# コードをネイティブバイナリに事前コンパイル。起動時間とメモリ使用量を大幅削減できる(リフレクション制限あり)。

FSharp
<!-- .fsproj -->
<PublishAot>true</PublishAot>

# ビルド
dotnet publish -r linux-x64 -c Release
# 自己完結シングルバイナリが生成される
プロファイリング・ベンチマーク応用

BenchmarkDotNet は .NET の標準ベンチマークライブラリ。[] アトリビューターで計測対象を指定。JIT ウォームアップを考慮した正確な計測ができる。

FSharp
open BenchmarkDotNet.Attributes
open BenchmarkDotNet.Running

[<MemoryDiagnoser>]
type MyBench() =
    [<Benchmark>]
    member _.ListMap() = [1..1000] |> List.map ((*) 2)
    [<Benchmark>]
    member _.ArrayMap() = [|1..1000|] |> Array.map ((*) 2)

BenchmarkRunner.Run<MyBench>() |> ignore
🏠