Почему у Линка нет головы и хвоста?

Мне часто хочется использовать методы 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не. Боже, чего ты никогда не замечаешь)

Другие вопросы по тегам