Optional Value. Уменьшение количества null reference-ов.
От: Alexander Polyakov  
Дата: 09.09.10 15:26
Оценка: 25 (3) +1
Довольно часто встречаются задачи, в которых фигурирует некоторое значение, но при некоторых условиях значение отсутствует. Для таких ситуаций предлагается использовать конструкцию OptionalValue, см. код ниже. Код каждого метода очень простой, поэтому лучше посмотреть сам код. Ниже буду описывать использование OptionalValue.

Катастрофа при Extract Method

Рассмотрим строчку кода:
string someMethodResult = new SomeClass1().SomeMethod1();
Сделаем обычный Extract Method:
string someMethodResult = ExtractedMethod1().SomeMethod1();

SomeClass1 ExtractedMethod1()
{
    return new SomeClass1();
}

Теперь заменим вызов конструктора "new SomeClass1()" на null.
В первом варианте получаем ошибку компиляции:
string someMethodResult = null.SomeMethod1(); //ошибка компиляции
Во втором варианте получаем ошибку в run time-е:
string someMethodResult = ExtractedMethod1().SomeMethod1(); //ошибка в run time-е

SomeClass1 ExtractedMethod1()
{
    return null;
}

Хочется иметь такую технику кодирования, чтобы Extract Method не переводил ошибки компиляции в ошибки run time-а.

Отсюда вывод: не использовать null в качестве возвращаемого значения! То есть набирать на клавиатуре null только:
  1. для передачи значений в уже имеющиеся методы (например, третий аргумент в методе PropertyInfo.SetValue),
  2. в операторе сравнения (фактически вариант а),
  3. может что-то забыл .
Но не для возврата значения.

Все null-ы мы таким образом не истребим, поскольку
  1. field-ы классов инициализируются null-ами,
  2. оператор "as" возвращает null,
  3. уже имеющиеся библиотеки поставляют null-ы.
Но наша цель более скромная – уменьшить количество null-ов. Кстати, по пунктам a и b еще можно кое-что отвоевать.

Как будет выглядеть приведенный выше пример в случае использования OptionalValue?
Делаем замену вызова конструктора "new SomeClass1()" на Nothing. Добиваемся, чтобы код компилировался, в итоге получаем первый вариант:
string someMethodResult = OptionalValue.Nothing<SomeClass1>().Process(
    value => value.SomeMethod1(),
    () => "[Экземпляр класса SomeClass1 не существует]");
второй вариант:
string someMethodResult = ExtractedMethod1().Process(
    value => value.SomeMethod1(),
    () => "[Экземпляр класса SomeClass1 не существует]");

IOptionalValue<SomeClass1> ExtractedMethod1()
{
    return OptionalValue.Nothing<SomeClass1>();
}
Таким образом, оба варианта ведут себя одинаково -- если код компилируется, то он работает.

Схожесть с System.Nullable

Да, OptionalValue очень похож на System.Nullable. Отличия:
  1. OptionalValue работает и для reference и для value типов, System.Nullable только для value типов,
  2. у OptionalValue generic параметр ковариантный.

The Maybe Monad

Да, OptionalValue является Maybe монадой. Но использование двух перегруженных методов ProcessValue часто оказывается удобнее использования разноименных монадных методов SelectMany и Select.

Query comprehension syntax

Методы SelectMany и Select позволяют использовать query comprehension syntax для OptionalValue.

Интересные ссылки на эту тему

Изобретатель null reference называет свое изобретение ошибкой на миллион долларов:

I call it my billion-dollar mistake. It was the invention of the null reference in 1965. At that time ...
http://en.wikipedia.org/wiki/C._A._R._Hoare


Общественность уже понимает необходимость разделения nullable типов и не nullable типов. Но пока в майнстримовых языка/платформах это не реализовано.

A more elegant solution that will never come true is to apply value type syntax for reference types: all references are implicitly not nulls for the compiler, but a question mark after a type (ie: «string?» or «Customer?») allows you to set null to the variable.
http://codevanced.net/post/One-Annotations-Way-Resharper.aspx

1.0 Non-Null Types Many errors in modern programs manifest themselves as null-dereference errors, suggesting the importance of a programming language providing the ability to discriminate between expressions that may evaluate to null and those that are sure not to (for some experimental evidence, see [24, 22]). In fact, we would like to eradicate all null dereference errors.
http://stackoverflow.com/questions/1943465/avoiding-null-reference-exceptions


В Википеии это описано под термином Option type.

Source code of OptionalValue

Методы GetValue/HasValue можно поменять местами с методом интерфеса IOptionalValue.Process, т.е. GetValue/HasValue будут в интерфейсе, а Process будет extension методом, который будет выражаться через GetValue/HasValue. В этом случае GetValue можно сделать свойством Value. Я этого не делаю, вот по какой причине. Не хочется иметь в базовом интерфейсе свойство, кидающее исключение. Метод GetValue желательно дергать как можно реже.

    public interface IOptionalValue<out TValue>
    {
        TResult Process<TResult>(Func<TValue, TResult> existFunc, Func<TResult> notExistFunc);
    }

    public static class OptionalValue
    {
        public static IOptionalValue<TValue> Nothing<TValue>()
        {
            return NotExistOptionalValue<TValue>.Instance;
        }

        public static IOptionalValue<TValue> AsOptionalValue<TValue>(
            this TValue value)
        {
            return new ExistOptionalValue<TValue>(value);
        }

        public static IOptionalValue<TTarget> ProcessValue<TValue, TTarget>(
            this IOptionalValue<TValue> optionalValue,
            Func<TValue, TTarget> func)
        {
            return optionalValue.ProcessValue(value => func(value).AsOptionalValue());
        }

        public static IOptionalValue<TTarget> ProcessValue<TValue, TTarget>(
            this IOptionalValue<TValue> optionalValue,
            Func<TValue, IOptionalValue<TTarget>> func)
        {
            return optionalValue.Process(func, Nothing<TTarget>);
        }

        public static void Process<TValue>(
            this IOptionalValue<TValue> optionalValue, 
            Action<TValue> existAction, 
            Action notExistAction)
        {
            optionalValue.Process(existAction.ToFunc(), notExistAction.ToFunc());
        }

        public static void ProcessValue<TValue>(
            this IOptionalValue<TValue> optionalValue, 
            Action<TValue> existAction)
        {
            Process(optionalValue, existAction, () => { });
        }

        public static IOptionalValue<TTarget> Select<TValue, TTarget>(
            this IOptionalValue<TValue> optionalValue,
            Func<TValue, TTarget> func)
        {
            return optionalValue.ProcessValue(func);
        }

        public static IOptionalValue<TTarget> SelectMany<TValue, TTarget>(
            this IOptionalValue<TValue> optionalValue,
            Func<TValue, IOptionalValue<TTarget>> func)
        {
            return optionalValue.ProcessValue(func);
        }

        public static IOptionalValue<T2> SelectMany<TValue, T1, T2>(
            this IOptionalValue<TValue> optionalValue,
            Func<TValue, IOptionalValue<T1>> func1,
            Func<TValue, T1, T2> func2)
        {
            return optionalValue.SelectMany(
                value => func1(value).Select(
                    value1 => func2(value, value1)
                         )
                );
        }

        public static bool HasValue<TValue>(
            this IOptionalValue<TValue> optionalValue)
        {
            return optionalValue.Process(delegate { return true; }, () => false);
        }

        public static TValue GetValue<TValue>(
            this IOptionalValue<TValue> optionalValue)
        {
            return optionalValue.Process(
                value => value,
                () =>
                    {
                        throw new InvalidOperationException(
                            string.Format("Optional value of '{0}' type has no value.", typeof (TValue)));
                    }
                );
        }

        public static TValue GetValueOrDefault<TValue>(
            this IOptionalValue<TValue> optionalValue)
        {
            return optionalValue.Process(value => value, () => default(TValue));
        }

        public static IOptionalValue<TValue> ToOptionalValue<TValue>(
            this TValue value) where TValue : class
        {
            return value == null ? Nothing<TValue>() : value.AsOptionalValue();
        }

        private class NotExistOptionalValue<TValue> : IOptionalValue<TValue>
        {
            public static readonly IOptionalValue<TValue> Instance = new NotExistOptionalValue<TValue>();

            private NotExistOptionalValue()
            {
            }

            public TResult Process<TResult>(
                Func<TValue, TResult> existFunc, Func<TResult> notExistFunc)
            {
                return notExistFunc();
            }
        }

        private class ExistOptionalValue<TValue> : IOptionalValue<TValue>
        {
            private readonly TValue value;

            public ExistOptionalValue(TValue value)
            {
                this.value = value;
            }

            public TResult Process<TResult>(
                Func<TValue, TResult> existFunc, Func<TResult> notExistFunc)
            {
                return existFunc(value);
            }
        }
    }

http://propertyexpression.codeplex.com/SourceControl/changeset/view/66209#1409174
http://propertyexpression.codeplex.com/SourceControl/changeset/view/66209#1409173
Re: Optional Value. Уменьшение количества null reference-ов.
От: IT Россия linq2db.com
Дата: 09.09.10 15:56
Оценка:
Здравствуйте, Alexander Polyakov, Вы писали:

AP>Довольно часто встречаются задачи, в которых фигурирует некоторое значение, но при некоторых условиях значение отсутствует. Для таких ситуаций предлагается использовать конструкцию OptionalValue, см. код ниже. Код каждого метода очень простой, поэтому лучше посмотреть сам код. Ниже буду описывать использование OptionalValue.


Это случайно не Null Object Pattern называется?
Если нам не помогут, то мы тоже никого не пощадим.
Re[2]: Optional Value. Уменьшение количества null reference-
От: Alexander Polyakov  
Дата: 09.09.10 16:43
Оценка:
Здравствуйте, IT, Вы писали:

IT>Это случайно не Null Object Pattern называется?

Скорее нет, чем да. Если пользоваться описаниями ссылка1, ссылка2, то Null Object Pattern предлагает делать кастомный интерфейс и кастомную реализацию Null Object-а для каждого случая. A OptionalValue один для всех .

Наиболее близкую ссылку я уже привел в первом посте Option type. Оттуда можно перейти, например, к реализации в Scala.
Re: Optional Value.
От: Qbit86 Кипр
Дата: 09.09.10 17:08
Оценка: 8 (1)
Здравствуйте, Alexander Polyakov.

В C# для этих целей использую Option из Elevate, в C++ — из Boost, ну а в F# свой есть.
Глаза у меня добрые, но рубашка — смирительная!
Re[3]: Optional Value. Уменьшение количества null reference-
От: IT Россия linq2db.com
Дата: 09.09.10 18:07
Оценка:
Здравствуйте, Alexander Polyakov, Вы писали:

IT>>Это случайно не Null Object Pattern называется?

AP>Скорее нет, чем да. Если пользоваться описаниями ссылка1, ссылка2, то Null Object Pattern предлагает делать кастомный интерфейс и кастомную реализацию Null Object-а для каждого случая. A OptionalValue один для всех .

Это всё элементарно решается с помощью дженериков.

AP>Наиболее близкую ссылку я уже привел в первом посте Option type. Оттуда можно перейти, например, к реализации в Scala.


Или в Немерле. Но без ПМ этим пользоваться не очень удобно.
Если нам не помогут, то мы тоже никого не пощадим.
Re: Optional Value. Уменьшение количества null reference-ов.
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 09.09.10 18:14
Оценка: 18 (2) +1
Здравствуйте, Alexander Polyakov, Вы писали:

Как-то слишком многословно и к тому же не компилится, не хватает ToFunc.

AP>    public interface IOptionalValue<out TValue>
AP>    {
AP>        TResult Process<TResult>(Func<TValue, TResult> existFunc, Func<TResult> notExistFunc);
AP>    }
AP>
AP>    [skipped]


Проблема этого интерфейса что он не энфорсит контракт, это при том что кроме Some\Just и None реализаций не нужно. Но я могу сколько угодно своих реализаций подсовывать.


Вот мой вариант, завалявшихся со стародавних времен:

public abstract class Option<T>
{
    class Just:Option<T>
    {
        T value;

        public Just(T value)
        {
            this.value = value;
        }

        public override bool HasValue
        {
            get { return true; }
        }

        public override T Value
        {
            get { return this.value; }
        }
    }

    class NoneClass:Option<T>
    {
        public override bool HasValue
        {
            get { return false; }
        }

        public override T Value
        {
            get { throw new InvalidOperationException("Value is empty"); }
        }
    }

    public abstract T Value { get; }
    public abstract bool HasValue { get; }

    private Option()
    {
    }

    public static readonly Option<T> None = new NoneClass();
    public static Option<T> Create(T value)
    {
        if (value == null)
        {
            throw new ArgumentNullException("value");
        }
        return new Just(value);
    }

    public static implicit operator Option<T>(T value)
    {
        if (value != null)
        {
            return Create(value);
        }
        else
        { 
            return None;
        }
    }

    public static explicit operator T(Option<T> option)
    {
        return option.Value;
    }
}

public static class Option
{
    public static Option<T> None<T>()
    {
        return Option<T>.None;
    }

    public static Option<T> ToOption<T>(this T value)
    {
        return value;
    }
        
    public static Option<V> SelectMany<T,V>(this Option<T> value, Func<T, Option<V>> selector)
    {
        return value.HasValue ? selector(value.Value) : None<V>();
    }

    public static Option<V> SelectMany<T,K,V>(this Option<T> value, Func<T, Option<K>> selector, Func<T,K,V> resultSelector)
    {
        return value.SelectMany(left => selector(left).SelectMany(right => resultSelector(left, right).ToOption()));
    }

    public static Option<V> Select<T,V>(this Option<T> value, Func<T, V> selector)
    {
        return value.SelectMany(v => selector(v).ToOption());
    }

    public static Option<T> Where<T>(this Option<T> value, Func<T, bool> predicate)
    {
        return value.SelectMany(v => predicate(v) ? v.ToOption() : None<T>());
    }

    public static V Process<T,V>(this Option<T> value, Func<T, V> success, Func<V> fail)
    {
        return value.HasValue ? success(value.Value) : fail();
    }

}

class Program
{
    static void Main(string[] args)
    {
        var z1 = from c1 in 2.ToOption()
                    from c2 in 3.ToOption()
                    select c1 + c2;
        Console.WriteLine(z1.HasValue);
        Console.WriteLine(z1.Value);

        var z2 = from c1 in 2.ToOption()
                    from c2 in 3.ToOption()
                    where c2 > 3
                    select c1 + c2;
        Console.WriteLine(z2.HasValue);
    }
}
Re[4]: Optional Value. Уменьшение количества null reference-
От: samius Япония http://sams-tricks.blogspot.com
Дата: 09.09.10 18:14
Оценка:
Здравствуйте, IT, Вы писали:

IT>Здравствуйте, Alexander Polyakov, Вы писали:


IT>Или в Немерле. Но без ПМ этим пользоваться не очень удобно.


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

что-то типа

// псевдокод
static TResult Process(
     this IOptionalValue optionalValue, 
     Func<TValue, TResult> existFunc, 
     Func<TResult> notExistFunc)
{
    if(optionalValue is ExistOptionalValue)
       return existFunc(optionalValue.Value)
    else 
       return notExistFunc();
}
Re[2]: Optional Value. Уменьшение количества null reference-
От: samius Япония http://sams-tricks.blogspot.com
Дата: 09.09.10 18:17
Оценка:
Здравствуйте, gandjustas, Вы писали:

G>Вот мой вариант, завалявшихся со стародавних времен:


G>
G>    public static V Process<T,V>(this Option<T> value, Func<T, V> success, Func<V> fail)
G>    {
G>        return value.HasValue ? success(value.Value) : fail();
G>    }

G>


Да, мне это ближе!
Re[4]: Optional Value. Уменьшение количества null reference-
От: Alexander Polyakov  
Дата: 09.09.10 21:07
Оценка:
Здравствуйте, IT, Вы писали:

IT>>>Это случайно не Null Object Pattern называется?

AP>>Скорее нет, чем да. Если пользоваться описаниями ссылка1, ссылка2, то Null Object Pattern предлагает делать кастомный интерфейс и кастомную реализацию Null Object-а для каждого случая. A OptionalValue один для всех .
IT>Это всё элементарно решается с помощью дженериков.
В описании паттерна четко фигурирует кастомный интерфейс (или абстрактный класс) с кастомным набором методов.

A Linked list is either a head element and a tail which is a list or empty (i.e. Null). Thus is makes sense to model an abstract linked list with a class List which has two methods getTail and accept as per the Visitor pattern [GHJV95, page 331].
http://www.cs.oberlin.edu/~jwalker/nullObjPattern/

Каким боком ты сюда дженерики вставишь? Уберешь этот кастомный интерфейс? Тогда это будет совсем не тот паттерн.
Такое ощущение, что ты ориентируешься только на слова в названии паттерна, попробуй почитать описание паттерна.

IT>Это всё элементарно решается с помощью дженериков.

Еще бы неплохо type inference более менее продвинутый иметь. Попробуй реализуй на Java-е.

IT>Но без ПМ этим пользоваться не очень удобно.

Пример кода с использование ПМ можно?
Re[5]: Optional Value. Уменьшение количества null reference-
От: Alexander Polyakov  
Дата: 09.09.10 21:25
Оценка:
Здравствуйте, samius, Вы писали:

S>Кстати, проще было бы реализовать его именно через ПМ (то есть через тест типа)

S>что-то типа
S>
S>// псевдокод
S>static TResult Process(
S>     this IOptionalValue optionalValue, 
S>     Func<TValue, TResult> existFunc, 
S>     Func<TResult> notExistFunc)
S>{
S>    if(optionalValue is ExistOptionalValue)
S>       return existFunc(optionalValue.Value)
S>    else 
S>       return notExistFunc();
S>}
S>

Реализация не важна, в первую очередь интересует простота использования.
Re[2]: Optional Value.
От: Alexander Polyakov  
Дата: 09.09.10 22:05
Оценка:
Здравствуйте, Qbit86, Вы писали:

Q>В C# для этих целей использую Option из Elevate

  1. Нет методов Process, нет перегруженных методов ProcessValue. А это ценно.
  2. Нет ковариантности generic параметра. Поэтому нельзя пользоваться полиморфизмом по generic параметру, что очень печально.
  3. Метод Select по правилам должен называться SelectMany.
  4. Нет методов SelectMany и Select для поддержки query comprehension syntax.
  5. Метод Some должен быть extension методом.
Используйте OptionalValue из Property Expression . Там это даже раньше появилось. По результатам обсуждения на форумах смерджу последнюю версию в Trunk, и выпущу новый релиз.
Re[3]: Optional Value.
От: Qbit86 Кипр
Дата: 09.09.10 22:16
Оценка: :)
Здравствуйте, Alexander Polyakov, Вы писали:

AP>Метод Select по правилам должен называться SelectMany.


По некоторым другим правилам должен называться и вовсе Bind, евпочя ;)
Глаза у меня добрые, но рубашка — смирительная!
Re[2]: Optional Value. Уменьшение количества null reference-
От: Alexander Polyakov  
Дата: 09.09.10 22:52
Оценка:
Здравствуйте, gandjustas, Вы писали:

G>Как-то слишком многословно

Где ты увидел многословность? Объем совпадает с твоим, просто у тебя отсутствуют некоторые полезные методы.

G>и к тому же не компилится, не хватает ToFunc.

Это мелочи, к тому же там была ссылка на сорци полного солюшена. Там все просто. Вот без ToFunc:
public static void Process<TValue>(
    this IOptionalValue<TValue> optionalValue, 
    Action<TValue> existAction, 
    Action notExistAction)
{
    optionalValue.Process(
        value =>
            {
                existAction(value);
                return new {};
            },
        () =>
            {
                notExistAction();
                return new {};
            });
}


G>Вот мой вариант, завалявшихся со стародавних времен:

G>
G>public abstract class Option<T>

Главный недостаток твоего варианта -- нет ковариантности generic параметра. Поэтому нельзя пользоваться полиморфизмом по generic параметру, что очень печально.

G>Проблема этого интерфейса что он не энфорсит контракт, это при том что кроме Some\Just и None реализаций не нужно. Но я могу сколько угодно своих реализаций подсовывать.

Это замечание незначительно само по себе, а с учетом отсутствия ковариантности generic параметра в альтернативном (твоем) варианте значимость этого замечания пропадает вовсе.
Re[3]: Optional Value. Уменьшение количества null reference-
От: Alexander Polyakov  
Дата: 09.09.10 23:17
Оценка:
Здравствуйте, samius, Вы писали:

G>>
G>>    public static V Process<T,V>(this Option<T> value, Func<T, V> success, Func<V> fail)
G>>    {
G>>        return value.HasValue ? success(value.Value) : fail();
G>>    }
G>>

S>Да, мне это ближе!
Как я уже писал в первом посте. Метод Process и методы GetValue/HasValue выражаются друг через друга в обе стороны, ситуация симметричная. Да, можно ввести в интерфейс IOptionalValue свойства Value и HasValue, а метод Process наоборот перенести в extension методы (в моем текущем проекте именно так). Мои аргументы в пользу варианта из первого поста:
  1. Не хочется иметь в базовом интерфейсе свойство, кидающее исключение.
  2. В случае если не срабатывает type inference, то для extension метода Process придется явно прописывать значения обоих generic параметров. А если Process является методом интерфейса, то прописывать придется значение только одного generic параметра.

Вы считаете эти аргументы незначительными?

По производительности вариант со свойствами Value/HasValue, наверное, лучше.
Re: Optional Value. Уменьшение количества null reference-ов.
От: Sinix  
Дата: 10.09.10 00:32
Оценка:
Здравствуйте, Alexander Polyakov, Вы писали:

AP>Хочется иметь такую технику кодирования, чтобы Extract Method не переводил ошибки компиляции в ошибки run time-а.

Правильное решение этой проблемы — использование контрактов и статической верификации. Если бы оно ешё работало...

AP> оба варианта ведут себя одинаково -- если код компилируется, то он работает.

Да. Но, вместо того, чтобы решать проблему в её источнике — методе, вы передаёте ответственность по соблюдению контракта всему остальному коду. NullReferenceException хотя бы гарантировал, что при ошибке в вашем коде программа упадёт. OptionalValue маскирует исходную проблему и источник её возникновения: "ой, у нас тут значения нет — фиг с ним, подсунем что-нить"
Re[4]: Optional Value. Уменьшение количества null reference-
От: samius Япония http://sams-tricks.blogspot.com
Дата: 10.09.10 04:17
Оценка: 16 (1)
Здравствуйте, Alexander Polyakov, Вы писали:

AP>Здравствуйте, samius, Вы писали:


G>>>
G>>>    public static V Process<T,V>(this Option<T> value, Func<T, V> success, Func<V> fail)
G>>>    {
G>>>        return value.HasValue ? success(value.Value) : fail();
G>>>    }
G>>>

S>>Да, мне это ближе!
Ближе собственно тем, что при работе с АТД используется один неполиморфный метод.

AP>Как я уже писал в первом посте. Метод Process и методы GetValue/HasValue выражаются друг через друга в обе стороны, ситуация симметричная. Да, можно ввести в интерфейс IOptionalValue свойства Value и HasValue, а метод Process наоборот перенести в extension методы (в моем текущем проекте именно так).

AP>Мои аргументы в пользу варианта из первого поста:
AP>

    AP>
  1. Не хочется иметь в базовом интерфейсе свойство, кидающее исключение.
    AP>
  2. В случае если не срабатывает type inference, то для extension метода Process придется явно прописывать значения обоих generic параметров. А если Process является методом интерфейса, то прописывать придется значение только одного generic параметра.
    AP>
1. Не нужны эти свойства, если пойти через тест типа аки в ПМ (здесь
Автор: samius
Дата: 09.09.10
).

2. Можно пример?

AP>Вы считаете эти аргументы незначительными?

нет.

AP>По производительности вариант со свойствами Value/HasValue, наверное, лучше.

вряд ли намного лучше. Копейки тут выжимать нет смысла, т.к. вся эта кухня куда дороже (по производительности) чем проверка на null.

З.Ы. В некоторых случаях может оказаться удобнее OptionalValue, построенный не на АТД, а на кортеже флага и значения. Такая штука шикарно передается в качестве параметров по умолчанию. Описано у desco.
Re[3]: Optional Value. Уменьшение количества null reference-
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 10.09.10 05:39
Оценка:
Здравствуйте, Alexander Polyakov, Вы писали:

AP>Здравствуйте, gandjustas, Вы писали:


G>>Как-то слишком многословно

AP>Где ты увидел многословность? Объем совпадает с твоим, просто у тебя отсутствуют некоторые полезные методы.
Твой код в полтора раза больше.
Какие например?
У меня еще есть Where комбинатор.

G>>Вот мой вариант, завалявшихся со стародавних времен:

G>>
G>>public abstract class Option<T>

AP>Главный недостаток твоего варианта -- нет ковариантности generic параметра. Поэтому нельзя пользоваться полиморфизмом по generic параметру, что очень печально.
Приведи пример где ковариантность необходима.

G>>Проблема этого интерфейса что он не энфорсит контракт, это при том что кроме Some\Just и None реализаций не нужно. Но я могу сколько угодно своих реализаций подсовывать.

AP>Это замечание незначительно само по себе,
Да ну? Сломать контракт это фигня?

AP>а с учетом отсутствия ковариантности generic параметра в альтернативном (твоем) варианте значимость этого замечания пропадает вовсе.

Приведи пример где ковариантность необходима.
Re[2]: Optional Value. Уменьшение количества null reference-
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 10.09.10 05:46
Оценка: 3 (2) +1
Здравствуйте, Sinix, Вы писали:

S>Здравствуйте, Alexander Polyakov, Вы писали:


AP>>Хочется иметь такую технику кодирования, чтобы Extract Method не переводил ошибки компиляции в ошибки run time-а.

S>Правильное решение этой проблемы — использование контрактов и статической верификации. Если бы оно ешё работало...

AP>> оба варианта ведут себя одинаково -- если код компилируется, то он работает.

S>Да. Но, вместо того, чтобы решать проблему в её источнике — методе, вы передаёте ответственность по соблюдению контракта всему остальному коду. NullReferenceException хотя бы гарантировал, что при ошибке в вашем коде программа упадёт. OptionalValue маскирует исходную проблему и источник её возникновения: "ой, у нас тут значения нет — фиг с ним, подсунем что-нить"

Ну это неверно. Maybe монада и была придумана, как средство описать частичные вычисления, которые могут не возвращать результат в некоторых случаях. Правда имеет смысл как можно дольше оставаться в этой самой монаде, а метод Process такой способностью не обладает.
Re[3]: Optional Value. Уменьшение количества null reference-
От: Sinix  
Дата: 10.09.10 06:19
Оценка: +1
Здравствуйте, gandjustas, Вы писали:

G>Ну это неверно. Maybe монада и была придумана, как средство описать частичные вычисления, которые могут не возвращать результат в некоторых случаях. Правда имеет смысл как можно дольше оставаться в этой самой монаде, а метод Process такой способностью не обладает.


Не спорю. Только топикстартер её использует для других целей:

Хочется иметь такую технику кодирования, чтобы Extract Method не переводил ошибки компиляции в ошибки run time-а.

Re: Optional Value. Уменьшение количества null reference-ов.
От: Sinclair Россия https://github.com/evilguest/
Дата: 10.09.10 07:28
Оценка: +3
Здравствуйте, Alexander Polyakov, Вы писали:

AP>Отсюда вывод: не использовать null в качестве возвращаемого значения! То есть набирать на клавиатуре null только:

AP>Но наша цель более скромная – уменьшить количество null-ов. Кстати, по пунктам a и b еще можно кое-что отвоевать.
Не очень понятен смысл неиспользования null-ов. Проблема, я так понимаю, в том, что NRE упадёт слишком поздно?

AP>Как будет выглядеть приведенный выше пример в случае использования OptionalValue?

AP>Делаем замену вызова конструктора "new SomeClass1()" на Nothing. Добиваемся, чтобы код компилировался, в итоге получаем первый вариант:
AP>
AP>string someMethodResult = OptionalValue.Nothing<SomeClass1>().Process(
AP>    value => value.SomeMethod1(),
AP>    () => "[Экземпляр класса SomeClass1 не существует]");
AP>
второй вариант:

AP>
AP>string someMethodResult = ExtractedMethod1().Process(
AP>    value => value.SomeMethod1(),
AP>    () => "[Экземпляр класса SomeClass1 не существует]");

AP>IOptionalValue<SomeClass1> ExtractedMethod1()
AP>{
AP>    return OptionalValue.Nothing<SomeClass1>();
AP>}

AP>Таким образом, оба варианта ведут себя одинаково -- если код компилируется, то он работает.
Не очень понял, что будет происходить при возврате null из SomeMethod1(). Будет выброшен NRE? Или в консоль уедет "[Экземпляр класса SomeClass1 не существует]"?

AP>

The Maybe Monad

Да, OptionalValue является Maybe монадой. Но использование двух перегруженных методов ProcessValue часто оказывается удобнее использования разноименных монадных методов SelectMany и Select.



AP>Общественность уже понимает необходимость разделения nullable типов и не nullable типов. Но пока в майнстримовых языка/платформах это не реализовано.

AP>

AP>A more elegant solution that will never come true is to apply value type syntax for reference types: all references are implicitly not nulls for the compiler, but a question mark after a type (ie: «string?» or «Customer?») allows you to set null to the variable.
AP>http://codevanced.net/post/One-Annotations-Way-Resharper.aspx

AP>

AP>1.0 Non-Null Types Many errors in modern programs manifest themselves as null-dereference errors, suggesting the importance of a programming language providing the ability to discriminate between expressions that may evaluate to null and those that are sure not to (for some experimental evidence, see [24, 22]). In fact, we would like to eradicate all null dereference errors.
AP>http://stackoverflow.com/questions/1943465/avoiding-null-reference-exceptions


AP>В Википеии это описано под термином Option type.


AP>

Source code of OptionalValue

Методы GetValue/HasValue можно поменять местами с методом интерфеса IOptionalValue.Process, т.е. GetValue/HasValue будут в интерфейсе, а Process будет extension методом, который будет выражаться через GetValue/HasValue. В этом случае GetValue можно сделать свойством Value. Я этого не делаю, вот по какой причине. Не хочется иметь в базовом интерфейсе свойство, кидающее исключение. Метод GetValue желательно дергать как можно реже.

Не вижу ничего плохого в выбросе исключения. Вопрос только в том, когда именно исключение кидать:
1. При компиляции, при попытке засунуть null в non-nullable объект => вроде бы невозможно на текущем уровне развития компилятора
2. При исполнении, при попытке прочитать null в non-nullable объекта => то же самое, что сейчас делает среда
3. При исполнении, при попытке засунуть null в non-nullable объект => вот это было бы полезно, но в ваших примерах я этого не вижу.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.