F# のデバッグ方法 #fsharp
F#のデバッグには、Visual Studioによる制限と、F#特有の、二種類の辛さが(C#に比べて)あります。本記事では、Visual Studio上での制限や、F#特有のデバッグ方法を紹介します。
また、Visual Studioしか知らないので、他の環境のデバッグについては、得意な人がきっと教えてくれます?
Visual Studio上での制限
Visual StudioでのF#のデバッグはMSDN(F# のデバッグ)に記事があります。特に注目したいのが、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ディレクティブで囲っておけば、リリース時には元のパイプライン演算子に置き換わります。
ただし、この方法では、スコープ内の全てのパイプライン演算子を上書きしてしまうので、止めたい位置で止めるのが難しくなります。
どの方法も一長一短なので、好きな方法を使えばいいと思います。