Здравствуйте, Serginio1, Вы писали:
S>Ну и делать проверку на EmptyNode
Это профанация идеи. Смысл not null — не в том, чтобы не получать NRE, а в том, чтобы иметь семантические гарантии.
EmptyNode ничуть не лучше null: во всём коде нужно делать проверки, что по ссылке — настоящая нода, а не эрзац.
Null Object Pattern работает тогда, когда у нас таки есть какой-то "нейтральный" объект, который может выступать в роли "заглушки". Ну, там, пустая строка вместо null-строки позволяет нам брать у неё длину, конкатенировать и т.п. вместо паники/исключения/segfault.
А non-nullability — это специальный инвариант, гарантирующий нетривиальную инициализацию.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[3]: C# [Proposal] init block for safe initialization of complex
S>>Ну и делать проверку на EmptyNode S>Это профанация идеи. Смысл not null — не в том, чтобы не получать NRE, а в том, чтобы иметь семантические гарантии. S>EmptyNode ничуть не лучше null: во всём коде нужно делать проверки, что по ссылке — настоящая нода, а не эрзац. S>Null Object Pattern работает тогда, когда у нас таки есть какой-то "нейтральный" объект, который может выступать в роли "заглушки". Ну, там, пустая строка вместо null-строки позволяет нам брать у неё длину, конкатенировать и т.п. вместо паники/исключения/segfault. S>А non-nullability — это специальный инвариант, гарантирующий нетривиальную инициализацию.
Угу,
А у Node2.Next будет null?
Суть EmptyNode это как раз и есть аналог пустой строки. Ты можешь вызвать методы, свойства. Просто они будут ссылаться на соответствующие Empty.
То есть каждый класс должен создавать такой объект. При этом он должен быть иммутабельным.
и солнце б утром не вставало, когда бы не было меня
Re[4]: C# [Proposal] init block for safe initialization of complex
Здравствуйте, Serginio1, Вы писали:
S> Суть EmptyNode это как раз и есть аналог пустой строки. Ты можешь вызвать методы, свойства. Просто они будут ссылаться на соответствующие Empty. S> То есть каждый класс должен создавать такой объект. При этом он должен быть иммутабельным.
Зачем вы объясняете мне то же самое, что я вам объяснил?
Null object pattern не решает поставленную задачу.
Вот представьте, что у вас есть департаменты, в которых работают сотрудники.
И есть инвариант, что у каждого департамента обязан быть указан руководитель, а у каждого сотрудника — департамент.
Не в том, чтобы они были "не null"! А в том, что не может быть департамента без руководителя, и сотрудника вне департамента.
Минимальная корректная топология — это department1.Manager = employee1, и employee1.Department = department1.
Нет никакого "EmptyDepatment" — кто у него будет руководителем? И нет никакого EmptyEmployee — в каком департаменте он будет работать?
Даже если мы возьмём и "как-то" породим эту минимальную пару, обойдя ограничение языка, нам это никак не поможет.
Потому что мы сможем сконструировать, например, департамент departmentX = new Department() { Manager = EmptyEmployee}.
И этот департамент с точки зрения бизнес-логики ничуть не лучше, чем департамент, у которого .Manager == null.
Не может "никто" быть руководителем департамента — бизнес-логика такая!
Мы, собственно, хотим убрать изо всего кода бесчисленных проверок вида if(dept.Manager == null) log.Warning("Обнаружен департамент без руководителя — не можем подготовить приказ о начислении премий") else RegisterBonus(dept.Manager). Всё, что вы можете добиться с NullObject — это замены этого кода на if(dept.Manager == EmptyEmployee) log.Warning("Обнаружен департамент без руководителя — не можем подготовить приказ о начислении премий") else RegisterBonus(dept.Manager)
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[5]: C# [Proposal] init block for safe initialization of complex
S>> Суть EmptyNode это как раз и есть аналог пустой строки. Ты можешь вызвать методы, свойства. Просто они будут ссылаться на соответствующие Empty. S>> То есть каждый класс должен создавать такой объект. При этом он должен быть иммутабельным.
S>Зачем вы объясняете мне то же самое, что я вам объяснил? S>Null object pattern не решает поставленную задачу. S>Вот представьте, что у вас есть департаменты, в которых работают сотрудники. S>И есть инвариант, что у каждого департамента обязан быть указан руководитель, а у каждого сотрудника — департамент. S>Не в том, чтобы они были "не null"! А в том, что не может быть департамента без руководителя, и сотрудника вне департамента.
S>Минимальная корректная топология — это department1.Manager = employee1, и employee1.Department = department1. S>Нет никакого "EmptyDepatment" — кто у него будет руководителем? И нет никакого EmptyEmployee — в каком департаменте он будет работать? S>Даже если мы возьмём и "как-то" породим эту минимальную пару, обойдя ограничение языка, нам это никак не поможет. S>Потому что мы сможем сконструировать, например, департамент departmentX = new Department() { Manager = EmptyEmployee}. S>И этот департамент с точки зрения бизнес-логики ничуть не лучше, чем департамент, у которого .Manager == null. S>Не может "никто" быть руководителем департамента — бизнес-логика такая! S>Мы, собственно, хотим убрать изо всего кода бесчисленных проверок вида if(dept.Manager == null) log.Warning("Обнаружен департамент без руководителя — не можем подготовить приказ о начислении премий") else RegisterBonus(dept.Manager). Всё, что вы можете добиться с NullObject — это замены этого кода на if(dept.Manager == EmptyEmployee) log.Warning("Обнаружен департамент без руководителя — не можем подготовить приказ о начислении премий") else RegisterBonus(dept.Manager)
Sinclair не читатель. Еще раз какой узел следующий для последнего узла?
Empty нужны для обозначения, что этот объект дефолтный. У него можно вызвать методы, получить свойства.
Так же его нельзя присваивать свойствам которые должны иметь недефолтные значения и это проверяется в сеттерах или конструкторах.
Можно помечать такие свойства атрибутами и проверять через SG
Все тоже, что и со string.Empty
и солнце б утром не вставало, когда бы не было меня
Re[6]: C# [Proposal] init block for safe initialization of complex
Здравствуйте, Serginio1, Вы писали:
S>Sinclair не читатель.
Нет, это Serginio1 не читатель.
S>Еще раз какой узел следующий для последнего узла?
Зависит от используемых инвариантов. Например, у "последнего" узла следующим может быть null.
Это позволяет, к примеру, делать простое итерирование:
public static IEnumerable<Node> EnumerateForward(Node? list) {
while (list) {
yield return list;
list = list.Next;
}
}
Это работает, если для нас такая топология является нормальной. В такой топологии никакой пользы от EmptyNode нету.
Если нет, то у нас совсем другой инвариант — за нодой должна идти валидная нода. И в ней тоже нет никакой пользы от EmptyNode.
S> Empty нужны для обозначения, что этот объект дефолтный. У него можно вызвать методы, получить свойства.
Я вам в третий раз пишу: дефолтные объекты не помогают решить проблему, сформулированную Владом. У него проблема не в том, что у null нельзя вызывать методы или получать свойства. S>Так же его нельзя присваивать свойствам которые должны иметь недефолтные значения и это проверяется в сеттерах или конструкторах.
Прекрасно. Попробуйте запретить присваивание EmptyNode в сеттерах Next и Previous. Получите нерабочий код.
И вообще — вы переносите проверку в рантайм, а смысл не в том, чтобы неверный код падал при исполнении. Он и так падает при исполнении.
И делать проверки на null в конструкторе мы умели ещё четыре версии языка назад:
this.next = next ?? throw new ArgumentNullException(nameof(next))
S>Можно помечать такие свойства атрибутами и проверять через SG
Это по-прежнему не решает исходную задачу. S> Все тоже, что и со string.Empty
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[7]: C# [Proposal] init block for safe initialization of complex
Здравствуйте, Sinclair, Вы писали:
S>Здравствуйте, Serginio1, Вы писали:
S>>Sinclair не читатель. S>Нет, это Serginio1 не читатель.
S>>Еще раз какой узел следующий для последнего узла? S>Зависит от используемых инвариантов. Например, у "последнего" узла следующим может быть null. S>Это позволяет, к примеру, делать простое итерирование: S>
S>Это работает, если для нас такая топология является нормальной. В такой топологии никакой пользы от EmptyNode нету.
угу с null ты получишь ошибку на null
и чем (list!=null) лучше
while (!list.IsEmpty)
S>Если нет, то у нас совсем другой инвариант — за нодой должна идти валидная нода. И в ней тоже нет никакой пользы от EmptyNode.
Есть такая же польза как и у string.Empty. Позволяет работать с методами и свойствами.
S>> Empty нужны для обозначения, что этот объект дефолтный. У него можно вызвать методы, получить свойства. S>Я вам в третий раз пишу: дефолтные объекты не помогают решить проблему, сформулированную Владом. У него проблема не в том, что у null нельзя вызывать методы или получать свойства.
У него проблема с отложенной инициализацией. Но и в его примере для последнего узла Next будет null
S>>Так же его нельзя присваивать свойствам которые должны иметь недефолтные значения и это проверяется в сеттерах или конструкторах. S>Прекрасно. Попробуйте запретить присваивание EmptyNode в сеттерах Next и Previous. Получите нерабочий код.
S>И вообще — вы переносите проверку в рантайм, а смысл не в том, чтобы неверный код падал при исполнении. Он и так падает при исполнении. S>И делать проверки на null в конструкторе мы умели ещё четыре версии языка назад: S>
S>this.next = next ?? throw new ArgumentNullException(nameof(next))
S>
Ограничение может быть не только на Empty, но и по другим свойствам. И вот Empty как раз хорош.
S>>Можно помечать такие свойства атрибутами и проверять через SG S>Это по-прежнему не решает исходную задачу. S>> Все тоже, что и со string.Empty S>
Исходная задача это заменить null и добавить проверку на присваивание!
Это можно сделать и добавив свои анализаторы отключив родные.
Но в итоге и это не решает проблему ибо null то остается!
и солнце б утром не вставало, когда бы не было меня
Re: C# [Proposal] init block for safe initialization of complex
init (var obj1 = new SomeType1(), var obj2 = new SomeType2())
{
obj1.Prop = obj2;
obj2.Prop = obj1;
// Local functions are allowed
void Connect() => obj1.Child = obj2;
Connect();
} // Compiler checks here that all non-nullable members are initialized
почему только локальные функции? Как бы чего не вышло? Установка свойства уже может вызывать кучу логики вполне себе внешней.
Да и пока не очень понял, так ли уж оно сильно надо? Например, чем плохо использовать обычные reference types? Или использовать нормальные Optional (которых правда в шарпе нет, но их можно подтянуть извне). Н-р почти во всех языках Next будет Optional/Maybe.
Вообще motivation часть как-то не раскрыта.
Re[8]: C# [Proposal] init block for safe initialization of complex
Здравствуйте, Serginio1, Вы писали:
S>угу с null ты получишь ошибку на null
И по крайней мере программа детерминированно упадёт. А с EmptyNode у нас может быть половина узлов в списке фиктивными, и программа будет делать вид, что работает. Вот только все метрики типа длин путей будут фуфельными.
S> и чем (list!=null) лучше S>
S>while (!list.IsEmpty)
S>
Обратный вопрос: чем list.IsEmpty лучше, чем list == null?
S> Есть такая же польза как и у string.Empty. Позволяет работать с методами и свойствами.
Нет никакой пользы. Сосредоточьтесь и постарайтесь писать по делу, а не абстрактную чушь.
S> У него проблема с отложенной инициализацией. Но и в его примере для последнего узла Next будет null
И это должен отловить компилятор!
S> Ограничение может быть не только на Empty, но и по другим свойствам. И вот Empty как раз хорош.
Опять вы пишете что-то бессвязное.
S> Исходная задача это заменить null и добавить проверку на присваивание!
Нет. Исходная задача — это гарантировать, что при обработке графа никогда не встретится непроинициализированный узел S> Это можно сделать и добавив свои анализаторы отключив родные. S> Но в итоге и это не решает проблему ибо null то остается!
Ну так вам об этом и говорят.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[9]: C# [Proposal] init block for safe initialization of c
S>Обратный вопрос: чем list.IsEmpty лучше, чем list == null?
Тем, что кой кому не нужен Nullable reference
S>> Исходная задача это заменить null и добавить проверку на присваивание! S>Нет. Исходная задача — это гарантировать, что при обработке графа никогда не встретится непроинициализированный узел S>> Это можно сделать и добавив свои анализаторы отключив родные. S>> Но в итоге и это не решает проблему ибо null то остается! S>Ну так вам об этом и говорят.
Но в итоге у него код не пройдет проверку! Так как Next для последнего и Previous для первого будет null!
Никто не мешает добавить свой анализатор без изменения языка!
А использование Empty объекта значительно лучше чем null!
и солнце б утром не вставало, когда бы не было меня
Здравствуйте, Serginio1, Вы писали:
S> Тем, что кой кому не нужен Nullable reference А вам не приходило в голову задуматься: а почему этому кой-кому не нужен Nullable Reference? Если не приходило — перечитайте стартовый пост, там всё написано.
S> Но в итоге у него код не пройдет проверку! Так как Next для последнего и Previous для первого будет null!
Именно это и прекрасно. Компилятор потребует от него добавить инициализацию для этих свойств, и только тогда успокоится. Это и есть статическая гарантия того, что в рантайме он никогда не напорется ни на Null, ни на EmptyNode. S>Никто не мешает добавить свой анализатор без изменения языка!
Ок, предположим, у нас есть такой анализатор. Накидайте пример пользовательского кода с его применением.
S>А использование Empty объекта значительно лучше чем null!
Нет. Восклицательные знаки аргументом в споре не являются. Повторюсь: я знаком с Null Object Pattern и понимаю не только его преимущества, но и недостатки.
В данной задаче этот паттерн противопоказан как раз тем, что мешает компилятору проверять инициализированность. Потому что понятие "Ссылка на Node, для которой запрещено использование null" в компилятор встроено, а "ссылка на Node, для которой запрещено использование EmptyNode" — нет. Это не Алгол.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[2]: C# [Proposal] init block for safe initialization of complex
Здравствуйте, sergii.p, Вы писали:
SP>почему только локальные функции? Как бы чего не вышло?
Потому, что про нелокальные функции у компилятора нет информации. Я и с локальными-то сомневаюсь, что
Установка свойства уже может вызывать кучу логики вполне себе внешней.
SP>Да и пока не очень понял, так ли уж оно сильно надо? Например, чем плохо использовать обычные reference types?
avoiding unexpected nulls is a huge benefit, particularly in larger codebases where it's harder to keep track of the edge cases where things can be null by yourself
SP>Или использовать нормальные Optional (которых правда в шарпе нет, но их можно подтянуть извне). Н-р почти во всех языках Next будет Optional/Maybe.
Попробуйте переписать пример с использованием Optional. Станет хуже SP>Вообще motivation часть как-то не раскрыта.
По-моему так вполне внятно раскрыта. Что именно непонятно или вызывает сомнения?
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[11]: C# [Proposal] init block for safe initialization of
Здравствуйте, Sinclair, Вы писали:
S>Здравствуйте, Serginio1, Вы писали:
S>> Тем, что кой кому не нужен Nullable reference S> А вам не приходило в голову задуматься: а почему этому кой-кому не нужен Nullable Reference? Если не приходило — перечитайте стартовый пост, там всё написано.
S>> Но в итоге у него код не пройдет проверку! Так как Next для последнего и Previous для первого будет null! S>Именно это и прекрасно. Компилятор потребует от него добавить инициализацию для этих свойств, и только тогда успокоится. Это и есть статическая гарантия того, что в рантайме он никогда не напорется ни на Null, ни на EmptyNode.
Антон ты прикалываешься? Еще раз
Так как Next для последнего и Previous для первого будет null
Только добавив Empty компилятор и успокоится S>>Никто не мешает добавить свой анализатор без изменения языка! S>Ок, предположим, у нас есть такой анализатор. Накидайте пример пользовательского кода с его применением.
#продвинутый nullable enable
var obj1 = new SomeType1();
var obj2 = new SomeType2();
obj1.Prop = obj2;
obj2.Prop = obj1;
// Local functions are allowedvoid Connect() => obj1.Child = obj2;
Connect();
#продвинутый nullable disable
Здравствуйте, Sinclair, Вы писали:
S>Потому, что про нелокальные функции у компилятора нет информации.
то есть вызвать нелокальные функции в обработке property.set он может, а в init блоке он их уже не видит? Или может я не понял, что имеется в виду под локальными функциями? Локальные в модуле, классе, функции?
S>Попробуйте переписать пример с использованием Optional. Станет хуже
переписал. Не вижу, что стало хуже
using System;
using System.Collections.Generic;
using Optional;
public class Node<T>
{
public required T Value { get; set; }
public Option<Node<T>> Next { get; set; }
public Option<Node<T>> Previous { get; set; }
public Node(T value)
{
Value = value;
Next = Option.None<Node<T>>();
Previous = Option.None<Node<T>>();
}
public static Option<Node<T>> Init<T>(IEnumerable<T> values)
{
using var enumerator = values.GetEnumerator();
if (!enumerator.MoveNext())
return Option.None<Node<T>>();
var head = new Node<T>(enumerator.Current);
var current = head;
while (enumerator.MoveNext())
{
var newNode = new Node<T>(enumerator.Current);
current.Next = Option.Some(newNode);
newNode.Previous = Option.Some(current);
current = newNode;
}
return Option.Some(head);
}
public static IEnumerable<T> TraverseForward<T>(Option<Node<T>> start)
{
var current = start;
while (current.Match(
some: node =>
{
yield return node.Value;
current = node.Next;
return true;
},
none: () => false
)) { }
}
}
S>Что именно непонятно или вызывает сомнения?
ну вот привёл код выше. Чем секция init будет лучше? Позволит писать компактнее — спорно. Безопаснее — да нисколько. В общем motivation должен быть в виде сравнения. Вот у меня был такой workaround, а вот так получается с init блоком. Второй вариант очевидно лучше по следующим причинам ... Сейчас лично мне совсем не очевидно.
Re[12]: C# [Proposal] init block for safe initialization of
Здравствуйте, Serginio1, Вы писали:
S> Антон ты прикалываешься? Еще раз S>
S>Так как Next для последнего и Previous для первого будет null
Ок. Я, наверное, плохо объясняю очевидные лично мне вещи.
Давайте помедленнее. Вот у нас класс ноды с обязательными ссылками:
class Node
{
public required Node Next { get; set; }
public required Node Previous { get; set; }
public string Name {get; init;}
public Node(string name) => Name = name;
}
public static IEnumerable<string> Iterate(Node start) {
var current = start;
do {
yield return current.Name;
current = current.Next;
} while(current != start);
}
Существующий компилятор, попытка 1:
var node1 = new Node("Hello");
var node2 = new Node("World") { Next = node1, Previous = node1 };
foreach(var n in Iterate(node1))
Console.WriteLine(n);
Compile time error: Required member 'Program.Node.Next' must be set in the object initializer or attribute constructor.
Compile time error: Required member 'Program.Node.Previous' must be set in the object initializer or attribute constructor.
попытка 2:
var node1 = new Node("Hello") { Next = null!, Previous = null! };
var node2 = new Node("World") { Next = node1, Previous = node1) };
node1.Previous = node2;
foreach(var n in Iterate(node1)
Console.WriteLine(n);
Unhandled exception. System.NullReferenceException: Object reference not set to an instance of an object.
Попробуйте решить проблему при помощи EmptyNode.
S>
S>#продвинутый nullable enable
S> var obj1 = new SomeType1();
S> var obj2 = new SomeType2();
S>obj1.Prop = obj2;
S>obj2.Prop = obj1;
S>// Local functions are allowed
S>void Connect() => obj1.Child = obj2;
S>Connect();
S>#продвинутый nullable disable
S>
Ничего не получится. Если Prop — non-nullable required, то этот код не скомпилируется. Если он объявлен как nullable — то любой случайный дятел сможет его занулить после строчки #продвинутй nullable disable.
Ещё идеи есть?
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[13]: C# [Proposal] init block for safe initialization of
S>>#продвинутый nullable enable
S>> var obj1 = new SomeType1();
S>> var obj2 = new SomeType2();
S>>obj1.Prop = obj2;
S>>obj2.Prop = obj1;
S>>// Local functions are allowed
S>>void Connect() => obj1.Child = obj2;
S>>Connect();
S>>#продвинутый nullable disable
S>>
S>Ничего не получится. Если Prop — non-nullable required, то этот код не скомпилируется. Если он объявлен как nullable — то любой случайный дятел сможет его занулить после строчки #продвинутй nullable disable. S>Ещё идеи есть?
Еще раз #продвинутый nullable enable это анализатор проверки установки свойств
Он будет выдавать ошибку если не установлены все свойства obj1 и obj2
Написать свой анализатор кода не проблема.
Разумеется основная проверка non-nullable required будет отключена, так за проверку будет отвечать #продвинутый nullable enable
Если нет отмены то можно и так
#nullable disable
#продвинутй nullable enable
код c проверкой на присваивание всем полям не null значений
#продвинутй nullable disable
#nullable enable
Это намного лучше чем городить новый язык, так как может применяться ко всем версиям языка начиная C# 8.0
и солнце б утром не вставало, когда бы не было меня
S>var node1 = new Node("Hello") { Next = null!, Previous = null! };
S>var node2 = new Node("World") { Next = node1, Previous = node1) };
S>node1.Previous = node2;
S>foreach(var n in Iterate(node1)
S> Console.WriteLine(n);
S>
S>Unhandled exception. System.NullReferenceException: Object reference not set to an instance of an object.
S>Попробуйте решить проблему при помощи EmptyNode.
var node1 = new Node("Hello") { Next = Node.EmptyNode, Previous = Node.EmptyNode };
var node2 = new Node("World") { Next = node1, Previous = Node.EmptyNode) };
node1.Previous = node2;
foreach(var n in Iterate(node1)
Console.WriteLine(n);
И никаких System.NullReferenceException
и солнце б утром не вставало, когда бы не было меня