Pull to refresh

Comments 41

Вопрос — а зачем для такой примитивной вещи, как Maybe (которая на шарпе реализуется в 10 строчек кода) делать аж целое видео на 15 минут?

Это можно было уложить в одну статью на 3 экрана, которая читается за 3 минуты.
Вероятно это было сделано для какого-нибудь RussianTech, а так у Димы уже давно вышла статья про монадический код и расширения на языке C# для его реализации. Давно пользуюсь этим подходом, очень удобно.
Не увидел там монады. В видео то же самое?
Я только не понимаю, в чем прикол вместо " != null" писать ".ReturnSuccess()", по-моему это уже лишнее.
Если так делать, то получится return x.With(...).(большая цепочка) != null. Выглядит неидеально.
Преимущество, как уже сказал автор, в читаемости кода. Каскадирование заменяется последовательной цепочкой.
В частности, ради примера нечитаемости, меня в свое время помнится разбесили тернарные операции. При втором уровне вложения на строке были три знака вопроса и три же двоеточия. Пока не раскидал выражение на if-else не понял как оно работатет. Количество строк кода увеличилось раз в 10, но читаемость улучшилась.

Над именами можно конечно поработать, например вместо ReturnSuccess я бы поставил более говорящее за себя IsNotNull.
Кстати, интересно, как лучше модифицировать эту проверку для работы со строками, чтобы проверять не только на null, но и на string.empty.
Ну это наверное просто, перегрузить With так чтобы если TResult=string то делать IsNullOrWhitespace.
Нет, ну я бы понял еще, если бы была полноценная реализация монады, но тут же реализация только для классов, а в таких условиях специальный метод — это просто лишняя сущность.
Сущностей итак создается много — в этом специфика паттерна.
Писать ReturnSuccess — просто ошибка. Учить этому — преступление против всех разработчиков.

" != null" — писать не совсем красиво. Но это истинное уродство. Это неприятный запах настоящей проблемы: использование null как варианта возвращаемого значения метода, которое каждый раз надо обрабатывать. Вообще null не особенно подходит для обработки как результата операции — он ведет себя не так как остальные объекты. И это порождает дополнительные условия.

Когда пишем ReturnSuccess — прыскаем запах дезодорантом.

Я рекомендую в большинстве случаев считать что null быть не может — и ожидать NullReferenceException или на худой конец ArgumentNullException кидать — и покрывать тестами эти ситуации. А на крайняк использовать паттерн Null Object.
Допустим есть метод суть которого в поиске некоторого объекта, что нужно вернуть если объект не найден если null не вариант?
В зависимости от того, для чего Вам этот объект нужен.

Допустим есть метод который ищет один объект и может его не найти.

Возвращая null вы как бы накладываете на объект 2 ответственности — внешний интерфейс объекта и информация об успехе его поиска. Отсюда и проблемы.
И тут проверка на null как раз отделяет одну ответственность от другой — она необходима.
То есть вы как будто бы запихали в один контейнер 2 объекта а потом разделаете их.
Это как работать с nullable типами в дот нете.
Состояние null не есть часть интерфейса по работе с обьектом. Те, кто работает с ним не должны получить null.

Можно сделать проверку на null после поиска и отправить объект дальше.
Можно использовать шаблон TryDoSomething, который широко используется.
Можно вернуть объект который является null-ом, но поддерживает интерфейс доступа к обьекту (паттерн null object).

Выбирайте.

Есть еще вариант использовать какой то особенный DSL вроде предложенного выше или биндинга для WPF, но такого костыля можно избежать, если сразу грамотно выстроить поведение слоев или модулей приложения.
«Можно сделать проверку на null после поиска и отправить объект дальше.
Можно использовать шаблон TryDoSomething, который широко используется.
Можно вернуть объект который является null-ом, но поддерживает интерфейс доступа к обьекту (паттерн null object).»
А можно просто вернуть null.
Старик Оккама смотрит на вас с неодобрением.
Старик Оккама смотрит с неодобрением на концепцию языка c# и платформы .net.

Если мы будем считать что возврат null — это ожидаемое поведение, то нам придется обрабатывать этот null по особенному на всех уровнях приложения. Это будет стоить нам немалых ресурсов. Можно немножко сократить накладные расходы инструментами вроде предложенных автором статьи.

Есть механизмы этого избежать. Я их перечислил. И они работают с разной эффективностью в зависимости от ситуации. Но главная идея — считать что null не может вернуться никогда внутри приложения.
Нет, нет и еще раз нет! Ловить NRE в таких случаях — вы хоть представляете как этот код будет выглядеть?
Я ничего не предлагаю ловить. Я предлагаю только кидать, ну или позволить кому то другому кидать.
Ловит пускай комплексная система отлова ошибок в приложении.

На самом деле мой заминусованный комментарий не относился к оценке проделанной Вами работы в демонстрации монады с помощью DSL — тут я снимаю шляпу.
Это был комментарий на конкретный вопрос к тому же не разобравшись полностью.

Я лишь считаю своим долгом обозначить, что надо понимать, что предложенная схема — это решение проблемы, которая имеет классические решения. И предложенное решение все таки не совсем органично смотрится в рамках остального кода. Совсем другой стиль.

Возможно стоит обозначить где именно стоит использовать такой подход.
А где пихать его не стоит. Все таки в биндинге данных на вью — там где описанная Вами проблема особенно актуальна — нам все таки вполне хватает встроенных инструментов.
Сколько эмоций, сколько боли в Ваших словах!
Предполагается что Return вызывается в конце, чем тогда не устраивает elvis-оператор ?? returnValue. Реализация Do или ForEach — это bad practice.
Это стилистическое предпочтение. К тому же, если не только проверять на `null` но делать еще что-то (например try-catch) то аргумент уже не работает.
Реализация Do или ForEach — это bad practice.можете объяснить почему? Я всегда подозревал это, но у меня нет адекватных доказательств.
Мне нравится, как проверка на null реализована в CoffeeScript. Вернее, наверное, так еще много где реализовано, просто я видел только в CoffeeScript'е.

result = person?.address?.houseName?

Компилируется это в следующий JS:

var result, _ref;

result = (typeof person !== "undefined" && person !== null ? (_ref = person.address) != null ? _ref.houseName : void 0 : void 0) != null;
Так и должно быть! Надеюсь, что разработчики C# учтут это в будущих версиях продукта и мы не будем городить кучу монад ради таких простых вещей.
Если уже говорить про JS то как бы в нем это все и так достаточно тривиально делается:

      var person = { address: { postalCode:12345 } };
      
      var postalCode = person 
                    && person.address 
                    && person.address.postalCode;

      var spouse =  person 
                    && person.spouse 
                    && person.spouse.firstName                
                    || "<not married>";
                    
      alert(postalCode);
      alert(spouse);
Ага. Но, во-первых, в CofeeScript вариант более читабельный.
А во-вторых, ваш вариант не сработает для пустых строк (бывает, что у них другая семантика, чем у null/undefined). Также, он не сработает для чисел со значением 0, для булевых флагов со значением false.
Запись покороче, но проблема с цепочками-то не решена — надо каждый раз прописывать все от начала.

Вариант coffeeScript и предложенное решение на C# эту проблему решает.

Кроме того, в варианте C# в цепочке можно вызвать методы. Типа:
var lang = db.getUser(id).With(user => db.GetLanguageInfo(user.lang));
Очень понравился паттерн. Сразу захотелось попробовать на практике, спасибо за видео. Напомнило паттерн «билдер», похожее каскадирование инструкций и такой же восторг от полезности паттерна.
Жду других паттернов.
В билдере появляются промежуточные объекты, а тут их нет.
1) Не объекты, а объект. Один билдер на один конструируемый immutable объект.
2) Я всего лишь сказал «напомнило». Тем, что конструкции цепочкой выстраиваются.

string append = (new StringBuilder(«a»))
.Append(«pp»)
.Append(«en»)
.Append(«d»)
.ToString();
Очень нравятся подкасты Дмитрия, я уже давно слушаю их. Мои коллеги теперь побаиваются моего кода :))))) потому что там монады :))))) Хотя все мои уверения что это намного понятней, чем городить кучу проверок, не помогают :) Пользуюсь монадами, работают надежно и делают код наглядней. Дмитрий, спасибо Вам за то, что Вы остаетесь с нами и делитесь с Нами своим бесценным опытом!
Вот кстати встретил недавно небольшой парсер на C#, основанный на монадах. Тоже можно взглянуть. code.google.com/p/sprache/
Монадические парсеры все же лучше писать на F# :)
все-таки все эти попытки впихнуть монады в языки без паттерн-матчинга и for comprehensions выглядят несколько вымученными
Конечно вымученны, но альтернатив-то нет.
«for comprehensions» — это про скалу?

Аналог в C# — LINQ.
да, про Scala. Я уже тоже подумал про LINQ, и увидел Ваш развернутый комментарий внизу.
А как компилятор реализует эту цепочку, если реальным вызовом функций (with, if ....), то такое решение может быть не очень производительным, например если фильтруется много объектов и первое же условие отфильтровывает большую часть их, если просто без помощи монадической записи то последующие проверки на null не происходят.
Ха, естественно у этого подхода есть «стэковая стоимость», особенно если цепочка большая и где-то в начале попался null. Но то что мы теряем в производительности мы выигрываем в экспрессивности.
А если не придумывать свои названия методов, а назвать их как в LINQ, то можно будет LINQ-синтаксис использовать. Что-то типа:
     public static TOutput Select<TInput, TOutput>(this TInput src, Func<TInput, TOutput> next)
            where TInput : class
            where TOutput : class
        {
            if (src == null) return null;
            return next(src);
        }


И потом как-то так:
  var houseName = from a in person.Address
                            select a;


Только в случае null-ов оно будет конфликтовать со всеми остальными реализациями LINQ, т.к. надо будет это дело реализовать для всех классов. Можно сделать свой Maybe<>, или заюзать Nullable<>, но это уже будет неудобно, да и null туда все равно можно будет класть.

Но для других монад я бы определенно советовал использовать стандарты LINQ.

Вообще возможность записать null в любую ссылочную переменную — большая ошибка в дизайне языка.
Вообще возможность записать null в любую ссылочную переменную — большая ошибка в дизайне языка.


Полностью согласен. А насчет LINQ, идея конечно интересная.
Sign up to leave a comment.

Articles