1 ポイント 投稿者 GN⁺ 2024-11-11 | 1件のコメント | WhatsAppで共有

-.NET 9に含まれるF# 9は、null許容参照型とコンパイラ診断の改善により、C#/.NET相互運用で生じる安全性の問題を減らす

  • 判別共用体の.Is*プロパティ、boolを返す部分アクティブパターン、空のcomputation expressionなどにより、日常的なF#構文がより簡潔になる
  • FSharp.Coreはコレクションのランダム関数とC#コレクション式のサポートを追加し、F#のイミュータブルコレクションを他の.NETコードからも使いやすくする
  • コンパイラは、誤った属性の使用、65,520個超のILメソッド、private memberの可視性といった問題を、より早い段階で明らかにする
  • equalityチェックと整数範囲、list・array comprehensionの最適化により、一部のループは1.25×〜8×、一部の配列comprehensionは最大**10×**高速化する

.NET 9で提供されるF# 9

  • F# 9には、プログラムをより安全で、堅牢で、高性能にするための変更が含まれる
  • .NET 9で利用でき、最新の.NET SDKは.NETダウンロードページから入手できる
  • 主な変更はF# open source code repositoryで開発された

言語機能の変化

  • null許容参照型

    • F#はnullを避けるよう設計されているが、C#で書かれた.NETライブラリと一緒に使うとnullが入ってくることがある
    • F# 9は、string | nullのようにnullが有効な参照型を型安全に表現する
    • stringnullを入れたり、string | nullの値から直接.Lengthにアクセスしたりすると、null可能性の警告が発生する
    • パターンマッチでnullケースを先に処理すると、その後のバインディングは非null値として扱われる
    • ジェネリックコードでnullを返すには、'T : not structのような参照型制約が必要になる
    • 詳細はNullable Reference Types in F# 9で確認できる
  • 判別共用体の.Is*プロパティ

    • 判別共用体(discriminated union)は、各ケースに対して自動生成される**.Is*プロパティ**を持つ
    • たとえばContact型にEmailPhoneケースがあれば、person.contact.IsEmailのように特定ケースかどうかを確認できる
    • これまでは同じチェックのために、match式でEmail _ -> true | _ -> falseのようなコードを書く必要があった
  • 部分アクティブパターンのbool返却

    • 部分アクティブパターン(partial active pattern)は、従来はマッチ成功時にSome ()、失敗時にNoneを返す必要があった
    • F# 9では**bool返却**も許可される
    • 大文字小文字を無視する文字列マッチングの例では、String.Equals(..., StringComparison.OrdinalIgnoreCase)の結果をそのまま返せる
  • 引数がある場合は拡張メソッドを優先

    • 一部の.NETライブラリでは、型の固有プロパティと同名の拡張メソッドが定義されている
    • F# 9はこのパターンに合わせ、引数が渡された場合、型チェック失敗ではなく拡張メソッドとして解決する
    • 例では、FooXプロパティと同名の拡張メソッドX(f: Foo, i: int)f.X(1)の形で呼び出し、プロパティ設定と呼び出しチェーンを行える
  • 空のcomputation expression

    • F# 9は空のcomputation expressionsをサポートする
    • seq { }は空のシーケンスを作り、HTML DSLのようなコードではp { }のような空ブロックを表現できる
    • 空のcomputation expressionはビルダーの**Zeroメソッド**呼び出しにつながる
    • 以前のbuilder { () }より自然な構文である

ハッシュディレクティブとF# Interactiveの改善

  • 文字列以外のハッシュディレクティブ引数

    • コンパイラのハッシュディレクティブは、従来は引用符で囲んだ文字列引数しか許可していなかった
    • F# 9では任意の型の引数を受け取れる
    • #nowarn "0070"の代わりに#nowarn 0070#time "on"の代わりに#time onのように書ける
  • F# Interactiveの拡張された#help

    • F# Interactiveの#helpディレクティブは、オブジェクトや関数のドキュメントをREPL上で表示する
    • 引数は引用符なしで渡せる
    • たとえば#help List.map;;は、説明、パラメーター、戻り値、例、完全修飾名、アセンブリ情報を表示する
    • 詳細はEnhancing #help in F# Interactive blog postで確認できる
  • #nowarnFSプレフィックスを許可

    • 従来は#nowarn "FS0057"のように書くと、警告番号が合っていてもInvalid warning number 'FS0057'エラーが発生していた
    • F# 9ではFSプレフィックスがあっても警告番号として受け付けられる
    • #nowarn 57#nowarn 0057#nowarn FS0057、文字列形式の"57""0057""FS0057"がすべて動作する
    • プロジェクト内では同じスタイルを維持するのが望ましい

コンパイラの安全性と診断の改善

  • 誤った[<TailCall>]位置への警告

    • F# 9は、[<TailCall>]属性が不適切な位置に付いていると警告を出す
    • 非再帰関数、letバインディング値、再帰letバインディング値に付けた場合が例に含まれる
    • こうした属性はコードの動作には影響しないが、読む人を混乱させる可能性がある
  • AttributeTargets適用の強化

    • コンパイラは、let値、関数、union case宣言、暗黙コンストラクター、struct、classで**AttributeTargets**を正しく強制する
    • Xunitテストでunit引数を付け忘れるような、見つけにくいバグを防げる
    • 従来は[<Fact>] let ``this test always fails`` = Assert.True(false)が実際には関数でないため、テストランナーに無視され、dotnet test実行時に成功していた
    • 今後はerror FS0842: This attribute is not valid for use on this language elementエラーが発生する
  • パーサー回復

    • パーサー回復の改善により、編集中で文法的に未完成のコードでも、構文ハイライトのようなツール機能が動作し続ける
    • 回復対象には、未完成のasパターン、object expression、enum case宣言、record宣言、複雑なprimary constructorパターン、未解決のlong identifier、空のmatch節、不足したunion caseフィールドとフィールド型が含まれる
  • 診断メッセージと位置の精度

    • F# 9は新しい診断メッセージと、より正確な診断位置を追加する
    • 対象には、object expressionのあいまいなoverrideメソッド、non-abstract classでのabstract member、判別共用体ケースと同名のプロパティ、active patternの引数数不一致、重複フィールドを持つunion、computation expressionでuse!and!を併用するケースなどが含まれる
    • 生成されたILでメソッドが65,520個超あるクラスには、新しいコンパイル時エラーが発生する
    • このようなクラスはCLRでロードできず、ランタイムエラーにつながる
  • 実際の可視性オプション

    • F#にはprivate memberをIL上でinternalとして記録する性質があり、InternalsVisibleToによってF#プロジェクトにアクセス可能な非F#プロジェクトからprivate memberに不適切にアクセスできていた
    • F# 9はこの動作を修正するオプトインの**--realsig+**コンパイラフラグを提供する
    • .fsprojには<RealSig>true</RealSig>を追加して利用できる
    • ソリューションが従来の動作に依存しているか確認できる

FSharp.Core標準ライブラリの変化

  • コレクションのランダム関数

    • ListArraySeqモジュールにランダムサンプリングとシャッフル関数が追加される
    • データサイエンス、機械学習、ゲーム開発など、ランダム性が必要な一般的シナリオでF#を使いやすくする
    • すべての関数には3つのバリエーションがある
      • 暗黙的でスレッドセーフな共有Randomインスタンスを使うバリエーション
      • Randomインスタンスを引数に取るバリエーション
      • 0.0以上1.0未満のfloat値を返す必要があるカスタムrandomizer関数を受け取るバリエーション
    • 提供される関数はShuffleChoiceChoicesSampleの4つで、それぞれに3つのバリエーションがある
    • 全関数とバリエーション一覧はRFC #1135で確認できる
  • ランダム関数ごとの動作

    • Shuffleは同じ型・同じサイズの新しいコレクションを返し、各要素はコレクション長に対して一様な重みでシャッフルされる
    • 配列には、既存配列内で要素をシャッフルする**InPlaceバリエーション**もある
    • Choiceはコレクションサイズに対して一様な重みで単一のランダム要素を返す
    • Choicesは入力コレクションからN個の要素をランダムな順序で選択し、同じ要素が複数回選ばれることがある
    • Sampleは入力コレクションからN個の要素をランダムな順序で選ぶが、同じ要素を2回選ばない
    • SampleでNはコレクション長を超えられない
  • CustomOperationAttributeの引数なしコンストラクター

    • CustomOperationAttributeに引数なしコンストラクターが追加され、computation expression builderのカスタム操作を作りやすくなる
    • ほとんどの場合、明示名はメソッド名と同じなので、[<CustomOperation("bar")>]の代わりに[<CustomOperation>]を使える
  • C#コレクション式のサポート

    • C#からF#のリストやセットを**コレクション式(collection expression)**で初期化できる
    • たとえばSetModule.FromArray([1, 2, 3])の代わりにFSharpSet<int> mySet = [ 1, 2, 3 ];のように書ける
    • F#のイミュータブルコレクションは、System.Collections.Immutableコレクションにはないstructural equalityが必要な場合に使える

パフォーマンス改善

  • equalityチェックの最適化

    • equalityチェックは高速化され、メモリ割り当ても減る
    • struct型配列でArray.containsを使って存在しない値を探す例では、従来は1,000回ボクシングしていたが、今はボクシングしない
    • 2メンバーstructに対する配列関数ベンチマークで、ArrayContainsNonexistingの平均時間は5,190.95nsから766.005nsに減少し、割り当ては24,000Bから0になった
    • ArrayTryFindNonexistingは5,139.58nsから1,140.515nsに減少し、割り当ては24,024Bから24Bに減った
    • 詳細はF# Developer Stories: How we’ve finally fixed a 9-year-old performance issueで確認できる
  • struct判別共用体のフィールド共有

    • struct判別共用体の複数ケースでフィールド名と型が同じなら、同じメモリ位置を共有できる
    • これによりstructのメモリ使用量が減る
    • 例では、同じint64ベースのフィールドを共有するstruct判別共用体のサイズは16バイトである
    • 以前のようにケースごとに固有のフィールド名を使わなければならなかった版では60バイトだった
    • 以前は同じフィールド名が許可されていなかったため、バイナリ互換性の問題はない
  • 整数範囲の最適化

    • コンパイラはstart..finishstart..step..finish式の、より多くのケースで最適化されたコードを生成する
    • 従来は型がint/int32で、stepが定数1または-1の場合だけ最適化されていた
    • 他の整数型や他のstep値では、非効率なIEnumerableベース実装が使われていた
    • 今ではこうしたケースもすべて最適化される
    • for … in start..finish do …[start..step..finish][for n in start..finish -> f n]で**1.25×から8×**の高速化が見られる
  • list・array comprehensionの最適化

    • listとarray comprehensionでfor x in xs -> …形式が最適化される
    • 特に配列で改善が顕著である
    • 速度は最大**10×**向上し、割り当てサイズは半分から4分の1まで減る

Visual Studioツールの改善

  • live buffersのデフォルト有効化

    • Visual Studioのlive buffers機能は以前はオプトインだったが、十分にテストされた後、デフォルトで有効になった
    • IDEを動かすバックグラウンドコンパイラが保存されていないファイルバッファーを使う
    • ファイルをディスクに保存しなくても変更が反映される
    • 以前は、編集したが保存していないファイル内のシンボルをrenameすると、予期しない動作が起こることがあった
  • 不要な括弧を削除するcode fix

    • 不要な括弧について、Visual Studioで削除code fixが提供される
    • たとえばlet f (x) = xlet f x = xに、let _ = (2 * 2) + 3let _ = 2 * 2 + 3に変えられる
    • 明確さのための括弧ではなく、ノイズに近いものを減らす機能である
  • F#プロジェクトのカスタム可視化サポート

    • Visual Studioのデバッガービジュアライザー(debugger visualizer)がF#プロジェクトでも動作する
  • パイプライン途中のsignature tooltip

    • 以前は、パイプライン途中の関数に複雑なカリー化パラメーターがすでに適用されている場合、signature helpは提供されなかった
    • 今では次のパラメーターに対するsignature tooltipが表示される

1件のコメント

 
GN⁺ 2024-11-11
Hacker Newsのコメント
  • F#は大学で初めて触れて以来、ずっといちばん好きな言語だった
    共用体、null安全性、パターンマッチング、レコード、より強力な型推論とジェネリック制約といった機能では、C#よりはるかに先を行っていた
    C#も時間がたつにつれてこうした機能を取り入れたのは良いことだが、互いに互換性のない形で入ったのは残念
    F#への投資はC#よりずっと小さいため、革新のスピードで遅れた面もあるが、それでも依然として素晴らしい言語で、.NETエコシステムとおおむね互換性があり、C#と同じ性能をはるかに少ないボイラープレートで出せる

    • 非互換性の大半は、**ソースジェネレーター(source generators)**とその他のコード生成ベースのツールに集約できる
      必要な「接着コード」を含む補助的なC#プロジェクトを書けば、かなり簡単に対処できる
      それ以外に具体的に念頭に置いている問題があるのか気になる
      F# 9は、C#に最近追加されたref structのジェネリック引数の使用もサポートしており、F#自体で定義する機能も導入する予定だと理解している
      これまで追いつく作業を印象的なほどよくやってきたし、もっと評価されてよい
  • 「生成されたILにメソッドが65,520個を超えるクラスには、新しいコンパイル時エラーが出る。そうしたクラスはCLRがロードできず、実行時エラーになる」という箇所は想像するのも難しい
    とにかくF#は素晴らしい言語
    Excelの次にMicrosoftが出した2番目に良いものだと思うし、.NETを筋の通ったプラットフォームにしてくれる

    • C#はかなり過小評価されていると感じる
      すでにJSやTSに慣れている人に教えるのは比較的簡単で、生産性も高い言語だ
      ゲームエンジン、エンタープライズ向けバックエンド、デスクトップアプリまで、さまざまな文脈で使われている
      Microsoftが初期に何度かミスをして成長を鈍らせたと思うが、汎用言語としては本当に良く、学ぶのも比較的簡単だ
    • F#の欠点は何だと見ているのか気になる
      数年前にLINQPadという軽量IDEで少し触ったことはあるが、その後の長所短所や発展状況は追っていない
      https://www.linqpad.net/
  • Phosphorでは、F#の上に会社と技術の方向性を賭ける大きな判断を数年規模で行った
    1年以上試した末に、アプリケーションをTypeScriptとRustで完全に書き直した
    作っている製品はエンドユーザープログラミングツールなので、従来のフロントエンド/バックエンドの境界が曖昧になるが、.NETエコシステムはそこにあまり合わなかった
    もともとはFableでF#コードをJS、Rust、.NETなどにコンパイルし、複数の技術の間で型安全性を保ちながら必要な相互運用を行うつもりだった
    実際には、複数ライブラリ間の相互運用は予想よりはるかに難しく、さまざまな依存関係とバインディングを管理・更新するのは本当に苦痛だった
    F#が美しく効率的なコードを生み出すという前提は今も正しいと思うが、エコシステムと設計の性質上、従来型のフロントエンド/バックエンドの境界が明確なアプリケーションにだけよく合うと見ている
    その場合でも、F#はバックエンドにだけ使うことになるだろう
    今もっとも期待している技術は、社内で使っているEffectMoonbit
    EffectのSchemaライブラリはTSの型システムの穴をかなり埋めてくれるし、MoonbitはMS/.NET依存から離れた現代版F#のように見える
    MoonbitはOCaml向けFableとも言えるReScriptの作者が設計しており、非常によく設計され、最適化されたJS、WASM、ネイティブ出力へ直接コンパイルされる
    Effectは本番環境で使っており、Moonbitはまだだが、AIファーストの世界のために作られた言語としての可能性はかなり大きい

    • F#コードをTypeScriptモジュールにコンパイルして公開する方式は良い体験だった
      コアのビジネスロジックとバリデーターはF#で書き、残りのフロントエンドアプリはTypeScript、バックエンドはC#で書いた
      つまり、コアロジックと検証だけをF#に置き、すべての入出力はTSとC#が担う構成だった
    • その会社がDarklangと関係があるのか気になる
      似た製品で、F#で書かれていたと記憶しているが、最近の状況は追えていない
      Effectはかなり良く、TypeScript以外の言語にもあればいいのにと思う
      MoonBitは独自のプロプライエタリなプログラミング言語のように見えるので、よく知られた言語ではなくそちらへ移るのはためらわれるが、その点をどう見ているのか気になる
  • .NETを使う言語なら何でも選べる暗号学の授業があったが、F#でやった課題は他の人のものよりずっと読みやすかった
    もっと頻繁に使いたいが、データサイエンスの作業はほぼ100%Python

  • F# 9も.NET 9自体のほぼすべての性能改善の恩恵を受ける
    特に**オブジェクトエスケープ解析(object escape analysis)**まわりの改善が大きい
    https://devblogs.microsoft.com/dotnet/performance-improvemen...

  • F#で仕事をしていた頃が本当に懐かしい
    生産性の高い言語なので、アップデートを追い続けるだけでも楽しい
    コミュニティの規模とMicrosoftが時折見せる無関心を考えれば、ツールサポートはかなり良かったと思う
    最大の不満はコードテストカバレッジの正確さだった

  • 最近 F# を少し触ってみたのですが、Python から来た身としては、REPL でいろいろ試せる点が本当に気に入っています
    熟練した F# 開発者もそのように使っているのか気になります
    この冬に小さな Web バックエンドプロジェクトを作って、言語とエコシステムをもっと理解したいです
    HTTP 周りは Oxpecker が良いと聞いたのですが、PostgreSQL クライアントやドライバでおすすめがあるのか知りたいです
    ORM は好きではありません
    https://lanayx.github.io/Oxpecker/

    • 可能性は 2 つあります
      https://monazita.gitlab.io/monazita/ は F# を学びながら個人プロジェクト用に作ったもので、基本的には動作しますが、さらに磨き込む余地があり、PostgreSQL 専用です
      https://github.com/jacentino/DbFun は先のプロジェクトよりも洗練されており、複数のデータベースをサポートしています
    • Npgsql は人気のある C# ドライバで、F# ラッパーもあります
      そこから始めるのがよさそうです
    • 複数の言語を使う開発者として、たまに F# を使う機会がありますが、概念実証の大半は REPL で行っています
      API がはっきりしない場合は、実際のコードベース内でも「Send to F# interactive」を押して、モジュール内で実行しながら試します
      新しいライブラリの試用、簡単なベンチマーク、PowerShell スクリプトの代替にも使っています
      #!/usr/bin/env -S dotnet fsi というシバンで F# スクリプトを実行可能にできるので、dotnet-sdk がすでにインストールされている .NET プロジェクト周辺のスクリプトでは、bash/Python の代替としてよく使っています
      たいていはより速く実行され、概ね同じくらい簡潔です
      個人的な感覚では、F# の構文とイディオムは、小さなコード片をつなぎ合わせる REPL プログラミングにおいて C# よりもよく合っています
      C# は通常、より多くのオブジェクト指向的な構造が必要になります
  • F# のバージョン管理がどうなっているのか気になります
    良さそうな QoL 改善は多いものの、セマンティックバージョニングの観点では互換性の破壊がないのでメジャーバージョン変更が正当化されるようには見えず、セマンティックバージョニングを使っていないプロジェクトだとしても、8 から 9 に上げるほど大きな言語機能の飛躍でもないように思います
    別のコメントで最近出た .NET 9 に触れていましたが、.NET のバージョン番号に合わせるためなのか気になります

    • .NET と C# は最近あたりから、毎年 1 つずつ番号を上げるリリースに移行しており、F# も同じ方式に従っているようです
      .NET のバージョンと一致するタイミングで意図的に切り替えたのか、それとも偶然なのかは分かりません
      例えば C# は .NET 9 に合わせて C# 13 を出しました
    • C# と F# のどちらでも、.NET バージョンは dotnet build を実行するときに使われるビルドツールのバージョンを指します
      これには msbuild、コンパイラ、NuGet パッケージなどが含まれます
      例えば F# チームは 8 と 9 の間に言語変更を出すこともできましたが、そうしなかったとしても、何らかの理由で .NET 9 を必要とするコンパイラ変更や msbuild 変更を出すことはできました
      開発者は、デプロイ環境に最新の .NET がインストールされるのを待つ必要がある場合でなければ、通常はコードを最新のランタイムバージョンへ上げれば済みます
      最近は自己完結型デプロイを作る MSBuild の変更のおかげで、その必要も減りましたが、知っておく価値のある点です
    • 新しい .NET バージョンは毎年出ており、C# と F# のバージョンもその年次バージョンに合わせられています
      ただし C# は 4 つ分先行しています
      いずれにせよ、セマンティックバージョニングは過大評価されていると思います
  • Windows で GUI アプリを作るとき、C# の代替として F# はどのような位置づけなのか気になります
    この用途で F# を使っている会社があるのかも気になります

  • F# を自分で試したことはありませんが、ざっと見た限りこの資料は素晴らしそうでした: https://fsharpforfunandprofit.com/

    • F# に初めて触れる人にとって、実際に最高のサイトの 1 つです
      熟練した C# 開発者にも、プログラミングを比較的新しく学んでいる人にも向いています
      最近はほとんど更新されていないので F# 9 の新機能に関する議論は見つけにくいですが、既存の記事は applybind のような概念を理解するのに優れています