Anúncio

C#言語機能の作り方

Microsoft MVP
20 de Jan de 2018
Anúncio

Mais conteúdo relacionado

Apresentações para você(20)

Similar a C#言語機能の作り方(20)

Anúncio

Último(20)

Anúncio

C#言語機能の作り方

  1. C#言語機能の作り方 機能は用法・用量を守って正しく追加してください ! はじめにお読みください
  2. 本日のあらすじ C#コンパイラーにちょこっと手を入れるかー
  3. 使用上の注意 • 「独自に言語を作ろう・拡張しよう」みたいな話は ハシカみたいなもんです • 早めに免疫つけといた方がいい • 歳食ってから罹ると重篤なことが !
  4. 改めて、本日話すこと • はじめにお読みください • 「ぼくのかんがえた最強のプログラミング言語」にならないために • まずは同様の機能提案がないか探してみる • GitHubのRoslynリポジトリを探してみる • コンパイラーに手を入れる前に、拡張機能 • Analyzer、Source Generator • C#コンパイラーに手を入れてみる • まずは提案: 背景、課題、提案内容 • 実際やってみよう: 書き換え → 実行
  5. 改めて、本日話すこと C#コンパイラーにちょこっと手を入れるかー スライド中にほとんどない
  6. はじめに 注意事項があります コンパイラーの中身、作成・拡張方法に興味があるなら
  7. 新しい言語を作るということ “ every programming language – consists of 10% new and 90% stuff that is just bread and butter for programming and that just has to be there 「 全てのプログラミング言語は10%だけ新しく、 90%はパンとバターみたいなありふれた必需品 ” 」 Masterminds of Programming内でのAnders Hejlsbergの言葉 新しい言語を作るには: • 作る人にとって、10%の楽しい作業のために10倍の労力 • 使う人にとっても、うれしいのはたった10%
  8. 言語に機能を足すということ(1) “ We can add, but we can never take away. This weighs heavily on our minds as we evolve C#. Anders famously says that “every feature starts with minus a hundred points”. 「 我々は機能を足せるけど、絶対に消せない あらゆる機能は-100ポイントから考え始めろ ” 」 MSDNブログ内でのMads Torgersenの言葉 言語を新機能を足すには: • かなり慎重・保守的であるべき • やらないのが基本。やるには相当の理由を求める
  9. 言語に機能を足すということ(2) “ we need to be bold. We need to not be stymied by our responsibility to a distant future. Because otherwise we won’t have that future. C# needs to be among the greatest programming languages today, or it won’t be among them tomorrow. 「 我々は大胆でなければならない 今、最高でなければ、未来の最高にはなれない ” 」 C#開発チームの今: • かなりアグレッシブに新機能を取り入れる • 常に最高であろうとしている MSDNブログ内でのMads Torgersenの言葉
  10. この節のまとめ • 覚えておくべきこと • 新しいこと10%に対して変わらず必要なもの90%のコストがかかる • 言語作りは相当に保守的なもの • それでも、コンパイラーの専門家はアグレッシブに頑張ってる • お前の好みは聞いていない • この辺りを守らないと • 「ぼくがかんがえた最強のプログラミング言語」にしかならず
  11. 機能提案を探してみる 自分でやる前にまず検索 いくつかRoslynリポジトリ上の実例紹介
  12. だいたいもうある • たいていの要望はC#チーム把握済み • 冒頭の話通り、C#チームは相当アグレッシブに新機能模索してる • 16年の歴史あり、どこかで誰かがもう言ってる • GitHubのissueにもだいたいもう提案がある • 探しましょう • とはいえ、探しにくいことが多い…
  13. 検索するうえでの困難 • 原因 • みんなあいまい • 定義がそもそも緩い/文化ごとに呼び名が違う • 「〇〇したい」は直接信じちゃダメ • よく話を聞いてみたら本当にやりたかったことは別物 • 古すぎて追えない • 大昔に否決済みなものはドキュメントに残ってなかったり • 結果 • 自分が思ってた単語では検索引っかからない • 結構深く読み込んでみないと言ってることが一緒かどうかわからない
  14. 探しにくい例(1) • 提案 • Structural Subtyping • C++のConcepts • ScalaとかのTraits • RubyとかのMixins • DelphiとかのDelegation • JavaのDefault Methods • 本当にやりたかったこと • 継承なしでsubtypingしたい • 拡張メソッドの延長 • メソッド以外も拡張したい • 静的メンバーも拡張したい • 拡張にフィールドを持ちたい • 拡張でも仮想的にしたい • 委譲を楽にしたい • 破壊的変更にならないように メンバー追加したい • 文化ごとに呼び名が微妙に違う • それぞれ被ってる部分もあるし、 違う側面もあるし • C#に適合させようとするとまた 別の言葉になったりするし • いくつかの要件に分かれるし
  15. 探しにくい例(2) • 提案 • メソッドの戻り値にvarを使わせてくれ • 他の人の解釈 • メンバーの型推論? • 投稿者の本当にやりたかったこと • 匿名型を返せるようにしたい public var Foo() => new { Name = "" }; それはずいぶん前から否決されてる • 推論が終わらない場合があり得る • 変更の影響が多段に及ぶ public { string Name } Foo() => new { Name = "" }; • まずC# 7のタプル使うこと検討して • それだったら提案がすでにある: denotableな匿名型
  16. 探しにくい例(3) • 提案 • ILインライン アセンブラーがほしい • 過去に否決済み • それやるとC#チームで別言語の保守をすることになる • 使いたい場面かなり限られる • 探せば代案がある • Compiler intrinsics • インライン アセンブラーの代案としてはしょぼい • でも、使いたい場面に対応するには十分そう • 保守コストはずいぶん低い • 同じものではなく、名前が違うので検索性は悪い
  17. 特に困るやつ • 重複だけでも大変なのに • お前の好みは聞いていない • 同じことぶり返されても困る • ダメだと思うなら言わなくても
  18. オープンソース化あるある • オープンソース化すると必ず来るリクエスト • ライセンスがGPLじゃないので直しておきますね(^^♪ • {}がうざいのでインデントでブロックを区切るべきです(^o^)丿 • ; は要らないよね。そろそろ消しましょう(^_-)-☆ • 製品自体、ない方が人類のためだから全部消しておきますね(*^^)v あくまで主義の問題 • 絶対的な優劣があるわけじゃなく • まして、他人に自分の主義を押し付けていいものじゃない
  19. 好みを言われても困る • 好みを聞きたいだけなら、ディスカッションよりも投票 • ディスカッションに参加するような人は一握り • 知識ある人に偏ってて、平均的な開発者の意見にはならない • 少人数だから特に偏る • というか、偏った人ほど声が大きい • 投票用の機能を持ったサイトを使う方がわかりやすい • 書き方もある • ×「~が好み」「~すべき」 • 〇「~にはこういう利点・欠点がある。実装コストはこれくらいにな るだろう。利点が十分得られると思うがどうか?」
  20. 何度もぶり返されても困る • wildcard • 要らない引数・要らないメンバーを無視 • 提案: _ がいい! • 既存の関数型言語にそういうやつが多いから • C#だと _ が今現在有効な識別子なので無理 • 一応、検討するだけ検討はした • 文脈読んで頑張って破壊的変更なしで実現できないか • 結論: メリットの割に複雑なので見合わない (x, *) => x var (x, *) = tuple; 第2引数を無視 2つ目のメンバーを無視 今のところ*が 最有力
  21. 何度もぶり返されても困る • wildcard • 要らない引数・要らないメンバーを無視 • 提案: ignore がいい! • _ でも無理って話になってるのに… • ignore (識別子として有効)で行けると思うの?… • 既存の識別子になってるものは使いにくいって何度言わせる気だ… (x, *) => x var (x, *) = tuple; 第2引数を無視 2つ目のメンバーを無視
  22. ダメだと思うもの言われても困る ダメ元で言ってみるけど、 破壊的変更してでもシンプルな構文ほしい class Program static void Main(string[] args) int a = 10 int b = 20 if a == 10 if b == 20 WriteLine("Value of a is 10 and b is 20") elsif b > 50 WriteLine("Value of a is 10 and b greater than 50") else WriteLine("Value of a is 10") {} とか ; とか なくていいよね!
  23. ダメだと思うもの言われても困る ダメ元で言ってみるけど、 破壊的変更してでもシンプルな構文ほしい まあ、破壊的変更ダメでしょ C#変えなくてもいいんだけど、 例えば「D#」作らない? どうぞ。自分で作って どうやれば… GitHubリポジトリのフォーク手順教えてやるよ
  24. この節のまとめ • 重複を探すのも一苦労 • 似て非なるものがいろいろあって言葉がわからない • ほんとにやりたいことと提案は必ずしも一致していない • 結構昔に否決されたものは記録追いにくい • 特に困る意見 • お前の好みは聞いていない • 同じようなことを繰り返されても… • ダメと思ってるなら言わなくても…
  25. コンパイラーの拡張 言語構文として入れにくい機能がある その場合、「拡張」を考える Compiler Platform
  26. 言語機能にしにくいもの • 言語構文として採用しにくい機能がある • 用途が狭すぎる • 場面ごとにちょっと要件が違う • 見えないところでやるには処理が多すぎる 要望は常々ある 言語機能にはしにくい 例: • INotifyPropertyChangedの実装 • 型エイリアス • レコード こういうやつは常に 「重複だらけ」になってる
  27. INotifyPropertyChanged • INotifyPropertyChangedを実装したい • 実装例 class Point : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; private void OnPropertyChanged(PropertyChangedEventArgs args) => PropertyChanged?.Invoke(this, args); private void SetProperty<T>(ref T storage, T value, PropertyChangedEventArgs args) { if(!EqualityComparer<T>.Default.Equals(storage, value)) { storage = value; OnPropertyChanged(args); } } public int X { get { return _x; } set { SetProperty(ref _x, value, XProperty); OnPropertyChanged(SumProperty); } } private int _x; public static readonly PropertyChangedEventArgs XProperty = new PropertyChangedEventArgs(nameof(X)); public int Y { get { return _y; } set { SetProperty(ref _y, value, YProperty); OnPropertyChanged(SumProperty); } } private int _y; public static readonly PropertyChangedEventArgs YProperty = new PropertyChangedEventArgs(nameof(X)); public int Sum => _x * _y; public static readonly PropertyChangedEventArgs SumProperty = new PropertyChangedEventArgs(nameof(Sum)); } class Point { public int X; public int Y; public int Sum => X + Y; } 6 pointでないとスライドに収まらないこの コード、意味のある情報はほとんどない 以下のクラスにINotifyPropertyChangedを実装したいだけ 言語機能としてサポートすべき!?
  28. INotifyPropertyChanged • INotifyPropertyChangedを実装したい • 用途 • UIへのバインド、O/Rマッパーでのデータ更新検知 • 場面ごとに • Sum => X+Yみたいな依存関係は必要? • 比較は何でやる? 参照比較? 中身まで比較? shallow/deep? • 中身が見えないと • 身に覚えのないSetPropertyとかいうメソッドで例外が出たり
  29. 型エイリアス • 型に別名を付けたい • 例: • 実装方法の案 • コンパイラー上のトリック • 内部的にはintなんだけど、別の型と認識するように頑張る • 構造体でラップする public typedef StockId = int; public struct StockId { int _value; public StockId(int value) { _value = value; } public static explicit operator int(StockId id) => id._value; }
  30. 型エイリアス • 型に別名を付けたい • 例: • 用途 • intのIDなんだけど、在庫のIDなのかマスターデータのIDなのか区別したい • 場面ごとに • intや、他のintエイリアスと暗黙的に変換したい/したくない • intに対してメンバーを足したい/intのままがいい • 中身が見えないと • 本当に別の型なのか、コンパイラー上のトリックなのかでリフレクションの挙動 が違う public typedef StockId = int;
  31. レコード • 純粋にフィールドだけを持つ型、作るの面倒じゃない? struct Point { public int X { get; } public int Y { get; } public Point(int x, int y) { X = x; Y = y; } public void Deconstruct(out int x, out int y) { x = X; y = Y; } // Equals, GetHashCode, ==, !=, With... } これも、意味のある情報かなり少ない この程度→ struct Point { public int X { get; } public int Y { get; } }
  32. レコード • 純粋にフィールドだけを持つ型、作るの面倒じゃない? • 用途: さすがに広い なのでC# 6で「レコード型」って機能が入りかかった • ほんとにどんな場面でも対応できるのか悩ましい • タプルとの変換も欲しい • 比較はとりあえずメンバーごとにshallow比較? • メンバーを分解して使いたい var (x, y) = p; • immutableなインスタンスの部分書き換えもしたい • シリアライズ/デシリアライズ なのでC# 7でもまだ入り損ねてる struct Point(int X, int Y);
  33. 難しいのはわかった • 公式に言語機能として取り入れにくいのはわかった じゃあ、自前で拡張してやんよ! • 一般論として、独自拡張は割に合わない • 独自だからリファレンスが少ない • IDEのサポートもないときつい、IDEサポートまで実装するの? • 中身が見えないと怖い • どの場面向けにどういう内部実装になってるのかわからないと怖い • どこで何が起きたかわからなくてデバッグしづらい
  34. 補足: IDEの補助 • IDEはリファレンスとしても活躍 • C#で普段、リファレンス見ない どの文脈でどういうもの が書けるかわかる それをどう書けば いいかわかる 言語機能に対して こういう補助を完璧にこなすには コンパイラーとIDEの連携が必要
  35. • C#コンパイラーの中身が見えるように • 誰が中身を見るのか • IDE/エディター (Visual Studio, VS Code, OmniSharp) • サードパーティのツール • 静的コード解析、リファクタリング、メタプログラミング(コード生成) そのためのCompiler Platform exeC# 旧コンパイラー (C# 5.0以前) exeC# 新コンパイラー (C# 6以降) コンパイラーの中身が見える C#コンパイラーの拡張
  36. Compiler Platformが提供するもの • 単一のコンパイラーで実行可能形式作成とIDE対応を両方やる • 言語機能を作ったら、そのまま即座にIDEにも対応 • リファレンスなくてつらい問題を軽減 • C#を解析して、C#を書き換える機能を提供する • 書き換えた結果・コード生成した結果が、見慣れたC# • 中身が見えなくて怖い問題を軽減 • 生成されたコード上をステップ実行できる • デバッグ実行しにくい問題を軽減 独自拡張が 多少割に合う
  37. 割に合うなら書いてみよう 1. SDKのインストール • ツール → 拡張機能と更新プログラム 「.NET Compiler Platform SDK」 • Visual Studioのインストール時に入れることも可能 「Visual Studioの拡張機能開発」
  38. 割に合うなら書いてみよう 2. プロジェクト作成 • Extensibility→Analyzer with Code Fix (NuGet + VSIX)
  39. 割に合うなら書いてみよう 3. 拡張機能のコードを書く • CodeFixProviderとDiagnosticAnalyzerの実装
  40. 実例: C# 8までレコードを待てない • 作った拡張を参照 • VSIX … Visual Studioに対してインストール/マシン全体に影響 • https://visualstudiogallery.msdn.microsoft.com/941ef3c4-a523-4d77-8bcd-fdfeebb15853 • NuGet … プロジェクトごとに参照/そのプロジェクト内にだけ影響 • https://www.nuget.org/packages/RecordConstructorGenerator/
  41. 実例: C# 8までレコードを待てない • 拡張を入れると… 書き換え可能なところに電球マーク
  42. 実例: C# 8までレコードを待てない • あとは指示に従う 書き換え可能なところに電球マーク 書き換えによって何が起こるかが見える
  43. 実例: C# 8までレコードを待てない • 結果 書き換え結果 人によって要件/好みは違う※ もし結果が気に入らなければ? • 別の拡張を書けばいい • コード生成にオプションを 持たせればいい ※ レコード型としては機能いろいろ足りてない 実際、 「docコメント要らない」っていう要望もらってる
  44. 現状 • 書けるのは… • Analyzer: コード解析(電球アイコンを出すところまで) • Code Fix: 電球アイコンからのコード修正 • 言語拡張として本当に欲しいのは… • Source Generator: 自動的にコード生成してほしい • ソースコード保存やビルドのタイミングで 能動的にメニュー選択しないとコード生成が働かない C# 7のタイミングには間に合わず その次での提供予定
  45. いろいろなIDE/エディター • 昔は「でも、Visual Studioなんでしょ?」ってなったけど • IDEなレイヤーのオープン化も進んでる • Visual Studio Code: クロスプラットフォームなエディター • OmniSharp: いろんなエディター向けプラグイン • ATOMとかSublimeとか • Language Server Protocol: IDEとコンパイラーの間をつなぐ通信方式
  46. ここまでのまとめ • Roslyn = コンパイラーのプラットフォーム化 • 正式な製品名: .NET Compiler Platform • 誰でもRoslynの力にあやかれる/Roslynに乗っかれる • C#を拡張できる • 今はもうVisual Studioだけじゃない 背景 • 一般には独自拡張は割に合わない • IDE対応もないといまいち • 生成結果が見えないといまいち Roslynが提供するもの • 自動的にIDEにも対応 • C#を解析して、C#コードを生成 する機能
  47. コンパイラーの書き換え 「予防」する方向でばっかり話してきたけど 実際に書き換えるなら: 提案、コード書き換え
  48. 書き換えのテーマ • C#ソースコードのサロゲート ペア対応 • 現状のC#はサロゲート ペア識別子を使えない • 補足: サロゲート ペア • UTF16で2つ1組になる文字 • 例 • 一部の漢字: 𩸽(ほっけ)、𠮷(上が土)、𠮟(左が七)とか • 絵文字 • 楔形文字、ヒエログリフ
  49. 提案出すことも想定 • 自分独自にやってもしょうがないし、提案出すことも考える • 必要なもの • 背景・現状の問題 • 解決案 • 検討事項 おさらい: • 言語作りは相当保守的であるべき • 実装するにもコストがかかる • 問題を正しく伝えないといけない • リスクやコストに見合うメリット を提示しないといけない
  50. 背景1: 仕様違反になっている • C#の仕様曰く「UnicodeのLetterなら何でもOK」 • サロゲート ペアな文字の中にもLetterはたくさんある • 仕様の修正 • C# 5.0まで: 「Unicode 3.0を使う」とか書かれてた • この時代、サロゲート ペアがそもそも存在しない • 今: 「最新のUnicodeを使う」って書き変わってる • この時点で、サロゲート ペアにも対応すべきになった 対応していないので 現在、仕様違反
  51. 背景2: Swift、絵文字使えるってよ • Swiftは識別子の文字制限あんまりしてない • 記号だろうが使える • サロゲート ペアは冗談抜きで全部使える • やれる言語がある以上、検討くらいはしてもいいのでは ※ ※ http://qiita.com/yuky_az/items/540777dc61133decb508
  52. 背景3: 他のプログラミング言語の事例 • Javaも、Goもサロゲート ペアに対応している • ちゃんと、Letterな文字のみ public class Main { public static void main(String[] args) { String 𩸽 = "ほっけ"; System.out.print(𩸽); } } import ("fmt") func main() { 𩸽 := "ほっけ" fmt.Println(𩸽) } Java Go
  53. 提案1: 仕様書に従うべき • C#もサロゲート ペアに対応すべき • 現状の仕様通り、Letterを受け付けるべきだろう • 一部の漢字の他、楔形文字やヒエログリフは使えてしかるべき • 絵文字はSymbolなので不可 • 変更方法 • 難しい変更ではない: UnicodeCharacterUnitilities.csの IsValidIdentifierを書き換えるだけ • charでカテゴリー判定するのを止める UnicodeCategory GetUnicodeCategory(char c) UnicodeCategory GetUnicodeCategory(string s, int index) 今: 変更:
  54. 課題1: C#でのサロゲート ペア処理面倒 • 現状のstringはUnicode 3.0時代な設計 • charが16ビット、サロゲート ペアがらみの処理がかなり面倒 • 今、corefxチームが文字列がらみも改善作業をしている • System.Text.Utf8: UTF-8を直接扱えるライブラリ • サロゲート ペアの扱いも楽になりそう もしかすると、この作業を待ってから実装した方が楽かも
  55. 提案2: 絵文字 • Swiftに倣う実装はどうか • つまり、何でも受け付ける • 変更方法 • UnicodeCategory.Surrogateを受け付ける
  56. 課題2: 絵文字だけ特別? • 絵文字だけ特別扱いは気持ち悪い • これまでのC#仕様に反する(絵文字はSymbol) • Symbolの中で使える文字と使えない文字があるのは混乱の元 • やばい文字 • Symbolにはやばい文字がちらほら • 異字体セレクター単品(もちろん不可視) • 𝟢 … 数字に見えるけど数学記号(書式付き数字) • 絵文字に限っても • 💙💚💛💜 … 色付きハート • カラーフォントに対応していないとほとんど区別がつかない • 🏻 … skin tone(肌色指定)単品
  57. 書き換えのデモ • 時間があれば • Swift式(書き換えが楽な方)でやってみる • srcCompilersCorePortableUnicodeCharacterUtilities.cs • IsLetterChar()にUnicodeCategory.LetterNumberを足す • VisualStudio.SetupプロジェクトをF5で実行
  58. もしpull-requestを出すなら • 「動くコードを書きました」だけではダメ • 単体テストもちゃんと書かないと通らない • 仕様書がどう変わるかとかも • 今回の場合は「仕様違反の是正」なので変化なし
  59. この節のまとめ • コンパイラーを修正したいならまず提案を • 背景・現状の問題 • 解決案 • 検討事項 • 例として: 識別子のサロゲート ペア対応 • 現状、仕様違反になっている状態 • 実際のところ、「𩸽」とかを識別子に使いたい? • enumメンバー、単体テストのメソッド名には結構日本語も書くことあるけど • 問題を正しく伝えないといけない • リスクやコストに見合うメリット を提示しないといけない ※ ちなみに、ちょうどこの資料書いてる最中にそういうissueが立った… 中国の方によるもの
  60. まとめ • 「独自に言語を作ろう・拡張しよう」みたいな話は ハシカみたいなもんです • それでもできることは何かしらある • フィードバック、バグ報告 • 何かしら漏れはある • マルチバイト文字がらみとかは日本人か中国人が言わないと気付かれない • .NET Compiler Platformで拡張 • 提案を出すなら問題やメリットを正しく伝える • コードを書き換えるなら単体テストや仕様書も

Notas do Editor

  1. なのでだいたいは、既存言語の拡張な方向でしか物事進まない。 新しい言語ができるときは、足したいときじゃなくて、引きたいとき(負の遺産の撤廃) あるいは、政治的な理由
  2. 「-100ポイントから」の補足: - みんな自分が5ポイントくらいの投票権を持っています - たくさんの人に投票してもらって、100ポイント集まったものは実装するってことでタスクに積み増す - 新機能・破壊的変更起こしかけない提案は最初から -100 ポイントから始めます みたいな。機能を足すってのは基準点がマイナス。他より倍とかもっとポイント取らないと入れない。
  3. Anders … .NETの父。.NET以前にも、BorlandでDelphi作ってたし、MS移籍後最初の仕事はJavaの拡張/Visual J++ Mads … コンパイラー専攻で博士号持ち Lucian … 非同期処理専攻で博士号持ち
  4. それぞれ違うものなんだけど、同じ問題を解決するものだったり。 一番上と一番下だと全然別物だけど、隣接する2個だけ見ると被る部分が結構。
  5. denotable anonymous typeも、人によって言い方違う denote (明示)、expose (外に晒す)、declare (宣言) これも探しにくい理由
  6. お前の好みは聞いていない事例: https://github.com/dotnet/roslyn/issues/2974 https://github.com/dotnet/roslyn/issues/13731 ;とかインデントは、空白文字の有無でソースコードの意味が変わるとか、それなりに問題もある。式の途中で改行入れてるのが大変になる(VBはこっちで苦労してる)。
Anúncio