F Sharp
F#は、.NETフレームワーク上で動作するマルチパラダイム言語です。この言語は、静的型付け、関数型、オブジェクト指向、および並列プログラミングをサポートしています。
F#は、Microsoft Researchが開発した言語で、機械学習、数値計算、クラウドコンピューティングなどの分野で広く利用されています。この言語は、高い抽象度とシンプルさにより、プログラマーの生産性を向上させます。
本書は、F#の基礎から応用までを網羅した教科書です。読者は、F#でのプログラミングの基礎を身につけ、効率的なアルゴリズムや高度な並列処理の実装までを学ぶことができます。
本書が、F#を学び始める人、またはF#を応用したい人にとっての貴重なリソースとなることを願っています。
F#の概要
[編集]F#とは何か
[編集]F#は、.NET Framework上で動作する関数型プログラミング言語です。 F#は、.NET用の機能を提供する共通言語ランタイム(CLR)の一部です。 F#は2005年にマイクロソフトが開発し、2007年に最初の公開バージョンが登場しました。 F#は、静的型付け、型推論、高階関数、高度なパターンマッチング、および並列プログラミングを簡潔かつ効果的に行うことができます。
F#のアーキテクチャ
[編集]F#は、関数型プログラミング言語として設計されています。そのため、F#アプリケーションは、大部分が関数で構成されています。F#は、.NET Frameworkの一部であり、同じライブラリ、ツール、およびリソースにアクセスすることができます。 F#は、C#やVB.NETと同様に、既存の.NETコンポーネント、ライブラリ、クラス、およびモジュールを使用できます。
F#はまた、並列および非同期プログラミングに最適化されています。これらの機能により、大規模な並列計算を効率的かつ簡潔に記述できます。F#は、.NETのAsynchronous Programming Model(APM)やTask-Parallel Library(TPL)など、.NETの標準並列化ライブラリにもアクセスできます。
F#の特徴
[編集]F#の特徴は以下の通りです。
- 関数型プログラミング:F#は関数型プログラミング言語であり、関数を一等公民として扱います。また、不変性や遅延評価などの概念が組み込まれており、関数型プログラミングによるシンプルかつ安全なコードが書けます。
- オブジェクト指向プログラミング:F#はC#やVB.NETと同じ.NET言語ファミリーに属しており、それらと同様にオブジェクト指向プログラミングにも対応しています。F#では、「型」と「オブジェクト」という概念を分けて考え、オブジェクト指向プログラミングで必要なカプセル化や継承などの機能を提供しています。
- 静的型付け:F#は静的型付け言語であり、コンパイル時に型の整合性チェックが行われます。これにより、実行時エラーを防ぎ、正確で信頼性の高いコードを書くことができます。
マルチプラットフォーム対応:F#は、.NET Frameworkや.NET CoreのようなMicrosoftのプラットフォームだけでなく、MonoやUnityなど、さまざまなプラットフォームで動作します。また、F#はWindows、Linux、Mac OS Xなどのオペレーティングシステムでも利用できます。
- 相互運用性:F#は、他の.NET言語(C#やVB.NETなど)やNative Code(C++やFortranなど)との相互運用が可能です。これにより、既存のコードをF#に移行することができるほか、F#で書かれたコードを他の言語で利用することもできます。
- パターンマッチング:F#には、パターンマッチングという便利な機能があります。これは、異なる型や構造を持つデータを安全かつ簡潔に分解することができるため、プログラムの柔軟性と読みやすさを向上させます。
- アクティブパターンマッチング:F#には、アクティブパターンマッチングという機能があります。これは、.NET FrameworkやCOMオブジェクトなどの外部リソースに対して、パターンマッチングを適用することができる機能です。これにより、F#で高度な書き方をすることができます。
F#のインストールとセットアップ
[編集]あなたの環境に応じて、複数の方法でF#をインストールすることができます。
Visual Studioを使用してF#をインストールする場合、Visual Studio Installerがまずインストールされます。初めてVisual Studioをダウンロードする場合は、インストーラから適切なエディションのVisual Studioをインストールしてください。
既にVisual Studioがインストールされている場合は、F#を追加したいエディションの横にある「変更」を選択してください。ワークロードページで、ASP.NETおよびWeb開発ワークロードを選択します。これには、ASP.NET CoreプロジェクトのF#および.NET Coreサポートが含まれています。「選択したすべてをインストールする」を選択して、右下隅の「変更」を選択します。
Visual Studio InstallerでF#が含まれるVisual Studioを開始するには、「Visual Studioで起動」を選択してください。
Visual Studio CodeでF#をインストールする場合、gitがPATHで利用可能であることを確認してください。コマンドプロンプトでgit --versionを入力し、Enterを押すことで確認できます。.NET SDKおよびVisual Studio Codeをインストールしてください。「拡張機能」アイコンを選択し、「Ionide」で検索してください。「Ionide-fsharp」は、Visual Studio CodeでF#をサポートするために必要な唯一のプラグインです。ただし、FAKEサポートを取得するために「Ionide-FAKE」をインストールしたり、パッケージのサポートを取得するために「Ionide-Paket」をインストールすることもできます。FAKEおよびPaketは、プロジェクトのビルドおよび依存関係の管理に使用される、追加のF#コミュニティツールです。Visual Studio for MacでF#をインストールする場合、どの構成を選択しても、F#はデフォルトでインストールされます。インストールが完了したら、「Visual Studioを起動」を選択してください。
MacOS上のFinderを介してVisual Studioを開くこともできます。
ビルドサーバーにF#をインストールする場合、.NET SDKを介して.NET Coreまたは.NET Frameworkを使用している場合は、ビルドサーバーに.NET SDKをインストールするだけです。必要なすべてが含まれています。.NET Frameworkを使用し、.NET SDKを使用していない場合は、Windows ServerにVisual Studio Build Tools SKUをインストールする必要があります。インストーラで、.NETデスクトップビルドツールを選択し、右側のインストーラメニューでF#コンパイラコンポーネントを選択します。
F#の基礎
[編集]変数と型
[編集]// F#の変数と型 // F#では、変数の宣言には let キーワードを使用します。 // let は、束縛 (binding) と呼ばれる操作を実行していることを示します。 // 変数は不変 (immutable) であり、一度束縛されたらその値を変更できません。 let age = 42 let name = "James" // F#では、型推論により、変数の型を明示的に指定する必要がない場合があります。 // ageの型は、整数型intであり // nameの型は、文字列型stringです // F#では、型アノテーションを使用して、変数の型を明示的に指定できます。 let x : float = 3.14 let y : int = 42 // 静的型付け言語であるF#では、型エラーがコンパイル時に検出されます。 // したがって、コンパイル時に発生しうる多くのエラーが実行時エラーになることがありません。 // F#には、各種の基本データ型があります。 // 例えば、次のような基本データ型があります。 // 整数型 let a : int = 42 let b : int32 = 42l let c : int64 = 42L let d : byte = 255uy let e : sbyte = 127y // 浮動小数点型 let f : float = 3.14 let g : double = 3.14159265359 // 真偽型 let h : bool = true let i : bool = false // 文字列型 let j : string = "hello world" // 文字型 let k : char = 'a' // ユニット型 let l : unit = () // F#には、配列やタプルなどの複合型もあります。 // これにより、複数の値をまとめて扱うことができます。 // 配列 let arr : int[] = [|1; 2; 3|] let arr2D : int[,] = array2D [[1; 2]; [3; 4]] // タプル let tpl : int * string = (42, "answer") // F#では基本的に変数に再代入することはできませんが、変更可能な値を扱う場合は、mutableというキーワードを用いて、明示的に変更可能な変数を宣言することができます。 // 変更可能な数値を宣言 let mutable count = 0 // 変更 count <- count + 1 // 上記のように、<-演算子を用いて、変更可能な変数の値を更新することができます。
式と演算子
[編集]F#はマルチパラダイムプログラミング言語であり、関数型言語の特徴を持っています。F#では、数値式、論理式、文字列式、リスト式、配列式、タプル式など、さまざまな式がサポートされています。
数値式では、算術演算子(+、-、*、/など)や比較演算子(=、<、>など)を使用して、数値計算を行うことができます。例えば、以下のようなコードを書くことができます。
let x = 5 let y = 10 let z = x + y // zには15が代入される let isGreaterThan = y > x // isGreaterThanにはtrueが代入される
論理式では、論理演算子(&&、||、notなど)を使用して、論理演算を行うことができます。例えば、以下のようなコードを書くことができます。
let a = true let b = false let c = a && b // cにはfalseが代入される let d = not a // dにはfalseが代入される
文字列式では、文字列結合演算子(+)を使用して、文字列を結合することができます。例えば、以下のようなコードを書くことができます。
let str1 = "Hello, " let str2 = "world!" let greeting = str1 + str2 // greetingには"Hello, world!"が代入される
リスト式では、リスト演算子(::)を使用して、リストを作成することができます。例えば、以下のようなコードを書くことができます。
let myList = 1 :: 2 :: 3 :: [] // myListには[1; 2; 3]が代入される
配列式でもリスト演算子を使用することができますが、それ以外にも様々な関数を使用することができます。例えば、以下のようなコードを書くことができます。
let myArray = [|1; 2; 3|] let firstElement = myArray.[0] // firstElementには1が代入される let newArray = Array.map (fun x -> x * 2) myArray // newArrayには[2; 4; 6]が代入される
タプル式では、カンマ(,)を使用して、複数の値を1つのオブジェクトにまとめることができます。例えば、以下のようなコードを書くことができます。
let myTuple = ("apple", 3) let (fruit, quantity) = myTuple // fruitには"apple"が代入されquantityには3が代入される
制御構造
[編集]以下は F# の制御構造の概要です。
条件分岐
[編集]F# では、if/then/else 構文を使って条件分岐を行うことができます。構文は以下の通りです。
if 条件1 then 式1 elif 条件2 then 式2 else 式3
例えば、x が 0 より大きい場合には "Positive"、0 の場合には "Zero"、0 より小さい場合には "Negative" という文字列を返す関数を定義すると以下のようになります。
let sign x = if x > 0 then "Positive" elif x = 0 then "Zero" else "Negative"
ループ
[編集]F# では、for ループや while ループを使って反復処理を行うことができます。
for
[編集]for ループ制御変数 = 初期値 to 最終値 do // 処理を記述する done
例えば、1 から 10 までの合計を計算すると以下のようになります。
let mutable total = 0 for i = 1 to 10 do total <- total + i printfn "%i" total
while
[編集]while 条件 do // 処理を記述する done
上記の for の例と等価なループをwhileで書いた例
let mutable total = 0 let mutable i = 1 while i <= 10 do total <- total + i i <- i + 1 printfn "%i" total
パターンマッチング
[編集]match
[編集]match
は、F#でパターンマッチングを行うための機能です。異なるケースに応じて、値やパターンに基づいて処理を選択するのに使われます。これにより、特定のパターンに一致する処理を行うことができます。
例えば、以下はmatch
の基本的な使い方です。
let describeNumber number = match number with | 0 -> printfn "ゼロです" | 1 -> printfn "一です" | 2 -> printfn "二です" | _ -> printfn "その他の数字です"
この例では、describeNumber
関数が与えられた数字に対してマッチングを行います。match
は与えられた number
の値を各パターンと比較し、それぞれのパターンに一致する処理を実行します。
0
の場合は "ゼロです" と表示します。1
の場合は "一です" と表示します。2
の場合は "二です" と表示します。- 上記のいずれにも当てはまらない場合は、
_
(ワイルドカード)で表されるデフォルトのパターンにマッチし、"その他の数字です" と表示します。
match
式は、パターンが網羅的であるかを確認し、全ての可能性を網羅しているかをチェックすることが重要です。パターンが網羅的でない場合、コンパイル時に警告が出ることがあります。
さらに、match
は多彩なパターンマッチングをサポートしており、タプルやリスト、カスタムデータ型などのパターンも扱えます。これにより、柔軟な条件分岐やデータ構造の解析が可能になります。
- より複雑なパターンマッチングの例
複雑なパターンマッチングの例として、タプルやリストなどの複合的なパターンを含むものを見てみましょう。
type Person = | Adult of string * int | Child of string * int let describePerson person = match person with | Adult (name, age) when age >= 18 -> printfn "%sさんは大人で、年齢は%d歳です" name age | Child (name, age) when age < 18 -> printfn "%sさんは子供で、年齢は%d歳です" name age | _ -> printfn "情報がありません" let adultPerson = Adult ("Alice", 25) let childPerson = Child ("Bob", 12) describePerson adultPerson describePerson childPerson
この例では、Person
というカスタムデータ型を定義し、Adult
と Child
という2つのケースを持つ型を作成しています。Adult
と Child
はそれぞれ名前と年齢の情報を保持します。
そして、describePerson
関数では、match
を使って Person
インスタンスを分析しています。
Adult
ケースでは、age
が 18歳以上の場合、"大人" としてその人物の情報を表示します。Child
ケースでは、age
が 18歳未満の場合、"子供" としてその人物の情報を表示します。- 上記のどちらにも一致しない場合、"情報がありません" と表示します。
この例では、カスタムデータ型やガード条件(when
条件)を使用して、より複雑なパターンマッチングを行っています。これにより、特定の条件に基づいてデータを詳細に分析・処理することができます。
プレースホルダー
[編集]F#におけるプレースホルダーは、パターンマッチングや関数の一部を置き換えるための特別な構文です。主に2つの場所で使用されます。
関数内のプレースホルダー
[編集]関数内で _
を使うことで、無視される引数を示すことができます。
例えば、次の関数は引数を無視して"Hello"という文字列を返します。
let sayHello _ = "Hello"
ここでの _
は関数に渡される引数を無視するプレースホルダーとして機能します。関数の中でその引数を使わない場合に便利です。
パターンマッチングでのプレースホルダー
[編集]match
式の中で _
を使うことで、特定のパターンにマッチするものを無視することができます。
例えば、以下の例では、0から9までの数値をマッチさせ、特定の数値に対して特定の処理を行いますが、それ以外の数値は無視します。
let describeNumber number = match number with | 0 -> printfn "ゼロです" | n when n > 0 && n < 10 -> printfn "%dは1桁の正の数です" n | _ -> () // それ以外の数値は無視する
ここでは、_
はそれ以外のすべてのパターンにマッチします。その場合、何も処理をせずに単に ()
(ユニット型の値)を返します。このような使い方で、特定のケース以外のパターンを無視することができます。
LINQ
[編集]F#でもLINQ(Language Integrated Query)を使用することができます。LINQはC#やVB.NETで特に広く使われていますが、F#でも同様のクエリ言語を使ってデータを操作することが可能です。
F#では、シーケンスやコレクションに対してクエリを書くためのクエリ式を使用します。以下はLINQのようなクエリを使った例です。
// シーケンスのフィルタリングとマッピング let numbers = [| 1; 2; 3; 4; 5 |] let result = query { for number in numbers do where (number % 2 = 0) select (number * number) } result |> Seq.iter (printfn "%d")
この例では、query
パターンを使ってnumbers
配列の偶数の要素をフィルタリングし、それらの数値を自乗しています。F#では、クエリ式を使ってデータをフィルタリング、マッピング、ソート、グループ化などの操作を行うことができます。
例外処理
[編集]F# では、try/with 構文を使って例外処理を行うことができます。
try // 例外が発生しうる処理を記述する with // 例外が発生した場合の処理を記述する
例えば、割り算を行う関数を定義すると以下のようになります。
let divide a b = try a / b with | :? System.DivideByZeroException -> 0
第 1 引数を第 2 引数で割り、整数の結果を返します。ただし、第 2 引数が 0 の場合は例外が発生し、0 を返します。
オブジェクト指向プログラミング
[編集]クラスの継承
[編集]F#でもクラスの継承は可能です。F#は.NETの言語ランタイムの一部であり、そのランタイムにおけるオブジェクト指向の機能を利用することができます。
以下は、F#でクラスの継承を行う例です。
type Person(name: string, age: int) = member this.Name = name member this.Age = age // Person クラスを継承した Customer クラスの定義 type Customer(name: string, age: int, customerId: int) = inherit Person(name, age) member this.CustomerId = customerId // Customer クラスのインスタンスを作成 let customer1 = Customer("Alice", 30, 12345) // 継承元のメンバーへのアクセス printfn "Name: %s, Age: %d" customer1.Name customer1.Age // Customer クラスの独自のメンバーへのアクセス printfn "Customer ID: %d" customer1.CustomerId
上記の例では、Person
クラスを継承した Customer
クラスを定義しています。inherit
キーワードを使用して Person
クラスを継承し、Customer
クラスの独自のメンバーである CustomerId
を追加しています。
F#ではオブジェクト指向プログラミングがサポートされており、クラスの継承やポリモーフィズムなどの機能を利用することができます。
プロトタイプベース的な手法
[編集]F#は、プロトタイプベースのオブジェクト指向言語としては一般的ではありませんが、一部のプロトタイプベースの特性を模倣する方法があります。プロトタイプベースの言語では、オブジェクトは別のオブジェクトをプロトタイプとして使用し、そのプロトタイプの特性を継承したり、動的に変更したりできます。
F#では、ResizeArray
(可変サイズの配列を表すクラス)や、F#のレコード型のような構造を利用して、プロトタイプの特性に近いものを模倣することができます。ただし、JavaScriptのような完全なプロトタイプベースの挙動を再現することは難しいかもしれません。
以下は、F#のレコード型を使ってプロトタイプの特性を近似した例です。
type Prototype = { Name: string Age: int } // プロトタイプとしてのオブジェクトを作成 let prototype = { Name = "Prototype"; Age = 30 } // 新しいオブジェクトをプロトタイプを基にして作成 let obj1 = { prototype with Name = "Object 1" } // 別のオブジェクトをプロトタイプを基にして作成 let obj2 = { prototype with Name = "Object 2"; Age = 25 } // オブジェクトの情報を表示 printfn "Prototype: %s, %d" prototype.Name prototype.Age printfn "Object 1: %s, %d" obj1.Name obj1.Age printfn "Object 2: %s, %d" obj2.Name obj2.Age
この例では、Prototype
というレコード型を作成し、それをプロトタイプとして扱います。with
キーワードを使ってプロトタイプを基に新しいオブジェクトを作成しています。ただし、これは完全なプロトタイプベースの挙動ではなく、単なるレコード型の特性を利用して近似したものです。
関数の定義
[編集]F#における関数定義の一般的な書式は以下の通りです。
let 関数名 引数1 引数2 ... 引数n = 関数の処理
// 引数が2つの場合の加算関数を定義する例 let add x y = x + y // 引数が2つの場合の減算関数を定義する例 let subtract x y = x - y // 引数が3つの場合の最大値を求める関数を定義する例 let max3 x y z = max x (max y z)
これらの例を使って、次に示すように関数を呼び出すことができます。
let result1 = add 2 3 // 5が返される let result2 = subtract 5 3 // 2が返される let result3 = max3 7 3 9 // 9が返される
再帰的呼出しを行う 関数の定義
[編集]F#で再帰関数を定義する場合は、以下の書式のように rec キーワードを伴って定義します。
let rec 関数名 引数リスト = // ベースケース(再帰を終了する条件)の処理 if ベースケース then // 処理 // 再帰ステップの処理 else // 処理 関数名 変更された引数リスト
たとえば、0からnまでの自然数の和を計算する以下の再帰関数を定義する場合は、以下のように書けます。
let rec sum n = if n <= 0 then 0 else n + sum (n - 1)
この関数を使って、例えばsum 5を評価すると、1 + 2 + 3 + 4 + 5 = 15が返されます。
モジュール
[編集]F#のモジュールは、F#コードの再利用可能な単位であり、関数、型、変数、およびその他の要素を含むことができます。モジュールは、F#コードの構造化された組織化を可能にし、コードの読みやすさ、保守性、および再利用性を向上させます。モジュールは、コードの一部を隠すことができ、カプセル化を実現することができます。F#のモジュールは、別のF#プログラムや.NETアプリケーションで使用することができます。
// モジュール宣言 module MyModule = // 関数宣言 let add x y = x + y let subtract x y = x - y
上記の例では、MyModuleという名前のモジュールを宣言しています。このモジュール内には、addとsubtractという関数が定義されています。add関数は2つの引数を受け取り、それらを加算した結果を返します。subtract関数は同様に2つの引数を受け取り、それらを減算した結果を返します。
このようにモジュールを利用する場合は、以下のように他のファイルでインポートして使用することができます。
// 別ファイルでのモジュールのインポート open MyModule // モジュール内の関数の使用例 let result1 = add 3 4 // 7 let result2 = subtract 5 2 // 3
ここでは、MyModuleモジュールをopenして他のファイルからアクセスすることができるようになっています。そして、addとsubtractという関数を呼び出しています。これらの関数は、それぞれ引数を与えることで、加算および減算の結果を取得することができます。
用語集
[編集]- コンパイラ (compiler):高水準のプログラムを機械語に変換するソフトウェア
- ランタイム環境 (runtime environment):プログラムが実行される場所で、必要なライブラリや関数を提供し、メモリやCPUを管理する仕組み
- マルチパラダイム言語 (multi-paradigm language):複数のプログラミングパラダイムを組み合わせた言語で、手続き型、関数型、オブジェクト指向などがある
- 関数型プログラミング (functional programming):関数や引数、戻り値などを用いてプログラミングを行うパラダイム
- シーケンス (sequence):要素が順に並ぶデータ構造のこと。F#では、リスト、配列、シーケンス式などがある
- パターンマッチング (pattern matching):指定したパターンに一致するデータを処理するための方法で、F#ではmatch式が用いられる
- 変数束縛 (variable binding):変数に値を割り当てたり、関数の引数や戻り値を指定したりする操作。F#ではlet式が用いられる
- オプション型 (option type):値を持つかどうかわからない場合に使用する型で、Some値またはNone値を取る。F#ではオプション型が用いられる
- 型推論 (type inference):コンパイラが、変数や関数などの型を自動的に導出する機能。F#では型推論が強力にサポートされている
- 型アノテーション (type annotation):変数や関数などに、明示的に型を指定する記述方法。F#でも型アノテーションが可能である
F#のデータ型
[編集]F#には、以下のようなデータ型があります。
- プリミティブ( Primitive Types )
- int: 整数値
- float: 浮動小数点数
- bool: 真偽値
- char: 文字
- string: 文字列
- タプル( Tuple Types )
- 2つ以上の値をまとめて扱う
- 型の異なる値をまとめられる
- コレクション
- リスト( List Types ):要素が何らかの順序で格納された可変長のリスト
- 要素が同じ型で構成されるシーケンス型
- 再帰的な定義が可能
- シーケンス( Sequence Types ):単一の型を持つ要素の連なり
- 制約のない、様々な値を要素とするシーケンス型
- 無限リストやジェネレーターに使用される
- 配列( Array Types ):要素が何らかの順序で格納された固定長の配列
- 要素が同じ型のコレクション型
- インデックスによるランダムアクセスが可能
- 集合( Set Types ):順序のない一意の要素の集合
- 連想配列( Map Types ):順序のないキーと値のペアの集合
- リスト( List Types ):要素が何らかの順序で格納された可変長のリスト
- ユーザー定義型
- レコード( Record Types )
- フィールド名と値からなる構造体のような型
- オブジェクト指向プログラミングにおけるクラスと似た振る舞いをする
- クラス
- 共用体( Discriminated Union Types)
- 同じデータの集合を表す型
- ラベル付きの値を使っているため、型安全なディスパッチが可能
- 構成要素の種類ごとに異なる振る舞いをすることができる
- インターフェイス
- 列挙型
- レコード( Record Types )
以上が F# で提供されるデータ型の種類になります。
タプル
[編集]F#のタプルには以下のような特徴があります。
- 複数の値を一つのオブジェクトにまとめることができます。
- タプル内の値の型や数は固定されています。
- タプルは他のオブジェクトと同様に変数に割り当てることができます。
また、F#のタプルは以下のような使い方ができます。
タプルの値を変数に代入することができます。
let person = ("John Doe", 30) let name, age = person printfn "Name: %s, Age: %d" name age
- 実行結果
Name: John Doe, Age: 30
タプルを関数の戻り値として使用することができます。
let addNumbers a b = (a + b, a * b) let result = addNumbers 4 5 printfn "Sum: %d, Product: %d" (fst result) (snd result)
- 実行結果
Sum: 9, Product: 20
タプルをリストの要素として使用することができます。
let people = [("John Doe", 30); ("Jane Smith", 25)] for person in people do printfn "Name: %s, Age: %d" (fst person) (snd person)
- 実行結果
Name: John Doe, Age: 30 Name: Jane Smith, Age: 25
以上がF#のタプルに関する機能の一部です。
オプション
[編集]F#のOption型は、値が存在する場合と存在しない場合の2つの状態を表すために使用されます。Option型はF#標準ライブラリの一部であり、以下のように定義されます。
type Option<'T> = | None | Some of 'T
'T
には任意の型を指定することができます。例えば、Option<int>
は整数型の値が存在する場合と存在しない場合の2つの状態を表現するために使用することができます。
Option型には、値が存在するかどうかを確認するために使用できる関数が用意されています。以下にその例を示します。
範囲を持つ値を返す場合、Option型を使用することが一般的です。以下の例は、文字列が数値である場合にその数値を返し、それ以外の場合はNone
を返します。
let parseInt s = match System.Int32.TryParse s with | true, n -> Some n | _ -> None
これを使って、"10"
の値を解析すると、結果はSome 10
になります。"abc"
のように解析できない値の場合は、None
が返されます。
Option型を使用すると、Nullポインタ例外などのバグを防ぐことができます。しかし、プログラマーが手動でOption型を扱うためのコードを書く必要があるため、多少面倒な面があります。
以下は、Option型の主な操作です。
None
: Option型の値「存在しない」を表現する。Some
: Option[removed]型の値「T型の値が存在する」を表現する。Option.isNone
: Option型の値がNone
であることを判定する。Option.isSome
: Option型の値がSome
であることを判定する。Option.defaultValue
: Option型の値がNone
である場合に、デフォルト値を与える。Option.get
: Option[removed]型の値がSome
である場合に、その中の値を取得する(None
の場合はエラー)。Option.tryGetValue
: Option[removed]型の値がSome
である場合に、その中の値とtrueを取得する。ただし、None
の場合には、デフォルト値とfalseを返す。Option.map
: Option[removed]型の値がSome
である場合に、その中の値に対して関数を適用し、新しいOption型を生成する。Option.bind
: Option[removed]型の値がSome
である場合に、その中の値に対して関数を適用し、新しいOption型を生成する。Option.tryWith
: Option[removed]型の値がSome
である場合に、その中の値を引数とする関数を適用する。ただし、None
の場合は、デフォルト値を返す。
コレクション
[編集]以下はF#のコレクションと主なメソッドを表形式でまとめたものです。
F# のコレクションとメソッド List Seq Array Set Map length/count List.length
Seq.length
Array.length
Set.Count
Map.Count
append @
Seq.append
Array.append
- - head List.head
Seq.head
Array.head
Set.minElement
Map.minValue
tail List.tail
Seq.tail
Array.tail
Set.remove
Map.remove
init List.init
Seq.init
Array.init
- - take List.take
Seq.take
Array.sub
Set.takeWhile
Map.filter
drop List.drop
Seq.drop
Array.sub
Set.skipWhile
Map.filter
filter List.filter
Seq.filter
Array.filter
Set.filter
Map.filter
map List.map
Seq.map
Array.map
Set.map
Map.map
fold List.fold
Seq.fold
Array.fold
Set.fold
Map.fold
上記の表では、各行がメソッド、各列がコレクションを表しています。たとえば、List
という列にあるlength/count
というセルは、リストの要素数を取得するためのメソッドであるList.length
に対応しています。同様に、Seq
という列にあるmap
というセルは、シーケンスの要素に関数を適用し、新しいシーケンスを作成するためのSeq.map
メソッドに対応しています。
リスト
[編集]F#において、リストは非常に重要なデータ構造です。F#のリストは不変な一連の値のシーケンスであり、アイテムを追加、削除、変更できません。 ただし、新しいリストを作成することで、既存のリストから要素を削除したり、要素を追加したりすることができます。
F#のリストは、パターンマッチングによって処理されることがよくあります。たとえば、次のようにして、リストの先頭要素と残りのリストを取得することができます。
let lst = [1; 2; 3; 4] match lst with | head :: tail -> printfn "Head: %i, Tail: %A" head tail | [] -> printfn "Empty list"
また、F#のリストは遅延評価されるため、必要に応じて要素を生成するために使用することができます。たとえば、次の関数は、与えられた整数に対する素数を持つ無限リストを生成します。
let rec isPrime n = match n with | 1 -> false | 2 -> true | _ -> let limit = int(sqrt(double(n))) + 1 [2..limit] |> List.forall (fun x -> n % x <> 0) let rec primes = Seq.unfold(fun state -> Some (state, state + 1)) 2 |> Seq.filter isPrime |> Seq.cache |> Seq.toList
F#のリストは、高階関数と一緒に使用することができる場合があります。特に、List.map、List.filter、List.foldなどの関数は、リストの要素に対して変換、フィルタリング、畳み込み操作を実行するために使用することができます。
let lst = [1; 2; 3; 4] let square a = a * a let squares = lst |> List.map square let even a = a % 2 = 0 let evens = lst |> List.filter even let sum a b = a + b let total = lst |> List.fold sum 0
これらの例は、F#のリストに関する機能の一部ですが、F#のリストは非常に強力で、多くの多彩な操作を実行することができます。
シーケンス
[編集]F#のシーケンスには、非常に多くの機能があります。以下にいくつかの例を挙げてみます。
- シーケンスの定義
- シーケンスは、
seq<'a>
型で定義されます。具体的には、以下のように書くことができます。 let mySeq = seq { 1; 2; 3 }
これにより、mySeqは1から3までの整数を含むシーケンスとして定義されます。
- シーケンスの操作
- F#のシーケンスには、フィルタリング、変換、組み合わせなどの操作を行うための多くの関数があります。例えば、以下のような関数があります。
Seq.filter
: 条件に合わない要素を取り除きます。Seq.map
: 各要素を変換します。Seq.reduce
: シーケンスの一部またはすべてを単一の値に集約します。Seq.append
: 2つのシーケンスを連結します。Seq.zip
: 2つのシーケンスをペアにします。
- シーケンスの遅延評価
- F#のシーケンスは、遅延評価の仕組みを持っています。つまり、必要なときに要素が実際に取得されます。たとえば、以下のように書くことができます。
let nums = seq { for i in 1 .. 10 do yield i } let even = nums |> Seq.filter (fun x -> x % 2 = 0) printfn "%A" even
この場合、evenを出力するまで、numsの要素は実際には計算されません。必要な場合にのみ計算されます。
以上が、F#のシーケンスに関する簡単な説明です。より詳細な情報については、F#の公式ドキュメントを参照してください。
配列
[編集]F#には、様々な配列を処理するための便利な組み込み関数が用意されています。以下に、配列に関するいくつかの機能を紹介します。
- 配列の初期化
- 新しい配列を作成し、特定の値で初期化するために、Array.init関数を使用します。例えば、次のコードは、要素数が10で、すべての要素が0である配列を作成します。
let array = Array.init 10 (fun _ -> 0)
- 配列の変換
- 配列の各要素を変換するために、Array.map関数を使用します。例えば、次のコードは、array配列のすべての要素に1を加えた新しい配列を作成します。
let newArray = Array.map (fun x -> x + 1) array
- 配列のフィルタリング
- 配列からいくつかの要素を取り出したい場合、Array.filter関数を使用します。例えば、次のコードは、array配列から値が偶数の要素のみを取り出す新しい配列を作成します。
let evenArray = Array.filter (fun x -> x % 2 = 0) array
- 配列の畳み込み
- 配列を畳み込むために、Array.fold関数を使用します。例えば、次のコードは、array配列のすべての要素を足し合わせた結果を返します。
let sum = Array.fold (fun acc x -> acc + x) 0 array
以上、F#の配列に関するいくつかの機能を紹介しました。F#の配列について詳しく学ぶには、F#の公式ドキュメントを参照してください。
集合
[編集]F# の Set は、重複のない要素のコレクションを扱うためのデータ構造です。以下にいくつかの機能を紹介します。
- Set の要素は自動的にソートされます。
- 各要素は一意な値であるため、Set は集合演算を行うのに適しています。2 つの Set への和集合、差集合、積集合を取ることができます。
- Set 内の要素にアクセスするには、イテレーションによる方法を使います。Set をラムダ式でフィルタリングすることもできます。
- 特定の要素を Set から削除するには、Set をフィルタリングして新しい Set を作成することで実現できます。
- Set の要素を追加するには、Set.add メソッドを使用します。
以下は、F# の Set を使用した例です。
// Set の作成 let set1 = Set.ofList [1;2;3;4;5] let set2 = Set.ofList [4;5;6;7;8] // 集合演算 let union = Set.union set1 set2 let diff = Set.difference set1 set2 let intersect = Set.intersect set1 set2 // Set のフィルタリング let filteredSet = set1 |> Set.filter (fun x -> x % 2 = 0) // Set から要素を削除する let removedSet = set1 |> Set.filter (fun x -> x <> 3) // Set に要素を追加する let addedSet = Set.add 6 set1
連想配列
[編集]F#のMap
は、連想配列を表現するためのデータ構造です。以下に、その機能をいくつか紹介します。
Map
は、キーと値のペアを格納することができます。キーの型と値の型は任意のものが使用できます。Map
はイミュータブルなデータ構造であり、新しいMap
を作成する場合、元のMap
は変更されず、変更点のみが新しいMap
に反映されます。Map.add
メソッドは、既存のMap
に新しいキーと値のペアを追加することができます。既に存在するキーが指定された場合は、値が上書きされます。Map.tryFind
メソッドは、指定されたキーに対応する値を返します。キーが存在しない場合はNone
を返します。Map.fold
メソッドは、指定された関数をMap
のすべてのキーと値のペアに適用し、結果を返します。
let map = Map.empty |> Map.add "one" 1 |> Map.add "two" 2 let map2 = map |> Map.add "two" 3 let value = map2 |> Map.tryFind "one" let sum = map2 |> Map.fold (fun acc key value -> acc + value) 0 printfn "map: %A" map printfn "map2: %A" map2 printfn "value: %A" value printfn "sum: %d" sum
Map
の作成、Map.add
メソッドの使用、Map.tryFind
メソッドの使用、およびMap.fold
メソッドの使用を行っています。
ユーザー定義型
[編集]F#のtype
構文は、柔軟で多様なデータ構造を定義するための強力な機能です。以下にその柔軟性を示すいくつかの例を挙げます。
レコード
[編集]F#におけるレコードは、フィールドの集合に名前を付けて構造を定義するための機能です。F#のレコードは、.NETのクラスとは異なり、値型(value type)として定義されます。
- F#のレコードは、C#のstructに相当する値型です。
- レコードは、for文やmatch式などのパターンマッチング機能によって、簡単に扱うことができます。
- レコードは、フィールドのパーシャリティ(partiality)をサポートします。つまり、レコードの一部だけを更新することができます。
- レコードは、F#のREPL(Read-Eval-Print Loop)で簡単に検査できます。例えば、パーシャルなレコードを定義した場合、REPL上でどのフィールドが定義されているかを確認できます。
- レコードは、型推論によって、明示的な型指定を省略できます。
以下は、F#でレコードを定義する方法と、レコードを使用する方法についての例です。
// レコードの定義 type Person = { Name : string Age : int } // Persons配列 let persons = [ { Name = "Alice"; Age = 25 } { Name = "Bob"; Age = 30 } { Name = "Charlie"; Age = 35 } ] // ループでレコードを使用する for person in persons do printfn "Name: %s, Age: %d" person.Name person.Age
- 実行結果
Name: Alice, Age: 25 Name: Bob, Age: 30 Name: Charlie, Age: 35
この例では、Personというデータ型(レコード)を定義し、NameとAgeという2つのフィールドを持たせています。そして、3人の人物情報を含むpersonsという配列を作成し、forループを使用してpersons内の全ての人に関する情報を表示しています。
type Person = { Name: string Age: int Email: string option } let alice = { Name = "Alice" Age = 30 Email = Some "alice@example.com" } let bob = { Name = "Bob" Age = 25 Email = None } let printPersonInfo person = printfn "Name: %s, Age: %d" person.Name person.Age match person.Email with | Some email -> printfn "Email: %s" email | None -> printfn "Email: Not provided" printPersonInfo alice printPersonInfo bob
- 実行結果
Name: Alice, Age: 30 Email: alice@example.com Name: Bob, Age: 25 Email: Not provided
レコード型はフィールド名とその型を指定し、複数の値をグループ化するために使用されます。この例では、Person
というレコード型を定義しています。Name
、Age
、Email
のフィールドを持ちます。Email
フィールドは string option
型として、オプションの文字列を表すことを示しています。
Person
レコード型を使用して alice
と bob
という2つの個人情報を定義しています。printPersonInfo
関数では、与えられた person
の情報を表示します。match
を使って、person
の Email
フィールドの有無に応じて適切なメッセージを出力しています。
Person
レコード型は、名前、年齢、オプションのメールアドレスの情報を持つよう定義されています。そしてそれぞれの値を持つ alice
と bob
のインスタンスを作成しています。printPersonInfo
関数では、それぞれの人物の情報をコンソールに表示しています。
クラス型 (Class Type)
[編集]type Customer(name: string, age: int) = member this.Name = name member this.Age = age let customer1 = Customer("Alice", 30) // Customer インスタンスのプロパティにアクセスして情報を表示する例 printfn "Customer Name: %s, Age: %d" customer1.Name customer1.Age
- 実行結果
Customer Name: Alice, Age: 30
クラス型はオブジェクト指向プログラミングにおけるクラスと似た機能を提供します。この例では、Customer
というクラス型を定義し、name
と age
を受け取るコンストラクタと、それに紐づく Name
と Age
のプロパティを持つクラスを作成しています。
Customer
というクラス型を定義し、name
と age
の2つのパラメータを取るコンストラクタを作成しています。また、それぞれのパラメータに対応するプロパティ Name
と Age
を定義しています。
let customer1 = Customer("Alice", 30)
で Customer
クラス型の新しいインスタンスを生成しています。その後、customer1
の Name
と Age
プロパティにアクセスして、その情報をコンソールに出力しています。
この例では、F#のクラス型を使用して、オブジェクト指向プログラミングの概念を実演しています。Customer
クラスのインスタンスを作成し、そのプロパティにアクセスしてデータを表示しています。
ユニオン型 (Union Type)
[編集]type Shape = | Circle of float | Rectangle of float * float | Square of float let circle = Circle(5.0) let rectangle = Rectangle(4.0, 6.0) let square = Square(3.0) let calculateArea shape = match shape with | Circle(radius) -> System.Math.PI * radius * radius | Rectangle(width, height) -> width * height | Square(side) -> side * side printfn "Circle Area: %f" (calculateArea circle) printfn "Rectangle Area: %f" (calculateArea rectangle) printfn "Square Area: %f" (calculateArea square)
- 実行結果
Circle Area: 78.539816 Rectangle Area: 24.000000 Square Area: 9.000000
ユニオン型は複数の型の中から一つを選択する型を表します。この例では、Shape
というユニオン型を定義しています。それぞれのケースは異なる形状を表し、Circle
、Rectangle
、Square
のいずれかとその固有のパラメータを持ちます。
また、calculateArea
関数は与えられた図形の面積を計算します。match
式を使って与えられた shape
の種類に応じて適切な計算を行います。円の場合は円の面積を、長方形の場合は長方形の面積を、正方形の場合は正方形の面積を計算します。
最後に、各図形の面積を計算してコンソールに表示しています。
F#には、共用体(Union Types)と呼ばれる特別なデータ型があります。共用体は、異なる型の値を含むことができるデータ型で、C#のenum型やJavaの列挙型に相当します。F#の共用体は、極めて柔軟で、使いやすいものです。
共用体を定義するには、typeキーワードによる新しい型の定義を行い、|を使って共用体の要素を列挙していきます。共用体の要素は、すべて同じ名前空間に属するものでなければなりません。
以下は、共用体を使用した簡単な例です。
// 共用体の定義 type Shape = | Circle of float | Rectangle of float * float let areaOfShape shape = match shape with | Circle(r) -> Math.PI * r ** 2.0 | Rectangle(w, h) -> w * h
この例では、Shapeという共用体型を定義して、CircleとRectangleという2種類の要素を持たせています。Circle要素は、1つの浮動小数点数を保持し、Rectangle要素は、2つの浮動小数点数を保持します。次に、areaOfShape関数を定義して、引数shapeがCircle要素であれば円の面積、Rectangle要素であれば長方形の面積を計算しています。
共用体を使用すると、複数の関連する値を1つの型にまとめることができ、プログラムの見通しがよくなります。また、異なる型の値を比較する場合にも非常に便利です。しかし、共用体が極めて柔軟であるため、使いすぎるとコードがややこしくなる場合もあります。
インターフェース (Interface)
[編集]type IShape = abstract member Area: float abstract member Perimeter: float type Rectangle(width: float, height: float) = interface IShape with member this.Area = width * height member this.Perimeter = 2.0 * (width + height) let rectangle = Rectangle(4.0, 6.0) let shapeArea = (rectangle :> IShape).Area let shapePerimeter = (rectangle :> IShape).Perimeter printfn "Rectangle Area: %f" shapeArea printfn "Rectangle Perimeter: %f" shapePerimeter
- 実行結果
Rectangle Area: 24.000000 Rectangle Perimeter: 20.000000
F#ではインターフェースもサポートしており、これにより抽象メンバーを定義することができます。上記の例では、IShape
というインターフェースを定義しています。このインターフェースには Area
と Perimeter
の抽象メソッドが含まれています。
そして、Rectangle
クラスのインスタンスを作成し、その Area
と Perimeter
を表示しています。このように、IShape
インターフェースを使うことで、異なる図形の面積や周囲の計算などを統一的な方法で扱うことができます。
列挙型
[編集]F#における列挙型(enumeration type)は、特定の値の有限なセットを表現するためのデータ型です。列挙型は事前に定義されたいくつかの選択肢の中から一つを選択するために使用されます。
以下は列挙型の基本的な構文と使用例です。
- 列挙型の定義
type DayOfWeek = | Monday | Tuesday | Wednesday | Thursday | Friday | Saturday | Sunday
この例では、DayOfWeek
という列挙型を定義しています。この型は、一週間の曜日を表すために使われます。それぞれの曜日は Monday
、Tuesday
、Wednesday
などの値として定義されています。
- 列挙型の使用例
let today = DayOfWeek.Tuesday match today with | DayOfWeek.Monday -> printfn "月曜日です" | DayOfWeek.Tuesday -> printfn "火曜日です" | _ -> printfn "その他の曜日です"
ここでは、today
という変数に DayOfWeek.Tuesday
を代入しています。そして match
を使って、today
の値に応じて曜日を表示する処理を行っています。
列挙型は特定の選択肢の集合を表すのに便利で、パターンマッチングなどと組み合わせることで、特定の選択肢に応じた処理を行うことができます。
F#のtype
構文は、レコード型からユニオン型、クラス型やインターフェースなど、さまざまなデータ構造を柔軟に定義することができます。これにより、関数型プログラミングからオブジェクト指向プログラミングまで、多岐にわたるプログラミングスタイルをサポートしています。
F#の高度な機能
[編集]関数型プログラミング
[編集]F#は、ML系の強力な関数型プログラミング言語です。F#は、高オーダー関数、カリー化、ピュア関数、代数的データ型などの強力な機能を備えています。
まず、高オーダー関数は、関数を引数として受け取る関数または関数を戻り値として返す関数です。これにより、非常に柔軟な関数を作成できます。例えば、以下の高オーダー関数 map
は、リストを受け取り、リストの各要素に関数を適用して新しいリストを生成します。
- map:(′a→′b)→′a list→′b list
let rec map f xs = match xs with | [] -> [] | x::xs' -> (f x)::(map f xs') map (fun x -> x * 2) [1; 2; 3] // [2; 4; 6]
次に、カリー化は、複数の引数をとる関数を、1つの引数をとる関数のチェーンに変換する方法です。カリー化を使用すると、再利用性が高く、より柔軟な関数を作成できます。
let add a b = a + b let addCurried a = fun b -> add a b addCurried 1 2 // 3
さらに、F#はピュア関数の作成を簡単にします。ピュア関数は、副作用がない関数であり、同じ入力に対して常に同じ出力を生成します。F#は、イミュータブル(変更不能)なデータ構造を使用することを推奨しており、これは、新しいデータ構造の生成のみをサポートしています。これにより、予期しない副作用が発生する可能性がある場合にも、安全性を確保することができます。
let add a b = a + b let doubleAdd a b = add (add a b) (add a b) doubleAdd 1 2 // 6
最後に、F#では、代数的データ型を使用して、異なる種類のデータを表現することができます。代数的データ型には、ユニオン型とレコード型があります。ユニオン型は、複数の型の一つでできる型であり、レコード型は、複数の型のフィールドを持つ型です。
type Color = | Red | Green | Blue let favoriteColor color = match color with | Red -> "I love red!" | Green -> "I love green!" | Blue -> "I love blue!" favoriteColor Red // "I love red!" type Person = { Name: string; Age: int; } let greetPerson person = sprintf "Hello %s, you are %d years old!" person.Name person.Age let person = { Name = "Alice"; Age = 25; } greetPerson person // "Hello Alice, you are 25 years old!"
以上がF#の関数型プログラミングの高度な機能です。これらの機能を駆使することで、より柔軟で再利用性の高いプログラムを作成できます。
静的型付き言語としてのF#
[編集]F#は、.NETフレームワークの一部であり、静的型付きのマルチパラダイムプログラミング言語です。F#の静的型付きシステムは、コンパイラによる静的解析により、実行時のエラーを減らし、保守性を向上させることができます。
F#には静的型付きの変数があり、型に基づいて制限されます。これにより、コードの品質が高く、デバッグが容易になります。また、F#では性能最適化のために、不変性が重視されています。このような不変性は、オブジェクトの状態を常に確実に把握することができるため、コードの可読性も向上します。
F#の静的型付きシステムは、.NETの型システムと高度に統合されています。これは、既存の.NETアプリケーションからの移行が容易であることを意味しています。また、F#は.NETの機能をサポートしており、C#やVB.NETと同じく、完全な相互運用性を持っています。
F#は、静的型付きシステムを活用することで、多くの問題を事前に検出することができます。これにより、バグを未然に防ぐことができるため、ソフトウェア開発の過程での品質と信頼性が向上し、保守性が高まります。
アクティブパターンマッチング
[編集]以下は、F#のアクティブパターンマッチングに関する簡単な例です。
// Active pattern to match even integers let (|Even|Odd|) x = if x % 2 = 0 then Even else Odd // Using the active pattern to match an even integer let isEven x = match x with | Even -> true | Odd -> false // Active pattern to match currency symbols let (|Dollar|Euro|) (s:string) = match s with | "$" -> Dollar | "€" -> Euro | _ -> failwith "Invalid currency symbol" // Using the active pattern to convert prices let convertPrice (symbol:string) (price:float) : float = match symbol with | Dollar -> price * 0.85 // Convert to euros | Euro -> price // No need to convert | _ -> failwith "Invalid currency symbol" // Example usage of the active patterns let evenNumber = 4 let oddNumber = 3 let dollarPrice = "$10.00" let euroPrice = "€5.00" printfn "Is %d even? %b" evenNumber (isEven evenNumber) printfn "Is %d even? %b" oddNumber (isEven oddNumber) let convertedDollarPrice = convertPrice dollarPrice 10.0 printfn "The price %s 10.00 is %.2f" dollarPrice convertedDollarPrice let convertedEuroPrice = convertPrice euroPrice 5.0 printfn "The price %s 5.00 is %.2f" euroPrice convertedEuroPrice
この例では、F#でアクティブパターンマッチングがどのように機能するかを示しています。最初に、|Even|Odd|
というアクティブパターンを定義します。このパターンは、整数が偶数か奇数かに応じて、2つのパターンマッチングを提供します。次に、isEven
関数を定義し、整数をEven
またはOdd
にパターンマッチングして、偶数であるかどうかを確認します。
次に、通貨シンボルをパターンマッチングするアクティブパターンを定義します。このアクティブパターンは、Dollar
とEuro
という2つのパターンを提供し、文字列がドルまたはユーロの通貨記号であるかを確認します。convertPrice
関数は、通貨シンボルと価格を受け取り、Dollar
の場合は価格をユーロに変換し、Euro
の場合は変換を行わずに価格を返します。最後に、printfn
を使用して、アクティブパターンを使用した変換の例を示します。
私たちが使用したprintfn
関数は、文字を出力するための簡単な関数で、%d
や%s
といったフォーマット指示子を使用して、変数の値を表示することができます。
F#の並列処理
[編集]F#は、.NET Frameworkの一部であるため、マルチスレッドや並列処理をサポートしています。F#の並列処理は、以下の2種類の方法で実現できます。
- Asyncパターンを使用した非同期処理
- Parallelパターンを使用した並列処理
Asyncパターンを使用した非同期処理
[編集]Asyncパターンは、.NET Framework 4.0以降で導入された非同期処理のためのパターンです。Asyncパターンを使用することで、マルチスレッドを使用した並列処理よりもコードがシンプルになります。以下は、Asyncパターンを使用したF#のコードサンプルです。
open System open System.Net open System.IO let downloadAsync (url: string) : Async<string> = async { let request = WebRequest.Create(url) use! response = request.AsyncGetResponse() use stream = response.GetResponseStream() use reader = new StreamReader(stream) return! reader.AsyncReadToEnd() } let downloadAndPrintAsync url = async { let! html = downloadAsync url printfn "Downloaded %d bytes from %s" html.Length url } async { do! downloadAndPrintAsync "http://google.com" do! downloadAndPrintAsync "http://baidu.com" } |> Async.RunSynchronously
このコードでは、Asyncパターンを使用して、2つのWebページを並列でダウンロードし、それぞれのサイズを表示しています。
Parallelパターンを使用した並列処理
[編集]Parallelパターンは、TPL(Task Parallel Library)の一部であるため、.NET Framework 4.0以降で利用できます。Parallelパターンを使用することで、C#のTaskやThreadPool、PLINQなどと同様に、簡単に並列処理を実現することができます。以下は、Parallelパターンを使用したF#のコードサンプルです。
let findPrimesInRange (low:int) (high:int) : int list = let isPrime n = if n < 2 then false else if n = 2 then true else if n % 2 = 0 then false else let limit = int(sqrt(double(n))) let rec loop i = if i > limit then true elif n % i = 0 then false else loop (i + 2) loop 3 [low..high] |> List.filter isPrime let findPrimesParallel (low:int) (high:int) : int list = [low..high] |> List.chunkBySize (List.length [low..high] / Environment.ProcessorCount) |> List.Parallel.map findPrimesInRange |> List.concat let sw = new System.Diagnostics.Stopwatch() let primes = sw.TimeIt (fun () -> findPrimesParallel 1 1000000) printfn "Found %d primes in %Oms" primes.Count sw.ElapsedMilliseconds
このコードでは、Parallelパターンを使用して、1~1,000,000の範囲内の素数を探索しています。既存のfindPrimesInRange関数を使用し、それを各々のチャンク(プロセッサ数に合わせて分割された入力リストのサブリスト)に対してマップしています。マップされた結果を一つのリストに連結することで、最終的な素数リストを作成します。また、sw.TimeItを使用することで、実行時間を計測しています。
これら2つのパターンは、さまざまなシナリオに応じて適用することができます。Asyncパターンは、I/OバウンドのアプリケーションやWebサービスなどの非同期処理に適しています。また、Parallelパターンは、CPUバウンドの重い演算などに適しています。F#の並列処理は、.NET Frameworkの豊富な並列処理ライブラリを活用しつつ、F#の関数型プログラミングの利点を生かした柔軟な並列処理を実現することができます。
F#の非同期処理
[編集]F#の非同期処理に関するサンプルコードです。
open System open System.Net // ダウンロード処理を行う非同期関数 let downloadAsync (url: Uri) = async { let webClient = new WebClient() let! html = webClient.AsyncDownloadString(url) return html } // メイン関数 [<EntryPoint>] let main argv = let url = "https://www.google.com" let uri = Uri(url) // 非同期ダウンロードを実行 let downloadTask = downloadAsync uri Async.RunSynchronously downloadTask printfn "Download completed." 0 // プログラムの終了コード
このコードでは、downloadAsync
関数は、 async
キーワードで修飾された非同期関数として定義されています。この関数は、System.Net
名前空間内にあるWebClient
クラスを使用して、指定されたUri
からHTMLをダウンロードします。
let! html = webClient.AsyncDownloadString(url)
のように、let!
を使用して非同期操作の結果を待つことができます。let!
は通常のlet
キーワードと似ていますが、非同期操作が完了するまで、他の処理をブロックせずに待機します。
Async.RunSynchronously
関数を使用することで、非同期操作を同期的に実行できます。この場合、downloadTask
変数に格納された非同期タスクを実行して、ダウンロード処理が完了するまでブロックします。
最後に、printfn
関数を使用して、ダウンロードが完了したことをユーザーに通知します。
F#から.NETの機能を直接使う
[編集]F#は.NET言語ランタイムの一部であり、そのため、.NETの機能やライブラリを直接利用することができます。以下にいくつかの方法を示します。
.NETクラスの使用
[編集]open System let currentDate = DateTime.Now printfn "Current Date and Time: %O" currentDate
open System
を使用することで、System
名前空間内のクラスや機能にアクセスできます。上記の例では、DateTime.Now
を使って現在の日時を取得し、それをコンソールに出力しています。
外部の.NETアセンブリの参照
[編集]外部の.NETアセンブリ(DLL)を利用する場合は、#r
ディレクティブを使用して参照を追加します。
#r
ディレクティブを使って外部のDLLを参照し、そのDLL内の名前空間やクラスを使うことができます。
#r "MyExternalLibrary.dll" open MyNamespace let result = MyExternalLibrary.MyClass.MyMethod()
.NETライブラリの使用
[編集]F#からは、標準の.NETライブラリを直接利用することもできます。
let list = System.Collections.Generic.List<int>() list.Add(1) list.Add(2) printfn "List: %A" list
System.Collections.Generic
などの.NETの標準ライブラリを使って、例えばリストを作成して操作することも可能です。
F#は.NETと密接に統合されており、その恩恵を受けて、様々な.NETの機能やライブラリを直接利用できることが特徴です。
F#の実践
[編集]F#によるWebアプリケーションの構築
[編集]F#によるデータ処理の実践
[編集]F#による数学的処理の実践
[編集]F#のエコシステムの紹介
[編集]F#のベストプラクティス
[編集]F#のモジュールシステム
[編集]テスト駆動開発
[編集]F#のコーディングガイドライン
[編集]F#のコミュニティと学習リソース
[編集]附録
[編集]コードギャラリー
[編集]Ruby#コードギャラリーからの移植です。
エラトステネスの篩
[編集]エラトステネスの篩を、若干 F# らしく書いてみました。
- エラトステネスの篩
let eratosthenes n = let is_prime = Array.create (n + 1) true is_prime.[0] <- false is_prime.[1] <- false for i = 2 to int(sqrt(float n)) do if is_prime.[i] then let mutable j = i * i while j <= n do is_prime.[j] <- false j <- j + i let primes = is_prime |> Array.mapi (fun i prime -> if prime then i else -1) |> Array.filter (fun x -> x >= 0) primes printfn "%A" (eratosthenes 100)
- 実行結果
[|2; 3; 5; 7; 11; 13; 17; 19; 23; 29; 31; 37; 41; 43; 47; 53; 59; 61; 67; 71; 73; 79; 83; 89; 97|]
最大公約数と最小公倍数
[編集]最大公約数と最小公倍数を、若干 F# らしく書いてみました。
- 最大公約数と最小公倍数
let rec gcd2 m n = if n = 0 then m else gcd2 n (m % n) let gcd (ints : int[]) = Array.reduce gcd2 ints let lcm2 m n = m * n / (gcd2 m n) let lcm (ints : int[]) = Array.reduce lcm2 ints printfn "(gcd2 30 45) => %d" (gcd2 30 45) printfn "(gcd [|30; 72; 12|]) => %d" (gcd [|30; 72; 12|]) printfn "(lcm2 30 72) => %d" (lcm2 30 72) printfn "(lcm [|30; 42; 72|]) => %d" (lcm [|30; 42; 72|])
- 実行結果
(gcd2 30 45) => 15 (gcd [|30; 72; 12|]) => 6 (lcm2 30 72) => 360 (lcm [|30; 42; 72|]) => 2520
二分法
[編集]二分法を、若干 F# らしく書いてみました。
- 二分法
let rec bisection low high f = let x = (low + high) / 2.0 let fx = f x if abs fx < 1.0e-10 then x else let (newLow, newHigh) = if fx < 0.0 then (x, high) else (low, x) bisection newLow newHigh f let result1 = bisection 0.0 3.0 (fun x -> x - 1.0) printfn "%.16f" result1 let result2 = bisection 0.0 3.0 (fun x -> x * x - 1.0) printfn "%.16f" result2
- 実行結果
0.9999999999417920 1.0000000000291000
- 旧課程(-2012年度)高等学校数学B/数値計算とコンピューター#2分法の例を F# に移植しました。
逆ポーランド記法の解析と評価
[編集]逆ポーランド記法は、数式の演算子を後置記法で表現する方法です。通常の中置記法では演算子がオペランドの間に置かれますが、逆ポーランド記法では演算子がオペランドの後ろに置かれます。これにより、括弧や演算子の優先順位を考える必要がなくなり、計算機で容易に評価できる形式になります。
例えば、中置記法での式 3 + 4 * 5
は、逆ポーランド記法では 3 4 5 * +
と表現されます。この記法では、演算子が対象のオペランドに対して順番に適用されます。
let evaluateExpression (expression: string) = let tokens = expression.Split(' ') let rec evalStack (stack: int list) = function | [] -> stack | "+" :: tl -> match stack with | operand2 :: operand1 :: rest -> evalStack ((operand1 + operand2) :: rest) tl | _ -> failwith "Invalid expression: not enough operands for + operator" | "-" :: tl -> match stack with | operand2 :: operand1 :: rest -> evalStack ((operand1 - operand2) :: rest) tl | _ -> failwith "Invalid expression: not enough operands for - operator" | "*" :: tl -> match stack with | operand2 :: operand1 :: rest -> evalStack ((operand1 * operand2) :: rest) tl | _ -> failwith "Invalid expression: not enough operands for * operator" | "/" :: tl -> match stack with | operand2 :: operand1 :: rest when operand2 <> 0 -> evalStack ((operand1 / operand2) :: rest) tl | _ -> failwith "Invalid expression: division by zero or not enough operands for / operator" | num :: tl -> match System.Int32.TryParse(num) with | true, parsedNum -> evalStack (parsedNum :: stack) tl | _ -> failwith "Invalid expression: unexpected token" | _ -> failwith "Invalid expression: unexpected token" match evalStack [] (List.ofArray tokens) with | [result] -> result | _ -> failwith "Invalid expression: too many operands or operators" let expression = "5 3 2 * + 8 2 / -" let result = evaluateExpression expression printfn "Result: %d" result
このF#のコードは、与えられた数式を評価するための関数を作成しています。
- evaluateExpression 関数
この関数は、与えられた数式を受け取り、その数式を評価します。
expression
を空白文字で分割し、tokens
に格納します。evalStack
内の再帰関数を呼び出してtokens
を評価します。evalStack
関数は、スタックを操作して数式を評価します。トークンをチェックし、スタックに対して適切な操作を行います。- 演算子(
+
,-
,*
,/
)の場合は、スタックから必要な数のオペランドを取り出し、対応する演算を実行します。 - 数値の場合は、文字列を整数に変換し、スタックにプッシュします。
- 不正なトークンがある場合は、エラーメッセージをスローします。
- 演算子(
evalStack
の実行結果をevaluateExpression
に返します。
- 使用例
expression = "5 3 2 * + 8 2 / -"
のような数式を与えると、このコードはスタックベースの計算を行い、最終的な結果を返します。この例では、(5 + (3 * 2)) - (8 / 2)
を計算し、最終的な結果 7
を返しています。
このコードはトークンベースで数式を評価し、スタックを使用して演算を実行しています。