F# のデバッグ方法 #fsharp

F#のデバッグには、Visual Studioによる制限と、F#特有の、二種類の辛さが(C#に比べて)あります。本記事では、Visual Studio上での制限や、F#特有のデバッグ方法を紹介します。

また、Visual Studioしか知らないので、他の環境のデバッグについては、得意な人がきっと教えてくれます?

Visual Studio上での制限

Visual StudioでのF#のデバッグMSDNF# のデバッグ)に記事があります。特に注目したいのが、3つ目の制限の、

デバッガーは F# 式を認識しません。 F# のデバッグ中にデバッガー ウィンドウまたはダイアログ ボックスに式を入力するには、式を C# の構文に変換する必要があります。 F# の式を C# に変換するときは、C# では等価を示す比較演算子として == を使用しますが、F# では単一の = を使用することに注意してください。

です。ウォッチ式や、イミディエイトウィンドウでも同様に、C#の式が必要になります。F#の構文をC#へ変換する方法を紹介します。

モジュールに定義された関数、ラムダ式の呼び出し

関数呼び出しはメソッド呼び出しと同様の方法で呼び出します。let f x y z =...という関数は、F#だとf 1 2 3で呼び出せますが、C#へはf(1, 2, 3)と変換します。カリー化されている場合でも、3引数のメソッドとして扱えます。実は、F#の関数は、C#からはメソッドとして見えるんです。

ローカル関数、ラムダ式の呼び出し

ローカル関数は関数値Microsoft.FSharp.Core.OptimizedClosures.FSharpFuncとして扱われます。f 1 2 3と呼び出したい場合は、f.Invoke(1).Invoke(2).Invoke(3)と変換します。

また、モジュールに定義された関数でも、関数値の場合は、Invokeメソッドで呼び出さなければなりません。 見分ける方法は、関数の場合はシグネチャ'a -> 'b -> 'cとなっており、関数値の場合は、('a -> 'b -> 'b)と括弧で囲われます。

演算子の呼び出し

演算子は、たとえば、let (++) x y = ...という演算子は、3++4とは書けませんop_PlusPlus(3, 4)と変換します。

MSDN演算子のオーバーロード (F#))の定義にしたがって、演算子メソッド名に変換します。そして、メソッドとして呼び出します。

変換例を幾つか挙げます。

演算子 メソッド
+ op_Addition
++ op_PlusPlus
+- op_PlusMinus

また、型に定義された演算子は、staticメソッドとして呼び出します。

ブレークポイント

基本的にどこでも置けます。ラムダ式の中や、コンピュテーション式の中でも大丈夫です。 ただし、ラムダ式の中から外側の変数の値は見えないようです。コンピュテーション式もメソッド呼び出しとラムダ式に展開されるので、同様です。

シャドーイングした場合は、全てシャドーイング後の値になります。

コンピュテーション式の中でステップインすると、C#のクエリ式のように、コンピュテーション式の実装の方に迷い込んでしまうので、注意しましょう。

パイプライン演算子の間にブレークポイント

F#ではパイプライン演算子|>をよく使います。ただし、簡単には途中の値を覗けません。覗く方法を幾つか紹介します。

式を変形する

簡単ですが面倒くさい方法です。計算過程を変数に束縛します。

f x |> g

上記式を次のように変換します。デバッグが終わったら元に戻します。

let a = f x
g a

この方法は確かに値を覗けるのですが、修正が大きすぎてミスりそうです。ですが、もう少し修正が小さい方法があります。

ID関数

let bp x = xのような関数を定義し、デバッグ時に見たいところに差し込みます。ブレークポイントbp関数の中に置いておきます。 先ほどと同じように、デバッグが終わったら削除します。

let bp x = x (* ここにブレークポイント *)
f x |> bp |> g

もしくは、直接ラムダ式を書き、ラムダ式の中にブレークポイントを置きます。

f x |> (fun x -> x) |> g

パイプライン演算子を上書きする

今までに挙げた方法では、コードを書き換える必要がありました。The trace-mode pipeline operator | F# Snippetsで見つけたのですが、パイプライン演算子を上書きし、演算子の中にブレークポイントを置く方法があります。

let (|>) x f = let result = f x in x (* ここにブレークポイント *)
f x |> g

そして、演算子を#ifディレクティブで囲っておけば、リリース時には元のパイプライン演算子に置き換わります。

ただし、この方法では、スコープ内の全てのパイプライン演算子を上書きしてしまうので、止めたい位置で止めるのが難しくなります。

どの方法も一長一短なので、好きな方法を使えばいいと思います。