次の方法で共有


F# の基礎

関数型プログラミングの .NET 開発者向け概要

Chris Marinos

コード サンプルのダウンロード

そろそろ、F# についてお耳に届いているのではないでしょうか。F# は、最も新しく Microsoft Visual Studio の言語ファミリに追加された言語です。わかりやすい構文、強力なマルチスレッド機能、他の Microsoft .NET Framework 言語との流動性のある相互運用性など、F# を学ぶ魅力的な理由は数多くあります。ただし、F# には、機能を使用する前に理解しておくべき新しい重要な概念があります。

別のオブジェクト指向言語や、Ruby または Python といった動的言語の学習を始めるときは、ほとんど説明の必要はありません。こうした言語の場合、ほとんどの用語を既に知っているため、新しい構文を学ぶだけですから。しかし、F# はそういうわけにはいきません。F# は関数型プログラミング言語で、想像以上に多くの新規用語が登場します。さらに、もともと学術分野で使用されていたため、こうした新規用語の定義の理解に苦しむことがあります。

さいわい、F# は学術言語として設計されたのではありません。F# の構文では、.NET 開発者が使い慣れたオブジェクト指向や命令型のスタイルへのサポートを残したまま、関数型の技法を取り入れ、より優れた新しい方法で問題を解決できるようにしています。他の .NET 言語とは異なり、F# のマルチパラダイム構造は、ユーザーが解決を試みている問題に最も適したプログラミング スタイルを自由に選べることを意味しています。F# の関数型プログラミングは、簡潔かつ強力なコードを記述して、実質的なソフトウェアの問題を解決することを目的としています。また、高次関数や関数合成などの技法を使用して、強力で理解しやすい動作を作成することや、水面下の複雑性を排除して、コードの理解、テスト、および並列処理を簡単にすることも目的としています。

ただし、F# のこれらのすばらしい機能をすべて活用するのであれば、基礎をきちんと理解しておく必要があります。今回の記事では、.NET 開発者が既に精通している用語を使って、こうした概念を紹介します。また、既存のコードや、これまでプログラミングを行ってきた手法に適用できる関数型プログラミング技法をいくつか説明します。この記事を読み終えるころには、関数型プログラミングについての十分な知識を持って、Visual Studio 2010 で F# を本格的に使い始めることができるでしょう。

関数型プログラミングの基礎

ほとんどの .NET 開発者は、何が関数型プログラミングでないかを理解すれば、関数型プログラミングが理解しやすくなります。関数型プログラミングの対極をなすと考えられるプログラミング スタイルは、命令型プログラミングです。主なプログラミング言語の大半は命令型なので、命令型プログラミングはおそらく最もなじみのあるプログラミング スタイルです。

関数型プログラミングと命令型プログラミングは、非常に根本的なレベルで異なります。次の最も単純なコードでもその違いがわかります。

int number = 0;
number++;

このコードは明らかに変数を 1 インクリメントしています。これはそれほど興味深いことではありませんが、この問題を解決できる方法として次の例を考えてみましょう。

const int number = 0;
const int result = number + 1;

変数 number を 1 インクリメントしていますが number 自体は変更していません。コンパイラは定数値の変更を許可しないため、もう 1 つの定数として result を用意してそこに格納しています。定数値はいったん定義すると変更できないため、定数は変更不可であると表現します。逆に、1 つ目の例の number 変数は値を変更できるため、変更可能であると表現できます。この 2 つのアプローチが、命令型プログラミングと関数型プログラミングの根本的な違いの 1 つを示しています。つまり、命令型プログラミングでは変更可能な変数が多用されるのに対し、関数型プログラミングでは変更不可の値が使用されます。

.NET 開発者であれば、おそらくほとんどの方が先ほどの例の number と result は変数だと言われるでしょう。しかし、関数型プログラミングを行う場合は、この点にもう少し注意が必要です。いずれにせよ、定数と変数という考え方は混乱を招くだけです。関数型プログラミングを行う場合、この number や result を単に値と表現します。"変数" という用語は、変更可能なオブジェクトのために取っておきます。これらの用語は関数型プログラミングに限らず、関数型のスタイルでプログラミングする場合、さらに重要になります。

この区別はささいなことのように思えるかもしれませんが、関数型プログラミングを非常に強力にしている多くの概念の基礎をなしています。たちの悪い多くのバグの根本原因は、変更可能な変数にあります。この後説明しますが、変更可能な変数は、コードの別の部分に暗黙の依存関係をもたらすため、特に同時実行に関連して多くの問題が生じることになります。これに対して、変数を変更不可にすると、複雑さが大きく軽減されます。変数を変更不可にすると、関数を値として使用する技法や複合プログラミングを使用できるようになります。これについても、この後詳しく説明します。

ここまでの説明で関数型プログラミングに懐疑的になっているとしても心配はいりません。それが普通です。命令型プログラミングを行ってきたプログラマの大半は、値を変更できなければ何の役にも立たないと考えてしまいがちです。では、次の例について考えてみましょう。

string stringValue = "world!";
string result = stringValue.Insert(0, "hello ");

Insert 関数は "hello world!" という文字列を作成していますが、元の文字列値は変更しません。.NET では文字列が変更不可だからです。.NET Framework の設計者が文字列に関数型の手法を使用したのは、その方が適切なコードを容易に記述することができたためです。文字列は、(整数や DateTime などの他の基本型と並んで) .NET Framework で最も広く使用されるデータ型の 1 つなので、実は思っている以上に関数型プログラミング手法を行ってきた可能性があります。

F# を使用する

F# は Visual Studio 2010 に付属しており、最新バージョンは msdn.microsoft.com/vstudio で入手できます。Visual Studio 2008 を使用する場合は、F# Developer Center (msdn.microsoft.com/fsharp、英語) から F# のアドインをダウンロードできます。このページには、Mono のインストール手順もあります。

F# では、F# Interactive (F# 対話型) という新しいウィンドウが Visual Studio に追加されました。このウィンドウでは、当然ながら、F# コードを対話的に実行できます。これはデバッグ モードでなくても使用できる、より強力なイミディエイト ウィンドウと考えてください。Ruby や Python を使い慣れている方は、F# Interactive を Read-Evaluate-Print Loop (REPL) とお考えください。F# について学習し、コードをすばやく試すのに便利なツールです。

今回の記事では、F# Interactive を使用してサンプル コードをコンパイルして実行すると何が行われるのかについて説明します。Visual Studio でコードを強調表示して、Alt キーを押しながら Enter キーを押すと、コードが F# Interactive に送信されます。これを確認するために、F# での簡単な加算の例を示します。

let number = 0
let result = number + 1

このコードを F# Interactive で実行すると、次のような結果が表示されます。

val number : int = 0
val result : int = 1

val という用語から、number と result はいずれも、変更可能な変数ではなく、変更不可の値であることが想像できるでしょう。このことは、F# の代入演算子 <- を使用するとわかります。

> number <- 15;;

  number <- 15;;
  ^^^^^^^^^^^^

stdin(3,1): error FS0027: This value is not mutable
>

関数型プログラミングは変更不可であることに基づいているため、このエラーは理にかなっています。let キーワードは、名前と値の間のバインドを変更不可にするために使用されます。C# の用語で表現すれば、F# ではすべてが既定で const になります。必要であれば、変数を変更可能にすることもできますが、そのことは明示的に示さなければなりません。既定値は、命令型言語で使い慣れた既定値とは正反対になります。

let mutable myVariable = 0
myVariable <- 15

型の推論と空白文字の識別

F# では、型を指定しないで変数や値を宣言できるため、動的言語だと思われるかもしれませんが、そうではありません。F# は、C# や C++ と同様の静的言語であると理解しておくことが重要です。ただし、F# には強力な型の推論システムがあるため、オブジェクトの型を指定しなくてもかまわない局面が多数存在します。これにより、静的言語のタイプ セーフ性を残しつつ、シンプルで簡潔な構文を使用することができます。

このような型の推論システムは命令型言語にはあまり見受けられませんが、関数型プログラミングに直接関係するシステムでもありません。ただし、F# について学習する場合は、型の推論は理解しておくべき重要な概念です。さいわい、C# 開発者は、var キーワードのおかげで、基本的な型の推論には既になじみがあるはずです。

// Here, the type is explictily given
Dictionary<string, string> dictionary = 
  new Dictionary<string, string>();

// but here, the type is inferred
var dictionary = new Dictionary<string, string>();

どちらの C# コード行でも、Dictionary<string, string> として静的に型指定して新しい変数を作成していますが、後者は var キーワードを使用して、変数の型を推論するようコンパイラに指示しています。F# では、この考え方が 1 つ上のレベルに引き上げられます。たとえば、次の F# の add 関数を見てみましょう。

let add x y =
    x + y
    
let four = add 2 2

上記のコードには型の指定がまったくありませんが、F# 
Interactive では静的な型指定が明らかになります。

val add : int -> int -> int
val four : int = 4

この矢印については後で詳しく説明しますが、今の時点では、add が 2 つの int 引数を受け取るよう定義されていることと、four が int 値であることを意味していると解釈できます。F# コンパイラは、add と four がどのように定義されたかに基づいてこれを推論しています。この推論を行うためにコンパイラが使用する規則については、この記事の目的を超えてしまうためここでは触れませんが、興味があれば F# Developer Center で詳しく学習できます。

型の推論は F# が不要なコードを減らす方法の 1 つですが、F# には add 関数の本文や戻り値を示す中かっこやキーワードもないことがわかります。これは、F# が既定で空白文字を識別する言語であるためです。F# では、インデントを利用して関数の本文を示し、戻り値は関数の最終行に配置します。型の推論と同じように、空白文字の識別は関数型プログラミングに限ったしくみではありませんが、F# を使用するためにはこの考え方も知っておく必要があります。

副作用

関数型プログラミングでは変更可能な変数ではなく変更不可の値が使用されることから、関数型プログラミングと命令型プログラミングが異なることがわかりましたが、それだけではあまり実用的とは言えません。ここでは、副作用について説明します。

命令型プログラミングでは、関数からの出力が、入力引数とプログラムの現在状態によって決まります。関数型プログラミングでは、関数からの出力は入力引数のみに依存します。つまり、同じ入力値を使用して関数を複数回呼び出せば、必ず同じ出力値が返されます。命令型プログラミングでは、図 1 に示すような副作用のため、このことが当てはまりません。

図 1 変更可能な変数の副作用

public MemoryStream GetStream() {
  var stream = new MemoryStream();
  var writer = new StreamWriter(stream);
  writer.WriteLine("line one");
  writer.WriteLine("line two");
  writer.WriteLine("line three");
  writer.Flush();
  stream.Position = 0;
  return stream;
}

[TestMethod]
public void CausingASideEffect() {
  using (var reader = new StreamReader(GetStream())) {
    var line1 = reader.ReadLine();
    var line2 = reader.ReadLine();

    Assert.AreNotEqual(line1, line2);
  }
}

最初の ReadLine 呼び出しでは、改行までのストリームが読み取られます。次に、ReadLine が改行までのすべてのテキストを返します。こうした手順の間に、ストリーム位置を表す変更可能な変数が更新されます。これが本来の機能とは無関係の副作用です。ReadLine の 2 回目の呼び出しでは、位置を表す変更可能な変数の値が変わっているため別の値を返します。

ここで、副作用による最も大きな影響の 1 つについて見てみましょう。まず、単純な PiggyBank クラスと、このクラスで使用するいくつかのメソッドについて考えてみます (図 2 参照)。

図 2 変更可能な PiggyBank

public class PiggyBank{
  public PiggyBank(int coins){
    Coins = coins;
  }

  public int Coins { get; set; }
}

private void DepositCoins(PiggyBank piggyBank){
  piggyBank.Coins += 10;
}

private void BuyCandy(PiggyBank piggyBank){
  if (piggyBank.Coins < 7)
    throw new ArgumentException(
      "Not enough money for candy!", "piggyBank");

  piggyBank.Coins -= 7;
}

貯金箱にコインが 5 枚が入っているとすると、DepositCoins を呼び出してから BuyCandy を呼び出せば問題ありません。しかし、呼び出しの順序を逆にすると、例外が発生します。

// this works fine
var piggyBank = new PiggyBank(5);

DepositCoins(piggyBank);
BuyCandy(piggyBank);

// but this raises an ArgumentException
var piggyBank = new PiggyBank(5);

BuyCandy(piggyBank);
DepositCoins(piggyBank);

BuyCandy 関数と DepositCoins 関数はいずれも、副作用によって貯金箱の状態が更新されます。つまり、関数の動作が貯金箱の現在状態によって異なります。コインの枚数が変更可能なため、関数の実行順序が重要になります。つまり、これらの 2 つのメソッドでは暗黙のうちにタイミングという依存関係が存在することになります。

では、コインの枚数を読み取り専用にして、変更不可のデータ構造のシミュレーションを行ってみましょう。図 3 では、BuyCandy と DepositCoins が既存の PiggyBank を更新するのではなく、新しい PiggyBank オブジェクトを返すようになっています。

図 3 変更不可の PiggyBank

public class PiggyBank{
  public PiggyBank(int coins){
    Coins = coins;
  }

  public int Coins { get; private set; }
}

private PiggyBank DepositCoins(PiggyBank piggyBank){
  return new PiggyBank(piggyBank.Coins + 10);
}

private PiggyBank BuyCandy(PiggyBank piggyBank){
  if (piggyBank.Coins < 7)
    throw new ArgumentException(
      "Not enough money for candy!", "piggyBank");

  return new PiggyBank(piggyBank.Coins - 7);
}

先ほどと同様に、DepositCoins の前に BuyCandy を呼び出そうとすると、引数の例外が返されます。

// still raises an ArgumentException
var piggyBank = new PiggyBank(5);

BuyCandy(piggyBank);
DepositCoins(piggyBank);

しかし今回は、順序を逆にしても同じ結果になります。

// now this raises an ArgumentException,  too!
var piggyBank = new PiggyBank(5);

DepositCoins(piggyBank);
BuyCandy(piggyBank);

ここでは、コインの枚数は変更不可で、BuyCandy と DepositCoins は入力引数のみに依存します。したがって、関数の実行順序には関係なく、結果は同じになります。タイミングという暗黙の依存関係がなくなりました。しかし、BuyCandy の実行を成功させたいので、BuyCandy の結果が DepositCoins の出力によって決まるようにしなければなりません。そこで、次のように、依存関係を明確にする必要があります。

var piggyBank = new PiggyBank(5);
BuyCandy(DepositCoins(piggyBank));

影響が広範囲に及ぶ場合には、この違いは捕らえにくくなります。変更可能な状態の共有と暗黙の依存関係は、命令型コードで発生する最もひどいバグの原因になることがあり、命令型言語でマルチスレッド処理が非常に難しくなる原因でもあります。関数の実行順序について考慮しなければならない場合は、厄介なロック メカニズムを使用して、実行順序が変わらないようにする必要があります。純粋な関数型プログラムでは、副作用やタイミングによる暗黙の依存関係がないので、関数の実行順序は重要ではありません。つまり、ロック メカニズムやその他のエラーが発生しやすいマルチスレッド処理技法を懸念する必要がなくなります。

主にマルチスレッド処理を簡単に実行できるという理由から、最近、関数型プログラミングに注目が集まっていますが、他にも関数型のスタイルでプログラミングを行うメリットがあります。たとえば、副作用のない関数を使用すれば、各関数が入力引数のみに依存するためテストが容易になり、他のセットアップ関数のロジックに暗黙のうちに依存することがなくなるためメンテナンスが容易になります。また、副作用のない関数は、コード量が少なくなり、組み合わせが容易になります。コードの組み合わせについては、この後説明します。

F# では、関数の副作用ではなく、関数の結果値を評価することに重点を置きます。命令型言語では、通常、処理を実行するために関数を呼び出しますが、関数型言語では結果を得るために関数を呼び出します。このことは、F# の if ステートメントを見るとわかります。

let isEven x =
    if x % 2 = 0 then
        "yes"
    else
        "no"

F# では、関数の最終行が戻り値になりますが、この例では関数の最終行が if ステートメントになっています。これは、コンパイラが技巧を凝らしているわけではありません。F# では、次のように if ステートメントも値を返すよう設計されています。

let isEven2 x =
    let result = 
        if x % 2 = 0 then
            "yes"
        else
            "no"
    result

result 値は文字列型で、if ステートメントに直接割り当てられます。これは、C# で条件演算子が機能する方法に似ています。

string result = x % 2 == 0 ? "yes" : "no";

条件演算子は、副作用の原因になることよりも、値を返すことに重点が置かれています。より関数型に近い手法と言えます。一方、C# の if ステートメントは結果を返さないため、命令型の要素の強い手法と言えます。命令型の手法はすべて、副作用の原因になります。

関数を構成する

副作用のない関数のメリットについていくつか確認したので、F# で関数を使用してその可能性を存分に発揮する準備が整いました。まず、C# のコードで、0 ~ 10 までの数字を二乗します。

IList<int> values = 0.Through(10).ToList();

IList<int> squaredValues = new List<int>();

for (int i = 0; i < values.Count; i++) {
  squaredValues.Add(Square(values[i]));
}

Through と Square というヘルパー メソッドを除けば、ごく標準的な C# コードです。優秀な C# 開発者であれば、foreach ループではなく for ループを使用していることを不快に思うかもしれません。事実、そのとおりでしょう。C# のような近代言語では、抽象化として foreach ループを提供し、明示的なインデクサーを不要にすることで列挙体の処理を簡単にしています。この目標はうまく達成されていますが、図 4 のコードについて考えてみてください。

図 4 foreach ループの使用

IList<int> values = 0.Through(10).ToList();

// square a list
IList<int> squaredValues = new List<int>();

foreach (int value in values) {
  squaredValues.Add(Square(value));
}

// filter out the even values in a list
IList<int> evens = new List<int>();

foreach(int value in values) {
  if (IsEven(value)) {
    evens.Add(value);
  }
}

// take the square of the even values
IList<int> results = new List<int>();

foreach (int value in values) {
  if (IsEven(value)) {
    results.Add(Square(value));
  }
}

この例の foreach ループはそれぞれ同じように見えますが、各ループの本文でわずかに異なる操作が実行されています。命令型プログラミングではこのようなコードは慣用的に使われてきたため、コードが重複しても問題はないと考えてきました。

関数型のプログラミングでは、アプローチが異なります。foreach ループのような抽象化を作成してリスト全体を処理するのではなく、副作用のない関数を使用します。

let numbers = {0..10}
let squaredValues = Seq.map Square numbers

この F# コードでも一連の数字が二乗されますが、この処理は高次関数を使用して実行されます。高次関数とは、単に、別の関数を入力引数として受け取る関数のことです。この場合は、Seq.map 関数が、Square 関数を引数として受け取ります。Seq.map 関数は、一連の numbers の各数字に Square 関数を適用して、一連の二乗された数字を返します。多くの人々が「関数型プログラミングでは関数をデータとして使用する」と言う理由は、高次関数にあります。つまり、高次関数では、関数をパラメーターとして使用したり、整数や文字列などのように、関数を値や変数に代入したりできます。C# の用語で言えば、デリゲートやラムダ式の概念に非常によく似ています。

高次関数は、関数型プログラミングを非常に強力にする技法の 1 つです。高次関数を使用して、foreach ループ内の重複するコードを分離し、スタンドアロンの副作用のない関数にカプセル化することができます。これらのカプセル化された各関数は、foreach ループ内のコードが処理することになっていた 1 つの小さな操作を実行します。これらの関数には副作用がないため、これらの関数を組み合わせて、foreach ループと同じ処理を実行する、よりわかりやすく管理しやすいコードを作成することができます。

let squareOfEvens = 
    numbers
    |> Seq.filter IsEven
    |> Seq.map Square

このコードで唯一紛らわしい部分は、|> 演算子でしょう。この演算子を使用すると、関数への引数の順序を変更して、最後の引数が最初に目に入るようにできるため、コードがよりわかりやすくなります。定義は非常に単純です。

let (|>) x f = f x

|> 演算子なしでは、squareOfEvens コードは次のようになります。

let squareOfEvens2 = 
  Seq.map Square (Seq.filter IsEven numbers)

LINQ をご存じであれば、この方法で高次関数を使用するときに、非常になじみがあるように思えるはずです。これは、LINQ が関数型プログラミングと深い関連があるためです。実際に、LINQ のメソッドを使用すると、偶数を二乗する問題を C# に簡単に変換できます。

var squareOfEvens =
  numbers
  .Where(IsEven)
  .Select(Square);

これは、次に示す LINQ クエリ構文に変換されます。

var squareOfEvens = from number in numbers
  where IsEven(number)
  select Square(number);

C# コードや Visual Basic コードで LINQ を使用すると、関数型プログラミングのいくつかの機能を日常的に活用できます。これは、関数型プログラミング技法を学ぶ優れた方法です。

高次関数を日常的に使用するようになると、最終的には、非常に特殊な短い関数を作成して、高次関数に渡す必要があるという状況に遭遇するでしょう。関数型プログラミングでは、ラムダ関数を使用してこの問題を解決します。ラムダ関数とは、名前を指定しなくても定義できる関数です。通常、ラムダ関数は短く、非常に特殊な方法で使用されます。たとえば、ラムダ関数を使用して偶数を二乗するもう 1 つの方法を次に示します。

let withLambdas =
    numbers
    |> Seq.filter (fun x -> x % 2 = 0)
    |> Seq.map (fun x -> x * x)

このコードと先ほどのコードの唯一の違いは、Square と IsEven がラムダ関数として定義されている点です。F# では、fun キーワードを使用してラムダ関数を宣言します。ラムダ関数は、1 度だけ使用する関数を宣言する場合にのみ使用します。ラムダ関数は、定義された範囲外では簡単に使用できません。このような理由から、Square と IsEven は多くの状況に役立つため、ラムダ関数には適していません。

カリー化と部分適用

F# を使い始めるのに必要な基礎はほぼすべて学習しましたが、把握しておくべき考え方がもう 1 つあります。先ほどの例では、F# Interactive の |> 演算子と型シグネチャにある矢印はいずれも、カリー化と呼ばれる概念に関係します。

カリー化とは、多数の引数を受け取る関数を一連の関数に分割して、各関数が 1 つの引数を受け取り、最終的に、元の関数と同じ結果を生成することを指します。.NET 開発者にとって、カリー化は (特に、部分適用と混同されることが多いため)、この記事の中で最も取り組みがいがあるトピックでしょう。次の例では、実行中のカリー化と部分適用の両方を確認できます。

let multiply x y =
    x * y
    
let double = multiply 2
let ten = double 5

ほとんどの命令型言語とは異なる動作がすぐにわかるはずです。2 つ目のステートメントでは、2 つの引数を受け取る関数に 1 つの引数を渡すことによって、double という新しい関数を作成しています。その結果、関数は 1 つの int 引数を受け取り、x の値に 2、y の値に受け取った引数を指定して multiply を呼び出した場合と同じ出力を生成します。動作の点から見れば、次のコードと同じになります。

let double2 z = multiply 2 z

多くの場合、間違って、multiply がカリー化されて double が形成されていると考えます。しかし、これは部分的にしかあっていません。multiply 関数はカリー化されています。ただし、F# の関数は既定でカリー化されるため、multiply 関数がカリー化されるのは定義されるときです。double 関数が作成されるときには、multiply 関数が部分適用されていると言う方が正確です。

これらの手順について詳しく見ていきましょう。カリー化により、多数の引数を受け取る関数が一連の関数に分割され、各関数が 1 つの引数を受け取り、最終的に、元の関数と同じ結果が生成されます。F# Interactive によると、multiply 関数の型シグネチャは次のようになります。

val multiply : int -> int -> int

ここまでは、multiply は 2 つの int 引数を受け取り、int 型の結果を返す関数であるとして、コードを解読していました。では、実際に行われる処理について説明しましょう。multiply 関数は、実際には、一連の 2 つの関数です。1 つ目の関数が 1 つの int 引数を受け取って、もう 1 つの関数を返し、最終的に x を特定の値にバインドします。この関数は、y にバインドする値と見なすことができる int 引数を受け取ります。この 2 つ目の関数を呼び出したら、x と y の両方がバインドされ、結果的に、double の本文で定義されるような x と y が生成されます。

double を作成するために、multiply 関数のチェーンに含まれる 1 つ目の関数が評価されて、multiply が部分適用されます。結果として出力される関数に、double という名前が指定されます。double が評価されるときに、その引数と部分適用された値を使用して、結果が作成されます。

F# と関数型プログラミングを使用する

F# と関数型プログラミングの使い始めるにあたって用語を十分学習してきたので、次に実行する作業の選択肢はたくさんあります。

たとえば、F# Interactive を使用して、F# コードについて調べ、F# スクリプトをすばやく構築できます。また、F# Interactive は、ヘルプ ファイルや Web 検索を使用することなく、.NET ライブラリに含まれる関数の動作に関する日常的な疑問を検証する際にも役立ちます。

F# は、複雑なアルゴリズムを表現するのに優れているため、アプリケーションの複雑なの部分を F# のライブラリにカプセル化して、他の .NET 言語から呼び出せるようにすることができます。これは、工学技術アプリケーションやマルチスレッド処理を行う場合に特に役に立ちます。

最後に、日常の .NET 開発で、F# コードを記述しないで、関数型プログラミング技法を使用することもできます。たとえば、for ループや foreach ループではなく、LINQ を使用します。また、デリゲートを使用して、高次関数の作成を試みます。命令型プログラミングの可変性や副作用を制限します。関数型のスタイルでコードを記述し始めたら、もっとたくさん F# コードを記述したいと感じるようになるでしょう。

Chris Marinos は、ミシガン州のアナーバーに拠点を置く SRT Solutions 社のソフトウェア コンサルタントです。F#、関数型プログラミングなどの興味深いトピックについては、アナーバー周辺のイベントで Chris の講演を聞いたり、彼のブログ (srtsolutions.com/blogs/chrismarinos、英語) で確認したりできます。

この記事のレビューに協力してくれた技術スタッフの Luke Hoban に心より感謝いたします。