Почему у Линка нет головы и хвоста?
Мне часто хочется использовать методы Head и Tail в IEnumerables, которых нет в Linq. В то время как я мог легко написать свое собственное, мне интересно, были ли они целенаправленно пропущены. Например,
var finalCondition = new Sql("WHERE @0 = @1", conditions.Head().Key, conditions.Head().Value);
foreach (var condition in conditions.Tail())
{
finalCondition.Append("AND @0 = @1", condition.Key, condition.Value);
}
Итак, какова лучшая практика в отношении этого с Linq? Является ли тот факт, что я продолжаю находить использование для этого признаком, что я не делаю что-то рекомендованное? Если нет, то почему эта общая функциональная парадигма не была реализована в Linq?
4 ответа
Учитывая интерфейс IEnumerable<T>
, производительность не всегда может быть обеспечена.
Вы заметите, что большинство функциональных языков программирования реализуют хвост и голову. Однако следует отметить, что эти языки действуют в конструкциях памяти.
IEnumerable<T>
не имеет таких ограничений, и поэтому нельзя предполагать, что это будет эффективно.
Например, обычным функциональным паттерном будет рекурсивная работа над Главой коллекции, а затем повторение в Хвосте вызова...
Если бы вы сделали это, например, с Entity Framework, вы бы отправили следующий (мета) вызов SQL-серверу с жесткой петлей.
Select * from
(
Select * from
(
Select * from
(...)
Skip 1
)
Skip 1
);
Что было бы крайне неэффективно.
РЕДАКТИРОВАТЬ:
Давай думать об этом. Другая причина в том, что C#/VB.Net не поддерживает хвостовую рекурсию, и, таким образом, этот шаблон может легко вызвать Stackru
,
Технически, ваша голова будет .First(), а ваш хвост будет .Skip (1). Но, может быть, вы можете найти лучшее решение? Как использовать .Aggregate() на вашем IEnumerable?
Потому что концепция "Head & Tail" используется в функциональном программировании для сопоставления с образцом и рекурсивных вызовов. Поскольку C # не поддерживает сопоставление с образцом, нет необходимости реализовывать методы head() и tail ().
let rec sum = function
| [] -> 0
| h::t -> h + sum t
Что касается вашего случая - вы должны использовать метод Aggregate.
Я не предлагаю использовать это в рабочем коде, но я пытался объяснить фрагмент Haskell некоторым друзьям, которые знают C#, и обнаружил, что я могу добиться этого, смешавIEnumerable<T>
и (что вы можете, потому что всегда дает вамGetEnumerator()
и вы можете конвертировать вIEnumerable
используя итератор (yield return
). Итератор дает вам преимущество имитации лени (в некоторых контекстах, если его правильно использовать).
IEnumerable<int> sieve(IEnumerable<int> l)
{
var (p, xs) = getHeadAndTail(l);
yield return p;
foreach (var n in sieve(xs.Where(i => i % p > 0)))
yield return n;
}
(T, IEnumerable<T>) getHeadAndTail<T>(IEnumerable<T> l)
{
var e = l.GetEnumerator();
e.MoveNext();
var head = e.Current;
return (head, tailGetter());
IEnumerable<T> tailGetter()
{
while (e.MoveNext())
yield return e.Current;
}
}
(для пуристов — в исходном контексте обе функции являются локальными, поэтому они начинаются со строчной буквы)
Я подозреваю, что есть способ сделать это без (явного) введения (вероятно, все еще с использованием итераторов), но я еще не наткнулся на него.
(Сегодня ковыряясь, я только что обнаружил, чтоIEnumerator<T>
реализуетIDisposable
, покаIEnumerator
не. Боже, чего ты никогда не замечаешь)