Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

今日もC# 10.0(来年の話) #17

Closed
ufcpp opened this issue Oct 31, 2020 · 11 comments
Closed

今日もC# 10.0(来年の話) #17

ufcpp opened this issue Oct 31, 2020 · 11 comments

Comments

@ufcpp
Copy link
Collaborator

ufcpp commented Oct 31, 2020

主に昨日書いたブログをベースに。

csharplang の運営方針とかトリアージとか

サムネに使ってるコード(大まかなアイディア レベル。今は動かないし、具体的な構文はまだ未定):

namespace App1;

static class Extensions
{
    public static double Area(this Shape s) => s switch
    {
        Rect r => r.Width * r.Height,
        Circle c => (var r = c.Radius; r * r),
    };
}

record Shape
{
    record Rect(double Width, double Height);
    record Circle(double Radius);
}

これを C# 9.0 で動く状態にするなら:

using System.Runtime.CompilerServices;

namespace App1
{

    static class Extensions
    {
        public static double Area(this Shape s) => s switch
        {
            Shape.Rect r => r.Width * r.Height,
            Shape.Circle c => c.Radius * c.Radius,
            _ => throw new SwitchExpressionException(),
        };
    }

    record Shape
    {
        private Shape() { }
        public record Rect(double Width, double Height) : Shape;
        public record Circle(double Radius) : Shape;
    }
}

(デモ用に適当に書いたコードなので、縁の面積が r2 になってる… 気にしないでほしい)

@ufcpp
Copy link
Collaborator Author

ufcpp commented Oct 31, 2020

本題とはそれるけど、カルチャー依存文字列処理が NLS (Windows 内蔵の)から ICU (デファクトスタンダード)に移行した話とか、それに伴ってこれまで顕在化してなかった「デフォルト動作が CurrentCulture 利用で変」問題(Ordinal のつもりで呼んでたけど間違ってたみたいなコード)が起こってたりとか、特に、\r\n\n にマッチしない(Unicode 標準的には \r\n は不可分扱い)問題とかの話するかも。

dotnet/runtime 43956 43736

@ufcpp-live
Copy link
Owner

今日の書き捨てコード

今日の主題としては触れなかったものの、「低水準機能拡張」の一環で safe 固定長バッファーあり。

safe 固定長バッファーがあるなら、params Span<T> の実装も楽になる。

// safe 固定長バッファー
// managed 型も OK
string x[10];

m("abc", "def");

void m(params Span<string> p)
{
}

@ufcpp-live
Copy link
Owner

Union 型話(構文未定。仮想的なコード)

// 可能性としてあり得る一番短い書き方
enum record Shape
{
    Rect(double Width, double Height);
    Circle(double Radius);
}

union Union
{
    String(string n);
    Int1(int x);
    Int2(int x);

    // 匿名 union でいうと string | int | int になるけども…
}

数学では
// union
{ 1 } | { 2 } = { 1, 2 }
{ 1 } | { 1 } = { 1 }

// disjoint sum
// a とか b とかは単なる弁別用のマーカー
{ 1 } ⊕ { 2 } = { (a, 1), (b, 2) }
{ 1 } ⊕ { 1 } = { (a, 1), (b, 1) }

@ufcpp-live
Copy link
Owner

Discriminated Union はやっぱり「小さい機能の集まり」として実装されるはず。

その1: 網羅性チェック

using System.Runtime.CompilerServices;

// 第1案: 網羅性チェック

static class Extensions
{
    // private コンストラクターがある時に限り、網羅してる扱いで警告消していいんじゃない?
    public static double Area(this Shape s) => s switch
    {
        Shape.Rect r => r.Width * r.Height,
        Shape.Circle c => c.Radius * c.Radius,
    };
}

record Shape
{
    private Shape() { }
    public record Rect(double Width, double Height) : Shape;
    public record Circle(double Radius) : Shape;
}

// recortd, discriminated union に限らず
class ShapeC
{
    private ShapeC() { }
    public class Rect : ShapeC { public double Width; public double Height; }
    public class Circle : ShapeC { public double Radius; }
}

@ufcpp-live
Copy link
Owner

Discriminated Union がらみその2

// using static だとこのファイルがまさに問題起こす
//using static Shape;
//using static ShapeType;
// これで Rect, Circle が名前被りに

// 第2案: スコープ解決の推論

static class Extensions
{
    // 別提案として、こういうところに using を書かせてほしいという話もあり
    //using System;

    public static bool M1(this ShapeType s) => s switch
    {
        // ここに using を書く余地はない
        ShapeType.Rect => true,
        ShapeType.Circle => false,
    };

    // enum でこう書きたい
    // using static ShapeType で似たことはできるけど…
    // using static だとスコープがさすがに広すぎる
    public static bool M2(this ShapeType s) => s switch
    {
        Rect => true,
        Circle => false,
    };

    // discriminated union 前提として
    public static double Area1(this Shape s) => s switch
    {
        Shape.Rect r => r.Width * r.Height,
        Shape.Circle c => c.Radius * c.Radius,
    };

    // やっぱりこう書きたい
    public static double Area2(this Shape s) => s switch
    {
        Rect r => r.Width * r.Height,
        Circle c => c.Radius * c.Radius,
    };
}

record Shape
{
    private Shape() { }
    public record Rect(double Width, double Height) : Shape;
    public record Circle(double Radius) : Shape;
}

enum ShapeType
{
    Rect,
    Circle
}

@ufcpp-live
Copy link
Owner

簡単そうに見えて、型推論にかかる時間が簡単に爆発的に増えるのでやばい。
この手の型推論を認めてる言語、コンパイラーが「コンパイルに時間がかかりすぎています」エラーを出すことがある。

var x = new[]
{
    "",
    1,
    1f,
};

// 型推論の時間かかる。C# は認めてない
var x = new[]
{
    (IB)null,
    (IC)null,
};

// これはOK
var x1 = new IA[]
{
    (IB)null,
    (IC)null,
};
var x2 = new[]
{
    (IA)null,
    (IC)null,
};

@ufcpp-live
Copy link
Owner

C# はコンパイル時間にも結構気を使って文法を決めてる。
↓とかは割と最近入った「オーバーロード解決の改善」なものの、型制約と引数を見る順序を変えるだけだったのでパフォーマンス的なペナルティはそんなになかったらしい。

// これは割と最近追加されたオーバーロード解決。
// パフォーマンスのペナルティは大してないらしい
static class ClassEx
{
    public static void M<T>(this object _) where T : class { }
}

static class StructEx
{
    public static void M<T>(this object _) where T : struct { }
}

@ufcpp-live
Copy link
Owner

// オーバーロード解決は2乗オーダーだそうです
class Overload
{
    public void M(int x, int y) { }
    public void M(int x, short y) { }
    public void M(short x, short y) { }
    public void M(int w, int x, int y) { }
    public void M(int w, int x, short y) { }
    public void M(int w, short x, short y) { }
    public void M(short t, int x, int y) { }
    public void M(short t, int x, short y) { }
    public void M(short t, short x, short y) { }
    public void M(short t, int w, int x, int y) { }
    public void M(short t, int w, int x, short y) { }
    public void M(short t, int w, short x, short y) { }
}

@ufcpp-live
Copy link
Owner

.NET 5 ではグローバリゼーションの内部実装が ICU に変更されて…

using System;

// ファイルの改行コード依存
// LF にすると 0 になる
// git が autocrlf = ture だったら?…
var s = @"
"; // \r\n ? それとも \n ?

// 一応の解決策(絶対いや)
var s1 = "1行目" + Environment.NewLine
    + "2行目";

// ダメだ…
//const string cs = "1行目" + Environment.NewLine
//    + "2行目";

Console.WriteLine(s.IndexOf("\n")); // .net core 3.1 だと 1、.net 5 だと -1
Console.WriteLine(s.IndexOf('\n'));
Console.WriteLine(s.Contains("\n")); // true

Console.WriteLine(s.IndexOf("\n", StringComparison.Ordinal)); // これなら 1
Console.WriteLine(s.AsSpan().IndexOf("\n")); // Span の方はデフォで Ordinal

Console.WriteLine(s.Replace("\n", "aa")); // Ordinal!!!!!!

// IndexOf は CurrentCulture 見る
// Contains は Ordinal

@ufcpp-live
Copy link
Owner

カルチャーというと…

using System;
using System.Text;

Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
Console.WriteLine("i".ToUpper(
    System.Globalization.CultureInfo.GetCultureInfo("tr-TR")));
Console.WriteLine("i".ToUpperInvariant());
// トルコ語の i の大文字は İ
// ASCII で完結していないので「ASCII 特殊対応 fast path」とかが作れない

Console.WriteLine(DateTime.Now.ToString(
    System.Globalization.CultureInfo.InvariantCulture));
//10/31/2020 17:02:47 になる
// せめて ISO 標準の 2020/10/31T17:02:47 にしようよ…

Invariant がアメリカ式(ヤードポンド、ファーレンハイトの国)の書式なのどうなの…

@ufcpp-live
Copy link
Owner

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants