Эквивалентность record-ов
От: VladD2 Российская Империя www.nemerle.org
Дата: 16.10.23 15:08
Оценка:
Что-то прямо сижу и тихо охрениваю.

В record нельзя добавить собственную реализацию эквивалентности (переопределит Equals) и при это они не умеют реализовывать эквивалентность для записей со списками.

record R(int X, ImmutableArray<int> Xs);

static class P
{
    static void Main()
    {
        var r1 = new R(1,ImmutableArray.Create(2));
        var r2 = new R(1,ImmutableArray.Create(2));

        Console.WriteLine(r1 == r2);
   }
}


Выводит False!

С массивами тоже самое. Т.е. любая ссылка в параметре и прощай эквивалентность. Причем и вручную это не ясно как исправить.

А как жить то с такими говнорекордами? Вот на фиг так делать?

Я что-то пропустил? Как обойти это безобразие?
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re: Эквивалентность record-ов
От: hi_octane Беларусь  
Дата: 16.10.23 15:36
Оценка: 131 (2)
VD>Я что-то пропустил? Как обойти это безобразие?
Там же ещё есть record struct, которые вообще ValueType. Поэтому переопределение в рекордах немножко своё. Нужно сделать Equals от IEquatable, но без override, компилятор поймёт:

public sealed record R(int x, int[] xs)
{
    public bool Equals(R? other)
        => other.x == x && other.xs.SequenceEqual(xs);
}


И тогда на == будет True
Re[2]: Эквивалентность record-ов
От: VladD2 Российская Империя www.nemerle.org
Дата: 16.10.23 16:07
Оценка:
Здравствуйте, hi_octane, Вы писали:

_>Там же ещё есть record struct, которые вообще ValueType.


Как-то раньше в структурах удавалось Equals переопределять ведь.

_>Поэтому переопределение в рекордах немножко своё. Нужно сделать Equals от IEquatable, но без override, компилятор поймёт:


_>
_>public sealed record R(int x, int[] xs)
_>{
_>    public bool Equals(R? other)
_>        => other.x == x && other.xs.SequenceEqual(xs);
_>}
_>


_>И тогда на == будет True


Что-то не очень понял. Правда вот так понял:
record R(int X, int[] Xs)
{
    public virtual bool Equals(R? other) => other != null && other.X == X && other.Xs.SequenceEqual(Xs);

    public override int GetHashCode()
    {
        return X;
    }
}


Но в любом случае все это выглядит странным. А что что не реализована эквивалентность для вложенных последовательностей — это вообще лажа какая-то.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[2]: Эквивалентность record-ов
От: VladD2 Российская Империя www.nemerle.org
Дата: 16.10.23 16:16
Оценка:
Здравствуйте, hi_octane, Вы писали:

_>Там же ещё есть record struct, которые вообще ValueType.


Как-то раньше в структурах удавалось Equals переопределять ведь.

_>Поэтому переопределение в рекордах немножко своё. Нужно сделать Equals от IEquatable, но без override, компилятор поймёт:


_>
_>public sealed record R(int x, int[] xs)
_>{
_>    public bool Equals(R? other)
_>        => other.x == x && other.xs.SequenceEqual(xs);
_>}
_>


_>И тогда на == будет True


Что-то не очень понял. Правда вот так понял:
record R(int X, int[] Xs)
{
    public virtual bool Equals(R? other) => other != null && other.X == X && other.Xs.SequenceEqual(Xs);

    public override int GetHashCode()
    {
        return X;
    }
}


Но в любом случае все это выглядит странным. А что что не реализована эквивалентность для вложенных последовательностей — это вообще лажа какая-то.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[3]: Эквивалентность record-ов
От: yenik  
Дата: 17.10.23 05:00
Оценка: +1 -1
VD>
VD>record R(int X, int[] Xs)
VD>{
VD>    public virtual bool Equals(R? other) => other != null && other.X == X && other.Xs.SequenceEqual(Xs);

VD>    public override int GetHashCode()
VD>    {
VD>        return X;
VD>    }
VD>}
VD>


VD>Но в любом случае все это выглядит странным. А что что не реализована эквивалентность для вложенных последовательностей — это вообще лажа какая-то.


Коллекции суть ссылочный типы, естественно, равенство для них — это ссылочное равенство. Странным было бы, если бы их равенство по дефолту делалось через SequenceEqual.
Re: Эквивалентность record-ов
От: _FRED_ Черногория
Дата: 17.10.23 15:08
Оценка: 199 (6)
Здравствуйте, VladD2, Вы писали:

VD>Что-то прямо сижу и тихо охрениваю.

VD>В record нельзя добавить собственную реализацию эквивалентности (переопределит Equals) и при это они не умеют реализовывать эквивалентность для записей со списками.

С событиями тоже здорово вышло, мало кто ожидал, что они станут и их сравнивать:
using System;

var r1 = new R("R");
var r2 = new R("R");
Console.WriteLine($"Compare {nameof(r1)} and {nameof(r2)} before subscription: {r1 == r2}");

r1.Event += delegate { };
Console.WriteLine($"Compare {nameof(r1)} and {nameof(r2)} after subscription: {r1 == r2}");

record R(string Name) {
  public event EventHandler? Event;
}


P.S. По-честному, если рекорд не `sealed`, хорощо б и `EqualityContract` сравнивать и учитывать в хеше:
record R(string Name) {
  public event EventHandler? Event;
    
  public virtual bool Equals(R? other) => other != null && other.EqualityContract == EqualityContract && other.Name == Name;
  public override int GetHashCode() => (EqualityContract, Name).GetHashCode();
}
Help will always be given at Hogwarts to those who ask for it.
Re[4]: Эквивалентность record-ов
От: _FRED_ Черногория
Дата: 17.10.23 15:27
Оценка: +1
Здравствуйте, yenik, Вы писали:

VD>>Но в любом случае все это выглядит странным. А что что не реализована эквивалентность для вложенных последовательностей — это вообще лажа какая-то.

Y>Коллекции суть ссылочный типы, естественно, равенство для них — это ссылочное равенство. Странным было бы, если бы их равенство по дефолту делалось через SequenceEqual.

Это не так. Компилятор использует `EqualityComparer<>.Default.Equals(, )` для сравнения членов, поэтому если коллекция будет иметь переопределённый `Equals`, то будет использван он, а не "ссылочное равенство".
Help will always be given at Hogwarts to those who ask for it.
Re[4]: Эквивалентность record-ов
От: Разраб  
Дата: 18.10.23 01:42
Оценка:
Здравствуйте, yenik, Вы писали:


VD>>Но в любом случае все это выглядит странным. А что что не реализована эквивалентность для вложенных последовательностей — это вообще лажа какая-то.


Y>Коллекции суть ссылочный типы, естественно, равенство для них — это ссылочное равенство. Странным было бы, если бы их равенство по дефолту делалось через SequenceEqual.

F# :
> let arr1 = [|1;2;3|];;
val arr1 : int [] = [|1; 2; 3|]

> let arr2 = [|1;2;3|];;
val arr2 : int [] = [|1; 2; 3|]

> arr1 = arr2;;
val it : bool = true

> type Cart = { Id : int; Items : array<int> };;
type Cart =
  {Id: int;
   Items: int array;}

> let c1 = { Id = 1; Items = arr1 };;
val c1 : Cart = {Id = 1;
                 Items = [|1; 2; 3|];}

> let c2 = { Id = 1; Items = arr2 };;
val c2 : Cart = {Id = 1;
                 Items = [|1; 2; 3|];}

> c1 = c2;;
val it : bool = true
☭ ✊ В мире нет ничего, кроме движущейся материи.
Re[5]: Эквивалентность record-ов
От: yenik  
Дата: 18.10.23 05:17
Оценка:
_FR>если коллекция будет иметь переопределённый `Equals`

И я о том: Странным было бы, если бы их равенство по дефолту делалось через SequenceEqual. А если переопределить Equals, тогда конечно любые фантазии.
Можно было бы ещё вспомнить string, но это особенный тип коллекции.
Re[5]: Эквивалентность record-ов
От: yenik  
Дата: 18.10.23 05:32
Оценка: -1
Р>F# :

Хорошее замечание. И хорошо, что этого нет в C#. Представляю себе два списка по миллиону элементов, и вдруг a == b их перебирает. Нет, если нужно SequenceEqual, то и вызывать нужно SequenceEqual в явном виде.
Re[6]: Эквивалентность record-ов
От: Разраб  
Дата: 18.10.23 06:35
Оценка: :)
Здравствуйте, yenik, Вы писали:

Р>>F# :


Y>Хорошее замечание. И хорошо, что этого нет в C#. Представляю себе два списка по миллиону элементов, и вдруг a == b их перебирает. Нет, если нужно SequenceEqual, то и вызывать нужно SequenceEqual в явном виде.

Нет, если нужно ReferenceEquals , то и вызывать нужно ReferenceEquals в явном виде.
obj.ReferenceEquals (arr1,arr2)// => false
☭ ✊ В мире нет ничего, кроме движущейся материи.
Re[4]: Эквивалентность record-ов
От: VladD2 Российская Империя www.nemerle.org
Дата: 18.10.23 16:45
Оценка:
Здравствуйте, yenik, Вы писали:

Y>Коллекции суть ссылочный типы, естественно, равенство для них — это ссылочное равенство.


Рекорды тоже ссылочный тип. Но эквивалентность для них сделана по значению. Следственно естественного тут ничего нет. Это твои стереотипы.

Тут на лицо непоследовательность. Для одних типов делаем, для других нет.

Взялись повторять функциональные языки, но на полпути забили на это дело. В Немерле есть встроенный список, который сам по себе является алгебраическим типом и для него эквивалентность работает как надо. Тут же ничего подобного не предусмотрели. Сам метод переопределения эквивалентности сделали мягко говоря странно.

Y>Странным было бы, если бы их равенство по дефолту делалось через SequenceEqual.


Что тут странного? Странно, что коллекции не поддерживают Equals и IEquatable из коробки.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[2]: Эквивалентность record-ов
От: VladD2 Российская Империя www.nemerle.org
Дата: 18.10.23 16:46
Оценка:
Здравствуйте, _FRED_, Вы писали:

_FR>P.S. По-честному, если рекорд не `sealed`, хорощо б и `EqualityContract` сравнивать и учитывать в хеше:


А кто-нибудь может в двух словах объяснить на фиг он вообще EqualityContract придумали?
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[5]: Эквивалентность record-ов
От: Serginio1 СССР https://habrahabr.ru/users/serginio1/topics/
Дата: 18.10.23 18:29
Оценка: -1
Здравствуйте, VladD2, Вы писали:


Y>>Странным было бы, если бы их равенство по дефолту делалось через SequenceEqual.


VD>Что тут странного? Странно, что коллекции не поддерживают Equals и IEquatable из коробки.


Различная эквивалентность нужна. Например сравнивать через HashSet, сортированные списки по определенным полям, ReferenceEquals итд.
Кроме того надо разруливать циклические ссылки итд. Слишком много параметров сравнения. Проще для списков свою реализацию сделать.
и солнце б утром не вставало, когда бы не было меня
Re[3]: Эквивалентность record-ов
От: _FRED_ Черногория
Дата: 18.10.23 20:02
Оценка: 53 (1)
Здравствуйте, VladD2, Вы писали:

_FR>>P.S. По-честному, если рекорд не `sealed`, хорощо б и `EqualityContract` сравнивать и учитывать в хеше:

VD>А кто-нибудь может в двух словах объяснить на фиг он вообще EqualityContract придумали?

Чтобы экземпляр наследника вдруг не смог бы быть равен экземпляру базового типа.

The EqualityContract enables the equality methods to compare the runtime type of objects when they're checking for equality.

(отсюда)

Пример:
using System;

var r = new R("A");
var x = new X("A");
Console.WriteLine($"Compare {nameof(r)} and {nameof(x)}: {r == x}"); // False

var r1 = new R1("A");
var x1 = new X1("A");
Console.WriteLine($"Compare {nameof(r1)} and {nameof(x1)}: {r1 == x1}"); // True

record R(string Name) { }
record X(string Name) : R(Name) { }

record R1(string Name) {
  public virtual bool Equals(R1? other) => other is not null && other.Name == Name;
  public override int GetHashCode() => Name?.GetHashCode() ?? 0;
}

record X1(string Name) : R1(Name) { }
Help will always be given at Hogwarts to those who ask for it.
Re[5]: Эквивалентность record-ов
От: yenik  
Дата: 19.10.23 05:54
Оценка:
VD>Тут на лицо непоследовательность. Для одних типов делаем, для других нет.
VD>Что тут странного? Странно, что коллекции не поддерживают Equals и IEquatable из коробки.

https://learn.microsoft.com/ru-ru/dotnet/csharp/language-reference/builtin-types/record#value-equality

Для типов class два объекта равны, если они ссылаются на один и тот же объект в памяти.
Для типов struct два объекта равны, если хранят одинаковые значения и имеют один и тот же тип.
Для типов с модификатором record (record class, record struct и readonly record struct) два объекта равны, если они относятся к одному типу и хранят одинаковые значения.


Коллекции в .NET относятся к типу class и по определению не поддерживают переопределение Equals и IEquatable из коробки. Если бы к существующим коллекциям вдруг приделали переопределение Equals, то это было бы несовместимое изменение. Если бы новые коллекции стали делать с Equals на основе поэлементного сравнения, то это было бы непоследовательно.
Типы record — это новшество, у которого равенство отличается от традиционной трактовки равенства объектов в C# и других популярных языках программирования.
Re[6]: Эквивалентность record-ов
От: samius Япония http://sams-tricks.blogspot.com
Дата: 19.10.23 07:33
Оценка:
Здравствуйте, yenik, Вы писали:

VD>>Тут на лицо непоследовательность. Для одних типов делаем, для других нет.

VD>>Что тут странного? Странно, что коллекции не поддерживают Equals и IEquatable из коробки.

Y>https://learn.microsoft.com/ru-ru/dotnet/csharp/language-reference/builtin-types/record#value-equality

Y>

Y>Для типов class два объекта равны, если они ссылаются на один и тот же объект в памяти.
Y>Для типов struct два объекта равны, если хранят одинаковые значения и имеют один и тот же тип.
Y>Для типов с модификатором record (record class, record struct и readonly record struct) два объекта равны, если они относятся к одному типу и хранят одинаковые значения.


Y>Коллекции в .NET относятся к типу class и по определению не поддерживают переопределение Equals и IEquatable из коробки.

Это определение вводит очень странное отношение равеснства объектов, пользоваться которым не сильно удобно, т.к. оно не указывает на то, могут ли быть равны две строки с одинаковыми значениями, но в разных участках памяти. То есть, даже для того, что бы понять как ведут себя рекорды со строковыми полями, мы не можем это определение использовать.
Кроме того, оно определяет равенство объектов через ссылку на объект, но ссылка не является объектом в системе типов .NET.

Y>Если бы к существующим коллекциям вдруг приделали переопределение Equals, то это было бы несовместимое изменение. Если бы новые коллекции стали делать с Equals на основе поэлементного сравнения, то это было бы непоследовательно.

Y>Типы record — это новшество, у которого равенство отличается от традиционной трактовки равенства объектов в C# и других популярных языках программирования.
Традиционная трактовка через популярные языки — вообще что-то некодифицируемое. Мы в одном-то языке не можем разобраться.
Есть отношения эквивалентности и их реализации, которые могут быть хорошими или не очень. Пожалуй, это все, на что можно опираться. У рекордов — эквивалентность такая, какую сегодня дает компилятор, согласно спецификации рекордов (https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-9.0/records). Разумеется, всем не угодишь, поэтому, она такая, какая есть, а в хелпах сова на глобусе.
Re[4]: Эквивалентность record-ов
От: VladD2 Российская Империя www.nemerle.org
Дата: 19.10.23 09:20
Оценка:
Здравствуйте, _FRED_, Вы писали:

_FR>Чтобы экземпляр наследника вдруг не смог бы быть равен экземпляру базового типа.


А чем им для этого GetType() не подошел? Да и для структурной эквивалентности (что и должна обеспечиваться для записей) не нужно совпадение типов. Достаточно совпадения полей и их значений. Так что это должно быть как опция.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[6]: Эквивалентность record-ов
От: VladD2 Российская Империя www.nemerle.org
Дата: 19.10.23 09:31
Оценка:
Здравствуйте, Serginio1, Вы писали:

S>Различная эквивалентность нужна. Например сравнивать через HashSet, сортированные списки по определенным полям, ReferenceEquals итд. Кроме того надо разруливать циклические ссылки итд. Слишком много параметров сравнения.


Тут есть два нюанса.

1. Удобство. Заставлять делать людей рутинную работу — глупо. Несомненно, если нужны какие-то нюансы, надо позволить людям заменить стандартную реализацию. Но по умолчанию должен работать самый ожидаемый вариант. Тогда не придется тратить время на непроизводительный труд.

2. Под записями (рекордами) лежат математические модели алгебраических типов и структурной типизации. Они не из пальца высосаны. И раз так, то нужно выдерживать все каноны этих моделей.

S>Проще для списков свою реализацию сделать.


Проще не делать ничего без необходимости. Проще, как раз, было бы реализовать для списков сравнение. И уже у конкретных видов коллекций сделать тот или ной вид сравнения. Для списков — последовательное сравнение (совпадение всех элементов), для сетов — совпадение множеств.

Ну и логично было бы сделать нормальный однонаправленный связанный список на рекордах, чтобы он был алгебраическим типом (как в Немерле).
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Отредактировано 19.10.2023 10:09 VladD2 . Предыдущая версия .
Re[7]: Эквивалентность record-ов
От: Serginio1 СССР https://habrahabr.ru/users/serginio1/topics/
Дата: 19.10.23 09:43
Оценка:
Здравствуйте, VladD2, Вы писали:


S>>Проще для списков свою реализацию сделать.


VD>Проще не делать ничего без необходимости. Проще, как раз, было бы реализовать для списков сравнение. И уже у конкретных видов коллекций сделать тот или ной вид сравнения. Для списков — последовательное сравнение (совпадение всех элементов), для сетов — совпадение множеств.


VD>Ну и логично было бы сделать нормальный однонаправленный связанный список на рекордах, чтобы он был алгебраическим типом (как в Немерле).


Как раз в большинстве вариантов нужны свои сравнения. Опять же Хэш код вычислять нужно время. А нужно то всего на всего сравнить ссылки
Я вот не помню когда пользовался односвязными списками вообще, только для буферов (страниц)

А так тебе придется для строк свои реализации с IgnoreCase делать итд.
Для списка достаточно по дефолту ReferenceEquals
и солнце б утром не вставало, когда бы не было меня
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.