Comments 59
Maybe()
T
nullptr
Maybe<T>
||
Maybe
еще и реализовать метод SelectMany
и писать совсем странные конструкции вроде: var maybe=
from x in 1.Maybe()
from y in SomeOtherMaybe
select x + y
Правда, практического применения этой конструкции мне найти не удалось :)
Увы, C# в большинстве случаев не даст такое сделать, ибо generic != template, да и даже отсутствие typdef уже делает невозможным подобные вещи.
С другой стороны, скорость компиляции и требования к памяти оставляют желать лучшего в случае таких лютых извращений на C++.
Увы, C# в большинстве случаев не даст такое сделать, ибо generic != template, да и даже отсутствие typdef уже делает невозможным подобные вещи.
Думаю, что не «увы», а «хорошо что» :). У C# же достаточно неплохо с интероперабельностью с плюсами. Захотелось чего-нибудь эдакого — знай подключай плюсовые dll. А для типовых задач, решаемых на C# такая мощь — избыточна.
Особенно ошибки компилятора в них:)
Со временем можно научиться в этом разбираться. И если получится разбираться в каше логов GCC, то с остальными компиляторами проблем не возникает.
Думаю, что не «увы», а «хорошо что» :).
Ну да, примерно это и имелось ввиду :) Пока народ не проникся Roslyn, C# будет держаться.
А в плане силы C#, у него есть рефлексия, деревья выражений и кодогенерация, и тут можно очень много хорошего наворотить.
-----удалено, ошибся----
Польза таких избыточных абстракций в C# сомнительна, но чего греха таить, меня тоже на такие оберточные поделия часто прорывает)
public IActionResult Get(int id) => query .Where(x => x.Id == id) .SingleOrDefault() .PipeTo(x => x != null ? Ok(x) : new NotFoundResult(“Not Found”));
Выглядит уже не так хорошо. Исправим это с помощью метода Either:
public static TOutput Either<TInput, TOutput>(this TInput o, Func<TInput, bool> condition, Func<TInput, TOutput> ifTrue, Func<TInput, TOutput> ifFalse) => condition(o) ? ifTrue(o) : ifFalse(o); public IActionResult Get(int id) => query .Where(x => x.Id == id) .SingleOrDefault() .Either(x => x != null, Ok, _ => (IActionResult)new NotFoundResult("Not Found"));
Стало ничем не лучше, даже наоборот, переусложнён код, который ещё может быть кому-то предстоит отлаживать…
PipeTo, Do ещё приемлемы, а все остальное выглядит как функциональщина ради функциональщины:
Either — заменили? и: на, и,
ById — теперь надо реализовать ещё один интерфейс.
ById с проекцией — если вставить перед ним Select будет понятнее.
ToPagedEnumerable — не ленивая
IQueryableSpecification — В каком порядке отсортирует? сначала по Id потом по Name или наоборот?
В случае с лямбдой все понятно, а компилятор эту «спецификацию» создаст за нас.
MaybeWhere — зачем проверять на Expression, если мы передаем всегда IPaging
Проще реализовать два метода расширения и переложить работу на компилятор.
ПС: Не написали про методы MinBy, MaxBy.
Также полезным был бы интерфейс:
interface ICountedEnumerable<out T> : IEnumerable<T>
{
public int Count {get;}
}
Как промежуточный между ICollection и IEnumerable, расширения которого избавляют от лишней работы в методах ToArray, ToList.
ППС: И напоследок пару расширений:
IEnumerable<Tuple<T, T>> Pack<T>(this IEnumerable<T> source);
IEnumerable<Tuple<T, T>> Window<T>(this IEnumerable<T> source);
ICountedEnumerable уже есть, только называется IReadOnlyCollection
Either — заменили? и: на, и,Я использую только вторую перегрузку (та, что проверяет на null). Получается так:
str.Either(x => x + " Some more text", _ => "No Text");
. Возможно, первую, та что с condition можно и вправду удалить за ненадобностью. Подумаю об этом.IQueryableSpecification — В каком порядке отсортирует? сначала по Id потом по Name или наоборот?Подробнее эта часть описана в другой статье. Эта ссылка есть и в тексте этого топика. Скорее всего вы не перешли, поэтому возник вопрос.
В случае с лямбдой все понятно, а компилятор эту «спецификацию» создаст за нас.
MaybeWhere — зачем проверять на Expression, если мы передаем всегда IPagingПосмотрите на метод
Проще реализовать два метода расширения и переложить работу на компилятор.
Paged
. MaybeWhere нужен только для компоновки. Возможно его стоит сделать приватным.Конечно, если использовать как
> .SingleOrDefault().Either(x => x != null, Ok, _ => (IActionResult)new NotFoundResult(«Not Found»));
Есть какой-то сакральный смысл в том, что бы сначала самому себе подсовывать Default, а потом вот так изгаляться?
> .SingleOrElse(new NotFoundResult(«Not Found»));
Спасибо.
ById требует соблюдения некоторых соглашений. Что-то облегчили, в чем-то добавили сложности
Ваш Either не совсем та структура (монада, если угодно) Either, которая обычно используется в ФП…
Ну, и названия типа MaybeWhere глаз немного режут.
А вообще радует Ваш подход
query
.MaybeWhere(/* может быть ты хочешь применить where до проекции*/)
.Select (...)
.MaybeWhere(/* или после?*/)
.ToArray()
Тогда можно лениться и делать так:
public class Spec: IQueryableSpec<TEntity>, IQueryableSpec<TProjection>
В таком случае будут применены оба Where. Обычно два не требуется — достаточно одного. Специально для этого в самом конце есть Paged с явным указанием параметров. Мне в простых Crud проще вешать много интерфейсов на один класс. Главное понимать Flow:
Entity => Dto
. Соовтетственно фильтрации и сортировки применяются в таком-же порядке.Either
здесь действительно не имеет отношения к слове на букву М:) На данный момент я думаю, что в C# проще кидать исключения и писать только один try/catch на все приложение. Получится вполне себе Either<TResult, Exception>
. Пытаться конвертнуть все Exception'ы в Failure в .NET Framework — задача так себе.По поводу читабельности мы специально проводили эксперимент и давали нескольким программистам посмотреть код с
Either
и PipeTo
. Все правильно ответили как работает код, поэтому оставили название методов такими. Можете предложить название лучше?Either и PipeTo
Можете предложить название лучше?
Ну, Either тут больше похож на ContinueWith, не? На мой взгляд. Не претендую. Просто CPS, как он есть…
По поводу
Either<TResult, Exception>— дело вкуса, конечно. Я, например, в одном из проектов использую Either<TError, TResult>. И так это меня радует, неимоверно. Почти как у взрослых :), вынуждает разработчика обрабатывать оба варианта, иначе не скомпилируется.
ContinueWith
— вариант, но тогда придется пилить нечто вродеflow.ContinueWith(x => ...).Error(e => ...)
. А это уже надо опять промежуточный объект создавать и получится тот-же самый Either<TResult, Exception>
.А не поделитесь вашей реализацией
Either
? Я написал одну, показалось не элегантно и отказался. Может есть реализации лучше моей?public class Either<TL, TR>
{
[DataMember]
private readonly bool _isLeft;
[DataMember]
private readonly TL _left;
[DataMember]
private readonly TR _right;
public Either(TL left)
{
_left = left;
_isLeft = true;
}
public Either(TR right)
{
_right = right;
_isLeft = false;
}
/// <summary>
/// Checks the type of the value held and invokes the matching handler function.
/// </summary>
/// <typeparam name="T">The return type of the handler functions.</typeparam>
/// <param name="ofLeft">Handler for the Left type.</param>
/// <param name="ofRight">Handler for the Right type.</param>
/// <returns>The value returned by the invoked handler function.</returns>
/// <exception cref="System.ArgumentNullException">
/// </exception>
public T Match<T>(Func<TL, T> ofLeft, Func<TR, T> ofRight)
{
if (ofLeft == null)
{
throw new ArgumentNullException(nameof(ofLeft));
}
if (ofRight == null)
{
throw new ArgumentNullException(nameof(ofRight));
}
return _isLeft ? ofLeft(_left) : ofRight(_right);
}
/// <summary>
/// Checks the type of the value held and invokes the matching handler function.
/// </summary>
/// <param name="ofLeft">Handler for the Left type.</param>
/// <param name="ofRight">Handler for the Right type.</param>
/// <exception cref="System.ArgumentNullException">
/// </exception>
public void Match(Action<TL> ofLeft, Action<TR> ofRight)
{
if (ofLeft == null)
{
throw new ArgumentNullException(nameof(ofLeft));
}
if (ofRight == null)
{
throw new ArgumentNullException(nameof(ofRight));
}
if (_isLeft)
{
ofLeft(_left);
}
else
{
ofRight(_right);
}
}
public TL LeftOrDefault() => Match(l => l, r => default(TL));
public TR RightOrDefault() => Match(l => default(TR), r => r);
public Either<TR, TL> Swap() => Match((Func<TL, Either<TR, TL>>) (Right<TR, TL>), Left<TR, TL>);
public Either<TL, T> Bind<T>(Func<TR, T> f)
=> BindMany(x => Right<TL, T>(f(x)));
public Either<TL, T> BindMany<T>(Func<TR, Either<TL, T>> f) => Match(Left<TL, T>, f);
public Either<TL, TResult> BindMany<T, TResult>(Func<TR, Either<TL, T>> f, Func<TR, T, TResult> selector)
=> BindMany(x => f(x).Bind(t => selector(_right, t)));
public static implicit operator Either<TL, TR>(TL left) => new Either<TL, TR>(left);
public static implicit operator Either<TL, TR>(TR right) => new Either<TL, TR>(right);
public static Either<TLeft, TRight> Left<TLeft, TRight>(TLeft left)
=> new Either<TLeft, TRight>(left);
public static Either<TLeft, TRight> Right<TLeft, TRight>(TRight right)
=> new Either<TLeft, TRight>(right);
public static Either<Exception, T> Try<T>(Func<T> f)
{
try
{
return new Either<Exception, T>(f.Invoke());
}
catch (Exception ex)
{
return new Either<Exception, T>(ex);
}
}
}
public interface ISomeService
{
Task<Either<TradeError, Quote>> GetQuoteAsync(GetQuoteQuery query);
}
...
var result = await _service.GetQuoteAsync(query);
result.Match(SetQuoteError, SetQuote);
...
private void SetQuoteError(TradeError error)
{
// do something
}
private void SetQuote(Quote quote)
{
// do something
}
public interface ISomeService
{
Task<Either<TradeError, Quote>> GetQuoteAsync(GetQuoteQuery query);
}
...
public async Task<Either<TradeError, QuoteModel>> GetQuoteModelAsync(GetQuoteQuery query)
{
var result = await _service.GetQuoteAsync(query.);
return result.Bind(ToQuoteModel);
}
...
private static QuoteModel ToQuoteModel(Quote source) => new QuoteModel{ ... };
Должно быть понятно, вроде… :)
А так — здесь им идейку подкинул, тут структурку, глядишь, а они уже и сами упоролись куда надо.
Что это за новая мода — писать всю программу в одну строчку, а потом делать в ней переносы, когда она не входит в экран? Ладно еще запросы linq — это еще куда ни шло, но ветвления? Вы серьезно?
public static TInput Do<TInput>(this TInput o, Action<TInput> action)
{
if (null != o)
action(o);
return o;
}
Кроме того что это исключает тривиальные ошибки присваивания в условии, такой подход позволит применять функции и к структурам, хотя для них проверка на null и бессмысленна, но бывает заранее неизвестно что придется обрабатывать.
Даже на плюсах так никто уже не пишет.
int a = 1;
if ((a = 2) == 3)
{
a = 4;
}
В чем минус указанного подхода (с rvalue в первом операнде)?
while(null != (line = stream.ReadLine()))
{
}
Вместо
line = stream.ReadLine();
while(null != line)
{
/* ToDo */
line = stream.ReadLine();
}
Но мой вопрос был, в чем минус подхода в следующей записи:
if(null != expression)
При такой же читабельности получаем как плюс, исключение ошибки присваивания, и возможность единообразного использования как reference так и value типов.
Не говоря уже о такой «ошибке присваивания, как
null = line;
Гипотетически, если тип приводится к bool, можно наворотить, но Вы же не пишете
if(b == false)
{
}
, правда?if(false == b)
{
}
и не пишу так
if(!b)
{
}
т.к. при чтении кода намного меньше бросается в глаза
Но вы отвечаете не на тот вопрос. Меня действительно интересует в чем минус, или возможная проблема случая когда в условии первым операндом стоит rvalue.
Про семантику?
Сравните:
если мое_множество равно пустому_множеству то…
против
если пустое_множество равно моему_множеству то…
пустое множество не может равняться чему-то еще, оно одно такое, инициальный объект в этом типе.
Смотрите, Вы говорите
If(false == b) {}
false — это константа, она ничему больше, кроме самой себя, равняться не может.
С этим, надеюсь, спорить не будете?
Так вот,
Технически, никто Вам не запрещает писать так, как Вы пишете, ни компилятор, ни рантайм. Но читается это не очень…
Поэтому, собственно, весь остальной мир на шарпе и пишет if(value==null).
Из комментария выше про множества, с моей точки зрения оба утверждения семантически равноценны, т.к. операция сравнения не предполагает порядок указания сравниваемых частей.
Про стиль и пальцы написали уже. У меня такое не то, что code review не прошло бы, даже commit завернуло бы.
Языки программирования — они ж для человеков, не для роботов :)
В том, что я бы просто за такое ломал бы пальцы. Если делаете в своем проекте — извольте, но если работаете в команде — не следует так подставлять своих товарищей.
public abstract class Identified
{
public long Id { get; set; }
}
и уже ему пишем расширение:
public static T ById<T>(this IEnumerable<T> identifies, long id)
where T : Identified
{
return identifies.SingleOrDefault(c=>c.Id.Equals(id));
}
Кстати, почему у вас расширение только для IQueryable? можно сразу на IEnumerable, это может помочь дальше применять ById, но просто теперь для всех перечислений с этим типом.
public interface IHasId
{
object Id { get; }
}
public interface IHasId<out TKey> : IHasId
where TKey: IComparable, IComparable<TKey>, IEquatable<TKey>
{
new TKey Id { get; }
}
Для enumerable тоже бывает делаем расширения, но чаще нужны именно IQueryable.
IHasId
без generic'а. Бывают случаи, когда тип T
не доступен.IHasId<object>
IHasId<T>
висит where T:IEquatable<T>
. Так что привести к object нельзя, потому что object не реализует IEquatable
. Убирать это условие не хочется, потому что тип ключа по определению должен быть сравним (чтобы иметь возможность быть уникальным). Интерфейс без T
остался для поддержки композитных ключей.Вот моя библиотека для программирования на C# в функциональном стиле: github repo
Получается код типа:
public IActionResult Add(string a, string b, string c, string d, int count = 1, int mod=0)
=> _dal.GetUrl(a, b, c, d).Convert(_dal.GetProduct)
.IfNotNull(x =>
_dal.AddProductToCart(_dal.GetCartBySession(HttpContext.Session.Id), x, count, mod)
.Extend(x).Convert(PartialView), () => (IActionResult) NotFound());
C# — язык мультипарадигмальный
По мне так это язык ООП с элементами ФП, но никак не мультипарадигмальный. Не хватает например expression over statement или readonly на уровне аргументов и переменных для того что бы быть мультипарадигмальным.
Функциональный C#