Монада MayBe
Что такое монада MayBe можно узнать из видео:
http://www.techdays.ru/videos/4448.html
Довольно-таки полноценный код монады:
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace System.Monads
{
/// <summary> Для словарей </summary>
public static class MaybeIDictionary
{
/// <summary> Выполняет действие с каждым элементом словаря, если словарь существует </summary>
public static IDictionary<TKey, TValue> Do<TKey, TValue>(this IDictionary<TKey, TValue> source,
Action<TKey, TValue> action)
{
if (source != null)
{
foreach (var element in source)
{
action(element.Key, element.Value);
}
}
return source;
}
/// <summary> Достает значение по указанному ключу, если словарь существует и имеет такой ключ. В противном случае возвращает значение типа по умолчанию. </summary>
public static TValue With<TKey, TValue>(this IDictionary<TKey, TValue> source, TKey key)
{
if (source != null)
{
TValue result;
if (source.TryGetValue(key, out result))
return result;
}
return default(TValue);
}
/// <summary>
/// Достает значение по указанному ключу, если словарь существует и имеет такой ключ. В противном случае возвращает заданное нами значение по умолчанию </summary>
public static TValue Return<TKey, TValue>(this IDictionary<TKey, TValue> source, TKey key, TValue defaultValue)
{
if (source != null)
{
TValue result;
if (source.TryGetValue(key, out result))
return result;
}
return defaultValue;
}
}
/// <summary> Для перечислений - массивы, списки </summary>
public static class MaybeIEnumerable
{
/// <summary> Выполняет указанное действие с каждым элементом списка, если список существует </summary>
public static IEnumerable Do(this IEnumerable source, Action<object> action)
{
var enumerable = source as object[] ?? source.Cast<object>().ToArray();
if (source != null)
foreach (var element in enumerable)
action(element);
return enumerable;
}
/// <summary> Выполняет указанное действие с каждым элементом списка, если список существует </summary>
public static IEnumerable<TSource> Do<TSource>(this IEnumerable<TSource> source, Action<TSource> action)
{
var enumerable = source as TSource[] ?? source.ToArray();
if (source != null)
foreach (var element in enumerable)
action(element);
return enumerable;
}
/// <summary> Выполняет указанное действие с каждым элементом списка, если список существует </summary>
public static IEnumerable<TSource> Do<TSource>(this IEnumerable<TSource> source, Action<TSource, int> action)
{
var enumerable = source as TSource[] ?? source.ToArray();
if (source != null)
foreach (var element in enumerable.Select((s, i) => new {Source = s, Index = i}))
action(element.Source, element.Index);
return enumerable;
}
/// <summary> Проталкивает указанное поле, если список существует. Аналог Select() грубо говоря </summary>
public static IEnumerable<TResult> With<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> action)
{
return source != null ? source.Select(action) : null;
}
}
public static class MaybeNullable
{
/// <summary> Выполняет указанное действие, если объект существует. </summary>
public static TSource? Do<TSource>(this TSource? source, Action<TSource?> action)
where TSource : struct
{
if (source.HasValue)
action(source);
return source;
}
/// <summary> Проталкивает указанное значение, если объект существует и значение типа по умолчанию, если не существует </summary>
public static TResult With<TSource, TResult>(this TSource? source, Func<TSource?, TResult> action)
where TSource : struct
{
return source.HasValue ? action(source) : default(TResult);
}
/// <summary> Проталкивает указанное значение, если объект существует и значение указанного нами типа, если не существует </summary>
public static TResult Return<TSource, TResult>(this TSource? source, Func<TSource?, TResult> action, TResult defaultValue)
where TSource : struct
{
return source.HasValue ? action(source) : defaultValue;
}
/// <summary> Проталкивает значение дальше, если выполняется условие, в противном случае возвращает значение типа по умолчанию </summary>
public static TSource? If<TSource>(this TSource? source, Func<TSource?, bool> condition)
where TSource : struct
{
return source.HasValue && condition(source) ? source : default(TSource);
}
/// <summary> Пироталкивает значение дальше, если условие не выполняется, в противном случае возвращает значение по умолчанию </summary>
public static TSource? IfNot<TSource>(this TSource? source, Func<TSource?, bool> condition)
where TSource : struct
{
return source.HasValue && !condition(source) ? source : default(TSource);
}
/// <summary> Если объект не существует, восстанавливает его указанным способом </summary>
public static TInput Recover<TInput>(this TInput? source, Func<TInput> recoverFunc)
where TInput : struct
{
return source.HasValue ? source.Value : recoverFunc();
}
/// <summary> Выполняет метод если объект не пуст. Возвращает ошибку, если таковая произошла во время выполнения действия </summary>
public static Tuple<TSource?, Exception> TryDo<TSource>(this TSource? source, Action<TSource?> action)
where TSource : struct
{
if (source.HasValue)
try
{
action(source);
}
catch (Exception ex)
{
return new Tuple<TSource?, Exception>(source, ex);
}
return new Tuple<TSource?, Exception>(source, null);
}
/// <summary> Выполняет метод если объект не пуст. Возвращает ошибку, если таковая произошла во время выполнения действия и только если она удовлетворяет условию </summary>
public static Tuple<TSource?, Exception> TryDo<TSource>(this TSource? source, Action<TSource?> action, Func<Exception, bool> exceptionChecker)
where TSource : struct
{
if (source.HasValue)
try
{
action(source);
}
catch (Exception ex)
{
if (exceptionChecker(ex))
return new Tuple<TSource?, Exception>(source, ex);
throw;
}
return new Tuple<TSource?, Exception>(source, null);
}
/// <summary> Выполняет метод если объект не пуст. Возвращает ошибку, если таковая произошла во время выполнения действия и только если она заранее указана в массиве </summary>
public static Tuple<TSource?, Exception> TryDo<TSource>(this TSource? source, Action<TSource?> action, params Type[] expectedException)
where TSource : struct
{
if (source.HasValue)
try
{
action(source);
}
catch (Exception ex)
{
if (expectedException.Any(exp => exp.IsInstanceOfType(ex)))
return new Tuple<TSource?, Exception>(source, ex);
throw;
}
return new Tuple<TSource?, Exception>(source, null);
}
/// <summary>
/// Allows to do some conversion of <paramref name="source"/> if its not null and catch any exceptions
/// </summary>
public static Tuple<TResult, Exception> TryWith<TSource, TResult>(this TSource? source, Func<TSource?, TResult> action)
where TSource : struct
{
if (source.HasValue)
{
TResult result = default(TResult);
try
{
result = action(source);
return new Tuple<TResult, Exception>(result, null);
}
catch (Exception ex)
{
return new Tuple<TResult, Exception>(result, ex);
}
}
return new Tuple<TResult, Exception>(default(TResult), null);
}
/// <summary>
/// Allows to do some conversion of <paramref name="source"/> if its not null and catch exceptions, which handled by <param name="exceptionChecker"/>
/// </summary>
public static Tuple<TResult, Exception> TryWith<TSource, TResult>(this TSource? source, Func<TSource?, TResult> action, Func<Exception, bool> exceptionChecker)
where TSource : struct
{
if (source.HasValue)
{
TResult result = default(TResult);
try
{
result = action(source);
return new Tuple<TResult, Exception>(result, null);
}
catch (Exception ex)
{
if (exceptionChecker(ex))
return new Tuple<TResult, Exception>(result, ex);
throw;
}
}
return new Tuple<TResult, Exception>(default(TResult), null);
}
/// <summary>
/// Allows to do some conversion of <paramref name="source"/> if its not null and catch <param name="expectedException"/> exceptions
/// </summary>
public static Tuple<TResult, Exception> TryWith<TSource, TResult>(this TSource? source, Func<TSource?, TResult> action, params Type[] expectedException)
where TSource : struct
{
if (source.HasValue)
{
TResult result = default(TResult);
try
{
result = action(source);
return new Tuple<TResult, Exception>(result, null);
}
catch (Exception ex)
{
if (expectedException.Any(exp => exp.IsInstanceOfType(ex)))
return new Tuple<TResult, Exception>(result, ex);
throw;
}
}
return new Tuple<TResult, Exception>(default(TResult), null);
}
/// <summary> Возвращает true если объект не существует </summary>
public static bool IsNull<TSource>(this TSource? source)
where TSource : struct
{
return source.HasValue == false;
}
/// <summary> Возвращает true если объект существует </summary>
public static bool IsNotNull<TSource>(this TSource? source)
where TSource : struct
{
return source.HasValue;
}
}
public static class MaybeObjects
{
/// <summary> Выполняет действие, если объект существует </summary>
public static TSource Do<TSource>(this TSource source, Action<TSource> action)
where TSource : class
{
if (source != default(TSource))
action(source);
return source;
}
/// <summary> Проталкивает значение, если объект существует </summary>
public static TResult With<TSource, TResult>(this TSource source, Func<TSource, TResult> action)
where TSource : class
{
return source != default(TSource) ? action(source) : default(TResult);
}
/// <summary> Проталкивает значение, если объект существует, иначе возвращает наше значение по умолчанию </summary>
public static TResult Return<TSource, TResult>(this TSource source, Func<TSource, TResult> action, TResult defaultValue)
where TSource : class
{
return source != default(TSource) ? action(source) : defaultValue;
}
/// <summary> Проталкивает значение дальше, если выполняется условие </summary>
public static TSource If<TSource>(this TSource source, Func<TSource, bool> condition)
where TSource : class
{
return source != default(TSource) && condition(source) ? source : default(TSource);
}
/// <summary> Проталкивает значение дальше, если условие не выполняется</summary>
public static TSource IfNot<TSource>(this TSource source, Func<TSource, bool> condition)
where TSource : class
{
return source != default(TSource) && !condition(source) ? source : default(TSource);
}
/// <summary> Восстанавливает объект указанным способом если он не существует </summary>
public static TInput Recover<TInput>(this TInput source, Func<TInput> action)
where TInput : class
{
return source ?? action();
}
/// <summary> Преобразует объект в указанный тип </summary>
public static TResult OfType<TResult>(this object source)
{
return source is TResult ? (TResult) source : default(TResult);
}
/// <summary>
/// Allows to do <paramref name="action"/> and catch any exceptions
/// </summary>
public static Tuple<TSource, Exception> TryDo<TSource>(this TSource source, Action<TSource> action)
where TSource : class
{
if (source != default(TSource))
try
{
action(source);
}
catch (Exception ex)
{
return new Tuple<TSource, Exception>(source, ex);
}
return new Tuple<TSource, Exception>(source, null);
}
/// <summary>
/// Allows to do <paramref name="action"/> and catch exceptions, which handled by <param name="exceptionChecker"/>
/// </summary>
public static Tuple<TSource, Exception> TryDo<TSource>(this TSource source, Action<TSource> action, Func<Exception, bool> exceptionChecker)
where TSource : class
{
if (source != default(TSource))
try
{
action(source);
}
catch (Exception ex)
{
if (exceptionChecker(ex))
return new Tuple<TSource, Exception>(source, ex);
throw;
}
return new Tuple<TSource, Exception>(source, null);
}
/// <summary>
/// Allows to do <paramref name="action"/> and catch <param name="expectedException"/> exceptions
/// </summary>
public static Tuple<TSource, Exception> TryDo<TSource>(this TSource source, Action<TSource> action, params Type[] expectedException)
where TSource : class
{
if (source != default(TSource))
try
{
action(source);
}
catch (Exception ex)
{
if (expectedException.Any(exp => exp.IsInstanceOfType(ex)))
return new Tuple<TSource, Exception>(source, ex);
throw;
}
return new Tuple<TSource, Exception>(source, null);
}
/// <summary>
/// Allows to do some conversion of <paramref name="source"/> if its not null and catch any exceptions
/// </summary>
public static Tuple<TResult, Exception> TryWith<TSource, TResult>(this TSource source, Func<TSource, TResult> action)
where TSource : class
{
if (source != default(TSource))
{
TResult result = default(TResult);
try
{
result = action(source);
return new Tuple<TResult, Exception>(result, null);
}
catch (Exception ex)
{
return new Tuple<TResult, Exception>(result, ex);
}
}
return new Tuple<TResult, Exception>(default(TResult), null);
}
/// <summary>
/// Allows to do some conversion of <paramref name="source"/> if its not null and catch exceptions, which handled by <param name="exceptionChecker"/>
/// </summary>
public static Tuple<TResult, Exception> TryWith<TSource, TResult>(this TSource source, Func<TSource, TResult> action, Func<Exception, bool> exceptionChecker)
where TSource : class
{
if (source != default(TSource))
{
TResult result = default(TResult);
try
{
result = action(source);
return new Tuple<TResult, Exception>(result, null);
}
catch (Exception ex)
{
if (exceptionChecker(ex))
return new Tuple<TResult, Exception>(result, ex);
throw;
}
}
return new Tuple<TResult, Exception>(default(TResult), null);
}
/// <summary>
/// Allows to do some conversion of <paramref name="source"/> if its not null and catch <param name="expectedException"/> exceptions
/// </summary>
public static Tuple<TResult, Exception> TryWith<TSource, TResult>(this TSource source, Func<TSource, TResult> action, params Type[] expectedException)
where TSource : class
{
if (source != default(TSource))
{
TResult result = default(TResult);
try
{
result = action(source);
return new Tuple<TResult, Exception>(result, null);
}
catch (Exception ex)
{
if (expectedException.Any(exp => exp.IsInstanceOfType(ex)))
return new Tuple<TResult, Exception>(result, ex);
throw;
}
}
return new Tuple<TResult, Exception>(default(TResult), null);
}
/// <summary>
/// Handle exception with no actions
/// </summary>
public static TSource Catch<TSource>(this Tuple<TSource, Exception> source)
{
return source.Item1;
}
/// <summary>
/// Handle exception with <param name="handler"/> action
/// </summary>
public static TSource Catch<TSource>(this Tuple<TSource, Exception> source, Action<Exception> handler)
{
if (source.Item2 != null)
handler(source.Item2);
return source.Item1;
}
/// <summary> Возвращает true если объект не существует </summary>
public static bool IsNull<TSource>(this TSource source)
where TSource : class
{
return source == default(TSource);
}
/// <summary> Возвращает true если объект существует </summary>
public static bool IsNotNull<TSource>(this TSource source)
where TSource : class
{
return source != default(TSource);
}
}
}Исходник взят отсюда, от себя только частично комменты на русском + компоновка в один файлик + пара фиксов кода.
Монада очень важная и очень часто используемая, потому не запостить - грех.
Смотрите также:
Комментарии
Я видимо сильно отстал от жизни, раз в таких языках как сишарп уже юзают монады.
Код не читал, только саммари, есть какие-нибудь отличия от хаскельных монад?
Я так понял, тут в основном реализован функционал итераторов для перечислимых объектов, с выбросом Nothing в случае ошибок?
Видео не смог посмотреть. Не ставить же сильверлайт?)
Mihahail,
Шарп поддерживает монадические конструкции, хотя конечно не в той мере в которой это происходит в языках функционального программирования.
Соль не в перечисляемых объектах, если почитаешь монаду чуть пониже, то для обычных объектов она тоже юзается. В общем соль такова - это просто пробрасывание значения. Исключение постоянных проверок на Null.
Вот пример кода который я пишу прямо сейчас. Его смысл в том чтобы проверить, содержит ли текущий объект нужную мне конструкцию. В обычном коде он выглядит так:
bool result = false;
if (calc != null && calc.handle != null)
{
var methodHandle = calc.handle as HandleMethod;
if (methodHandle != null && methodHandle.method == checkMethod)
result = true;
}То есть фактически мне нужно просто пройти в calc.handle.method и сравнить его с моим checkMethod. При том если я не могу туда попасть по причине того что объектов нет - вернуть false
С помощью монады этот же самый код я могу забить однострочником
var result = calc.With(x => x.handle).OfType<HandleMethod>().With(x => x.method).If(x => x == checkMethod).ReturnSuccess();
Соответственно такая цепочка будет толкать null дальше, либо выполнять выражение.
С хаскеллом не работал, потому перечислить отличия не могу. Но это явно сдернуто оттуда.
Фу однострочник....
А если calc будет наллом?
Хочу заметить, я никого не принуждаю к использованию подобного метода. Но им пользуется огромное количество людей, в том числе и я сам. Раз я временами выкладываю наработки то было бы разумно опубликовать и такие куски, чтобы в дальнейшем свободно делать публикации с использованием этой монады.
Имхо монада дает неоспоримые плюсы. Например тот же пример что выше, немного перефразируем. Скажем у нас есть вот такая ситуация:
var result = calc != null && calc.handle != null && calc.handle.method != null && calc.handle.method.value != null ? calc.handle.method.value : null;
С монадой будет проще некуда:
var result = calc.With(x => x.handle).With(x => x.method).With(x => x.value);
Монада выглядит читабельней имхо.
Опять же, можно взять к примеру стандартные монады - Select, Where, Any - ведь они действительно удобны. Но всегда будут люди, которые скажут "фээ, я лучше буду разворачивать форичи вместо однострочника"
Extravert, просто я немного по другому оформляю
var result = calc
.With(x => x.handle)
.OrType<HandleMethod>()
.With(x => x.method)
.If(x => x == checkMethod)
.ReturnSuccess();Особенно касается всяких селектов и прочего. Правда не всегда LINQ выглядит красивее обычного foreach
в первом примере handle у меня абстрактного типа Handle, и я привожу его к конкретному HandleMethod и только в случае если он имеет данный тип цепочка выполняется дальше.
А ну все, больше претензий нет)
просто я немного по другому оформляю
Я сам так оформляю, просто по причине того что я написал "однострочник" и изобразил одной строкой :)
бтв забыл сказать - код с 'Truple' только для новых шарпиков, скажем шарп в юнити его не понимаетExtravert, надо чтоли тогда про Unity написать, правда не особо представляю как его можно использовать при создании игры
Спасибо.
После конструкций хаскеля однострочник выглядит стремно(очень очень очень) =) Хотя я пожалуй всё понял. Но сам бы так не стал писать, наверно.
Ещё я плохо знаю сишарп, когда читаю исхожу из аналогий с С++, который знаю чуть лучше.
Ещё я плохо знаком с шаблонами, как по этой конструкции определить, где возникло исключение? А то протолкнем налл на самый верх, а где он появился? Это ведь придётся лезть в код монады чтобы она, скажем, кидала алерт куда-нибудь?
В хаскелле, кстати, это всё делается иначе и гораздо проще, с использованием механизма классов типов.. Я (ну, это видно) не часто комментирую такие топики, но тут конструкция из моего любимого(но не самого часто используемого) языка была перенесена на оо-язык, что вообще-говоря интересно само по себе. Вряд ли это самая полезная ссылка, которую я могу привести, но вот


<a href= http://mosros.flybb.ru/viewtopic.php?f=2&t=635>Процесс получения диплома стоматолога: реально ли это сделать быстро?</a>