🔷

F# and F# tools update for Visual Studio 16.9(日本語訳)

2021/03/04に公開

はじめに

この記事は F# and F# tools update for Visual Studio 16.9 の日本語訳となります。
主に情報として必要なものを取り上げている都合上、元の記事の内容を一部省略しています。
また、意訳・誤訳等が含まれている可能性もありますので、予めご了承ください。

F# and F# tools update for Visual Studio 16.9

2020年11月のF# 5リリース以降、Visual StudioでのF# toolsの使いごこちを向上させる取り組みを行ってきました。
この記事では以下のカテゴリごとに主な改善点を紹介していきます。

  • Visual Studio上でのF#スクリプト(.NET 5)
  • F#用にVisual Studioへ追加された生産性向上のための機能
  • F# toolsのパフォーマンス改善
  • コアコンパイラの改善

1. Visual Studio上でのF#スクリプト(.NET 5)

Visual Studio 16.9では、.NET 5でF#スクリプトとFSIに追加された新機能へのサポートが行われました。
このサポート機能を有効化するには、

[ツール] > [オプション] > [F# Tools] > [F# Interactive] > [.Net Core スクリプトの使用]

True にする必要があります。

Use F# script tools

すでにF#インタラクティブウィンドウを開いていた場合は、一度閉じて再度開きなおす必要があります。
これでVisual Studioを使用して最新のF#スクリプトの実行や .NET Framework と互換性のないパッケージの依存関係を解決させることが可能となりました。

.Net Core スクリプトの使用 を有効化するとF#インタラクティブ上のコードは dotnet fsi を使用してF#コードを処理するようになり、すべてのF#スクリプトが .NET Core で実行した場合と同様の動作をするようになります。
つまりこれは以下のようなことが可能になったことを意味しています。

  • netstandard2.1 / netcoreapp3.1 / net5.0を対象とするパッケージは、Visual StudioのF#インタラクティブ上で #r nuget:... を指定することで読み込むことが可能
  • F#スクリプトで利用可能なライブラリセットが .NET Framework から .NET 5 に変更
  • .NET Framework を必要とするスクリプト(AppDomainsに依存しているなど)は、正常に動作しない場合がある

現状では意図的に .Net Core スクリプトの使用 を有効化する必要がありますが、将来的にはデフォルトで有効となる予定です。

2. Visual Studioへ追加された生産性向上に関する機能

関数呼び出しのためのシグネチャヘルプ

IntelliSenseには呼び出そうとする関数のシグネチャがどういったものなのか表示してくれるシグネチャヘルプやパラメータヘルプと呼ばれる機能があります。F#の場合、今まで ( キーを押したときに表示してくれていました。

今回のアップデートで 半角スペース キーを押した場合にも表示されるようになりました。
F#の関数はパラメータの区切りにスペース利用するため、この機能追加が長年の間、望まれていました。

また、この機能は |> などのパイプラインを使っていたり関数の部分適用をしていたりした場合、それらも含めて適したシグネチャヘルプを表示してくれます。
以下のサンプルでは、上記サンプルで使用した List.map にパイプラインで引数を部分適用させた場合のシグネチャヘルプを表示しています。部分適用されたパラメータ分のシグネチャが省略されていることがわかります。

新たに追加された13の文法に対する警告・エラー

(1) recキーワード漏れ

再帰的に定義された関数で rec キーワードが漏れている場合に追記するよう提案してくれるようになります。

また、相互再帰関数の場合にも同様の提案をしてくれるようになっています。

(2) C#ラムダ式からF#ラムダ式への変換

C#erがF#を利用する際によくやりがちなラムダ式の書き間違いをした場合に、Visual Studioがそれを検出して正しいラムダ式に変換してくれるようになります。

(3) F#ラムダ式のfunキーワード漏れ

F#のラムダ式に fun キーワードを書き忘れていた場合に、Visual Studioがそれを検出して追記してくれるようになります。

(4) 誤った使い方をしているreturnキーワードの削除

F#の return キーワードは コンピュテーション式 で利用する特別なキーワードであるため、それ以外で誤って使用している場合に、Visual Studioがそれを検出し削除してくれるようになります。

(5) レコード式から匿名レコード式への変換

名前付きレコード式の構文を使用しており、その式から対象のレコード型が推論できない場合に、匿名レコード式の構文を忘れている可能性があるとVisual Studioが判断し匿名レコード式の構文を使用するよう提案してくれるようになります。

(6) mutableな変数に対する誤った再代入法の修正

F#では再代入に <- を利用しますが、= を利用する言語を長らく使ってきているプログラマは無意識に F# でも = を利用してしまうことがあります。Visual Studioではそれを検出して、コード修正の提案をしてくれるようになります。

(7) 再代入を行っている場合に宣言をmutable宣言に修正

<- を利用して再代入を行っているにも関わらず、宣言時に mutable キーワードを忘れている場合があります。
Visual Studioではそれを検出して、mutable キーワードを追記してくれるようになります。

(8) ダウンキャストできない場合にアップキャストを提案

:?> または downcast を使用してダウンキャストできない場合に、Visual Studioはアップキャストと間違っている可能性があると判断しアップキャストを提案するようになります。

(9) =キーワード漏れ

レコード型/判別共用体/型エイリアスを定義するさいに = キーワードの入力漏れをしていた場合、Visual Studioがそれを検出し修正するように提案してくれるようになります。

(10) 同等性チェックに=を使用

F#では同等性チェックをする際に = を利用します。== を利用する他言語ユーザがF#でも == を使って同等性チェックをしようとしていた場合に、Visual Studioはそれを検出し修正するよう提案してくれるようになります。

(11) 否定するためにnotを使用

F#ではブール値を否定する場合には not を利用します。! を利用する他言語ユーザがF#でも ! を使って否定をしようとしていた場合に、Visual Studioはそれを検出し修正するよう提案してくれるようになります。

(12) 式をカッコで囲む

F#では、コードを明確にするためにカッコを使用しなければならないシナリオがいくつか存在しています。
Visual Studioではいくつかの考えられるシナリオを検出し、コードが明確になるようにカッコで囲むよう提案をしてくれるようになります。

ただし現状ではまだ考えられるすべてのケースを網羅しているわけではありません。
今後も検出範囲を拡大していく予定です。

(13) 減算とパラメータのあいまいさの修正

F# では values.Length -1values.Length - 1 では意味があいまいになります。
これは前者が values.Length に対して -1 という値をパラメータとして渡そうとしているように見え、後者が values.Length から 1 を引く減算をしようとしているように見えるためです。
今後、Visual Studioはコードを減算に変更するように提案します。

VSCodeでも新たに追加された13の文法に対する警告・エラーが導入されました

Chet Husk のおかげでVSCodeのIonideプラグインに同様の修正が含まれました。

3. F# toolsのパフォーマンス改善

IDE機能の応答性の向上

今回のリリースではF#言語サービスの調整を行いました。
現在F#言語サービスでは以下の2種類のリクエストについて区別しています。

  1. F#コードの構文に関するデータ
  2. F#コードセマンティクスに関するデータ

1つ目の「F#コードの構文に関するデータ」についてはF#型ェッカーに依存しておらず、2年以上前からこの種のリクエストについてはフリースレッド化されています。つまりバックグラウンドスレッドがユーザーコードの型チェックでビジー状態となっていても、別のバックグラウンドスレッドを使って構文解析を行い、そのデータに基づいて情報を処理することがきます。

2つ目の「F#コードセマンティクスに関するデータ」についてはF#の型チェッカーに依存しているため、型チェッカーの動作と連動しています。
F#は型推論機能があるため、ソースコードを変更するとプロジェクトやソリューション全体を通して何らかの型に影響を与える可能性があります。例えば判別共用体に新しいケースを追加したり、使用頻度の高い関数の戻り値の型を変更したりするとコードベース全体に影響を及ぼす可能性があります。
この動作の結果、型チェックされたデータを扱うF# toolsはF#コンパイラが型チェックを行う必要があるため影響を受けることになります。
しかし、たいていの場合は型を変更したからといって利用している箇所のすべてを変更することは稀です。いわゆる "既存の" データはほとんどの場合で正常に動作するものであり、F#言語サービスには最新のものかどうかを判定するためのキャッシュも存在しています。このことを考慮して、F#言語サービスではツール機能に対するリクエストの処理方法について以下の変更を加えました。

  1. 型チェック情報に最新のキャッシュが存在する場合はそのキャッシュデータを利用し、バックグラウンドでの型チェック処理を待たない
  2. 利用可能な最新のキャッシュがない場合、またはキャッシュが古いと判断された場合に型チェック処理を実行する
  3. 型プロバイダから型の更新をするリクエストがあった場合、型は常に最新でなければならないため、従来どおり型チェッカーと連動しての処理となる

上記の結果、大規模なコードベースで作業する際にツールチップやIntelliSense、その他の機能の応答性が向上しました。小規模なコードベースでは、すでにかなりの応答性があるため気付きにくいかもしれません。
型プロバイダを使用している場合には以前と同じ動作になります。

シグネチャファイルを使用したコードベースのパフォーマンスが向上

F#のシグネチャファイルは実装ファイルが実装するべきAPIのシグネチャを定義するためのファイルです。
シグネチャファイルは公開するAPIを制御するのに役立ち、利用者はシグネチャファイルに追加されたAPIのみ存在を知ることができます。

シグネチャファイルと実装ファイルのペアがどのようなものかの例を示します。

mymath.fsi : シグネチャファイル

module MyMath

/// Adds two numbers, x and y.
val add2: x: int -> y: int -> int

mymath.fs : 実装ファイル

module MyMath

let add2 x y = x + y

これは非常に単純な例ですが、add2関数のシグネチャがどういったものかについてシグネチャファイルで明示的に表現されていることがわかります。

大規模なコードベースで作業をしている場合には、実装ファイルごとにシグネチャファイルを作成することを強くオススメします。そうすることによりコードベース全体で公開されているAPIが意図どおりになっていることを保証させられます。
そしてVisual Studio 16.9からは、それらを使用するさいのパフォーマンスについて大きく2つの改善が加えられました。

まずは依存関係の階層を「上」に見ていく場合の最適化について見ていきます。
次のようなプロジェクトの順序を考えてみましょう。

Project1
|__file1.fsi
|__file1.fs
|__file2.fsi
|__file3.fs

file3.fsで作業している場合、シグネチャファイルのfile1.fsiとfile2.fsiが変更されていないかぎりF#コンパイラはそれらについての型チェック情報をキャッシュから再利用するように変更しました。シグネチャが変更されていないということは、つまりfile3.fsをいくら変更しようとも実装すべきAPI構成は一切変更されないことを意味しています。そのため、シグネチャファイルが変更されないかぎりは型チェックを再度実行する必要はありません。
その結果、F#言語サービスは型チェックに費やす時間が大幅に短縮され、型チェックツールに依存するIDE機能がの高速化が果たされました。

次にfile1.fsで作業している場合を考えてみましょう。
file1.fsのコードを編集してもfile1.fsiを更新しなかった場合、コンパイラが型チェックするファイルはfile1.fsだけとなります。これは先ほどと同じくシグネチャが変更されていないからです。
また、プロジェクトの残りの部分、すなわちfile2.fsiやfile3.fsについては最新のものだとみなします。その結果、IDEは依存関係の階層の「下」にあるものを型チェックする時間を大幅に削減することができます。

将来的にはVisual Studio内の既存の実装ファイルから署名ファイルを簡単に生成できる方法を提供する予定です。

4. コアコンパイラの改善

Visual Studio 16.9にはコード編集に役立つコンパイラの改善が含まれています。

誤ったXMLドキュメントコメントに対する警告

F# 5ではXMLドキュメントコメントを検証する警告を導入しました。

  • 不正なタグのチェック
  • XMLドキュメントコメントのパラメータ名がコードのパラメータと一致することをチェック
  • XMLドキュメントコメントが関数またはメソッドのシグネチャにないパラメータを参照していないことをチェック

たとえば以下のように / をつけ忘れている閉じタグがついた不正な形式のXMLドキュメントコメントに警告がでます。

また、パラメータ名がシグネチャと一致していない場合にも警告がでます。

この警告はライブラリ製作者から「非常に役立った」というフィードバックが多数寄せられたため、デフォルトで有効になっています。

シグネチャファイルと実装ファイルとの間でパラメータ名が一致しない場合の警告

シグネチャファイルと実装ファイルのパラメータ名が一致していることを検証する警告を導入しました。
次のようなシグネチャを考えます。

val myFunction: x: int -> y: int -> int

このとき実装ファイルのパラメータ名を xとy ではなく xとz などのようにした場合に警告が発生するようになりました。

クロージャを多用するコードの実行時パフォーマンスの向上

クロージャを多用するコードの実行時パフォーマンスを向上させました。
次のようなF#コードを考えてみます。

let mutable a = 0
let mutable res = 0

let f () = a <- a + 1; (fun x -> x + 1)

let g() = 
   for i in 0 .. 10000 do
      for j in 0 .. i do
        res <- f() res

g()

このコードは以前よりも約2倍の速度で実行されるようになりました。
これは、以前までF#コンパイラが実行時に特定のクロージャが呼び出されるたびに行っていた再割り当て処理を回避するようにしたためです。

今後について

ツールやコンパイラに対する改善作業は他にもいくつか検討をしています。例えば以下のような改善を検討しています。

  • コードベースにないもので「定義へ移動 (F12)」を呼び出したときに逆コンパイルされたソースが表示されるようにする
  • 型ヒントをインラインで表示させるキーコマンド
  • パラメータ名のヒントをインラインで表示させるキーコマンド
  • Visual Studio内からのF#シグネチャファイル生成の改善
  • F#インタラクティブのフレームワーク参照に依存するパッケージ読み込みのサポート

また、この他にも .NET 6 のリリースに合わせてF#言語の次のバージョンに何を含めるかについても同時に検討しています。
今後数か月以内にいくつかのエキサイティングな情報を共有したいと思っています。

それまでしばしお待ちください。

Discussion