Как заменить для циклов функциональным оператором в C#?

Коллега однажды сказал, что Бог уничтожает котенка каждый раз, когда я пишу для цикла.

При выяснении, как избежать для циклов, его ответ должен был использовать функциональный язык. Однако, если Вы застреваете с нефункциональным языком скажем C#, что методы там, чтобы избежать для циклов или избавиться от них путем рефакторинга? С лямбда-выражениями и LINQ, возможно? Если так, как?

Вопросы

Таким образом, вопрос сводится к:

  1. Почему для циклов плохи? Или, в какой контекст для циклов для предотвращения и почему?
  2. Можно ли предоставить примеры кода C# того, как это смотрит прежде, т.е. с циклом, и впоследствии без цикла?
32
задан Lernkurve 15 April 2010 в 16:18
поделиться

18 ответов

Функциональные конструкции часто выражают ваши намерения более четко, чем циклы for, в тех случаях, когда вы работаете с некоторым набором данных и хотите преобразовать, отфильтровать или агрегировать элементы.

Циклы очень подходят, когда вы хотите многократно выполнить какое-либо действие.


Например,

int x = array.Sum();

гораздо яснее выражает ваше намерение, чем

int x = 0;
for (int i = 0; i < array.Length; i++)
{
    x += array[i];
}
30
ответ дан 27 November 2019 в 19:52
поделиться

Вопрос в том, будет ли цикл изменяться или вызывать побочные эффекты. Если да, используйте цикл foreach. Если нет, рассмотрите возможность использования LINQ или других функциональных конструкций.

См. «foreach» против «ForEach» в блоге Эрика Липперта.

0
ответ дан 27 November 2019 в 19:52
поделиться

Если вы абстрагируете цикл for напрямую, вы получите:

void For<T>(T initial, Func<T,bool> whilePredicate, Func<T,T> step, Action<T> action)
{
    for (T t = initial; whilePredicate(t); step(t))
    {
        action(t);
    }
}

Проблема, с которой я столкнулся с этим с точки зрения функционального программирования, - это возвращаемый тип void . По сути, это означает, что циклы for плохо сочетаются ни с чем. Таким образом, цель состоит не в том, чтобы преобразовать 1-1 из цикла for в некоторую функцию, а в том, чтобы мыслить функционально и избегать выполнения несоставных вещей. Вместо того, чтобы думать о зацикливании и действиях, подумайте о проблеме в целом и о том, что вы отображаете и на что.

0
ответ дан 27 November 2019 в 19:52
поделиться

Это зависит от того, что находится в цикле, но он / она может иметь в виду рекурсивную функцию

    //this is the recursive function
    public static void getDirsFiles(DirectoryInfo d)
    {
        //create an array of files using FileInfo object
        FileInfo [] files;
        //get all files for the current directory
        files = d.GetFiles("*.*");

        //iterate through the directory and print the files
        foreach (FileInfo file in files)
        {
            //get details of each file using file object
            String fileName = file.FullName;
            String fileSize = file.Length.ToString();
            String fileExtension =file.Extension;
            String fileCreated = file.LastWriteTime.ToString();

            io.WriteLine(fileName + " " + fileSize + 
               " " + fileExtension + " " + fileCreated);
        }

        //get sub-folders for the current directory
        DirectoryInfo [] dirs = d.GetDirectories("*.*");

        //This is the code that calls 
        //the getDirsFiles (calls itself recursively)
        //This is also the stopping point 
        //(End Condition) for this recursion function 
        //as it loops through until 
        //reaches the child folder and then stops.
        foreach (DirectoryInfo dir in dirs)
        {
            io.WriteLine("--------->> {0} ", dir.Name);
            getDirsFiles(dir);
        }

    }
0
ответ дан 27 November 2019 в 19:52
поделиться

Почему циклы for плохи? Или, в каком контексте следует избегать циклов for и почему?

Если ваш коллега занимается функциональным программированием, то, вероятно, он уже знаком с основными причинами, по которым следует избегать циклов for:

Fold / Map / Filter охватывают большинство случаев использования обхода списка и хорошо подходят для функциональной композиции. Циклы for не являются хорошим шаблоном, потому что их нельзя компоновать.

В большинстве случаев вы просматриваете список, чтобы свернуть (агрегировать), сопоставить или отфильтровать значения в списке. Эти функции более высокого порядка уже существуют во всех основных функциональных языках, поэтому вы редко встретите идиому for-loop, используемую в функциональном коде.

Функции высшего порядка - это основа композиции функций, то есть вы можете легко объединить простую функцию в нечто более сложное.

Чтобы дать нетривиальный пример, рассмотрим следующее на императивном языке:

let x = someList;
y = []
for x' in x
    y.Add(f x')

z = []
for y' in y
    z.Add(g y')

На функциональном языке мы бы написали map g (map fx) , или мы можем исключить промежуточные список с использованием карты (например, g) x . Теперь мы можем, в принципе, исключить промежуточный список из императивной версии, и это немного поможет, но не сильно.

Основная проблема императивной версии состоит в том, что циклы for являются деталями реализации . Если вы хотите изменить функцию, вы меняете ее реализацию - и в конечном итоге вы изменяете большой объем кода.

Показательный пример: как бы вы написали map g (filter f x) в императивном порядке? Что ж, поскольку вы не можете повторно использовать свой исходный код, который отображает и отображает, вам нужно написать новую функцию, которая вместо этого фильтрует и отображает. И если у вас есть 50 способов сопоставления и 50 способов фильтрации, то как вам нужно 50 ^ 50 функций, или вам нужно смоделировать возможность передачи функций в качестве параметров первого класса, используя шаблон команды (если вы когда-либо пробовали функциональное программирование на Java вы понимаете, какой это может быть кошмар).

Вернувшись в функциональную вселенную, вы можете обобщить карту g (map fx) таким образом, чтобы вы могли заменить карту на фильтр или сверните по мере необходимости:

let apply2 a g b f x = a g (b f x)

И вызовите его, используя apply2 map g filter f или apply2 map g map f или apply2 filter g filter f или что вам нужно. Теперь вы, вероятно, никогда не будете писать подобный код в реальном мире, вы, вероятно, упростите его, используя:

let mapmap g f = apply2 map g map f
let mapfilter g f = apply2 map g filter f

Функции высшего порядка и композиция функций дают вам уровень абстракции, который вы не можете получить с помощью императивного кода.

Абстрагирование от деталей реализации циклов позволяет вам легко менять один цикл на другой.

Помните, что циклы for - это деталь реализации. Если вам нужно изменить реализацию, вам нужно изменить каждый цикл for.

Отобразить / свернуть / отфильтровать абстрагирование от цикла. Поэтому, если вы хотите изменить реализацию ваших циклов, вы измените ее в этих функциях.

Теперь вы можете задаться вопросом, зачем вам абстрагироваться от цикла. Рассмотрим задачу отображения элементов одного типа в другой: обычно элементы отображаются по одному, последовательно и независимо от всех остальных элементов. В большинстве случаев подобные карты - главные кандидаты для распараллеливания.

К сожалению, детали реализации последовательных и параллельных карт не взаимозаменяемы. Если у вас есть тонна последовательных карт по всему вашему коду и вы хотите поменять их на параллельные карты, у вас есть два варианта: скопировать / вставить один и тот же код параллельного сопоставления по всей базе кода или абстрагироваться от логики сопоставления в две функции. карта и pmap . Выбрав второй путь, вы уже по колено в области функционального программирования.

Если вы понимаете цель композиции функций и абстрагируетесь от деталей реализации (даже таких тривиальных, как цикл), вы можете начать понимать, как и почему функциональное программирование вообще такое мощное средство.

16
ответ дан 27 November 2019 в 19:52
поделиться

Цикл For, скажем так, «плохой», поскольку он подразумевает предсказание ветвления в ЦП и, возможно, снижение производительности при неудачном предсказании ветвления.

Но ЦП (с точностью предсказания переходов 97%) и компилятор с такими технологиями, как развертывание цикла, делают снижение производительности цикла незначительным.

1
ответ дан 27 November 2019 в 19:52
поделиться

Вы можете достаточно хорошо провести рефакторинг своего кода, чтобы вы не видели его часто. Хорошее имя функции определенно более читабельно, чем цикл for.

Взяв пример из AndyC:

Loop

// mystrings is a string array
List<string> myList = new List<string>();
foreach(string s in mystrings)
{
    if(s.Length > 5)
    {
        myList.add(s);
    }
}

Linq

// mystrings is a string array
List<string> myList = mystrings.Where<string>(t => t.Length > 5)
                               .ToList<string();

Если вы используете первую или вторую версию внутри своей функции, ее легче читать

var filteredList = myList.GetStringLongerThan(5);

Это слишком простой пример, но вы получаете моя точка зрения.

5
ответ дан 27 November 2019 в 19:52
поделиться
  1. Ибо петли неплохие. Есть много веских причин для сохранения цикла for.
  2. Часто можно «избежать» цикла for, переработав его с помощью LINQ в C #, который обеспечивает более декларативный синтаксис. Это может быть хорошо или плохо в зависимости от ситуации:

Сравните следующее:

var collection = GetMyCollection();
for(int i=0;i<collection.Count;++i)
{
     if(collection[i].MyValue == someValue)
          return collection[i];
}

vs foreach:

var collection = GetMyCollection();
foreach(var item in collection)
{
     if(item.MyValue == someValue)
          return item;
}

vs. LINQ:

var collection = GetMyCollection();
return collection.FirstOrDefault(item => item.MyValue == someValue);

Лично у всех трех вариантов есть свое место, и я использую их все. Это вопрос выбора наиболее подходящего варианта для вашего сценария.

14
ответ дан 27 November 2019 в 19:52
поделиться

Ваш коллега может предлагать при определенных обстоятельствах, когда задействованы данные базы данных, что лучше использовать агрегатную функцию SQL, такую ​​как Average () или Sum () во время запроса, в отличие от обработки данных на стороне C # в приложении ADO .NET.

В противном случае циклы for очень эффективны при правильном использовании, но помните, что если вы обнаружите, что вкладываете их в три или более порядков, вам может потребоваться лучший алгоритм, например, тот, который включает рекурсию, подпрограммы или и то, и другое. Например, пузырьковая сортировка имеет время выполнения O (n ^ 2) в худшем случае (в обратном порядке), но алгоритм рекурсивной сортировки составляет всего O (n log n), что намного лучше.

Надеюсь, это поможет.

  • Джим
1
ответ дан 27 November 2019 в 19:52
поделиться

Простой (и на самом деле бессмысленный) пример:

Loop

// mystrings is a string array
List<string> myList = new List<string>();
foreach(string s in mystrings)
{
    if(s.Length > 5)
    {
        myList.add(s);
    }
}

Linq

// mystrings is a string array
List<string> myList = mystrings.Where<string>(t => t.Length > 5).ToList<string>();

В моей книге второй выглядит намного аккуратнее и проще, хотя с первым все в порядке.

2
ответ дан 27 November 2019 в 19:52
поделиться

Иногда цикл for плох, если существует более эффективная альтернатива. Например, поиск, когда было бы более эффективно отсортировать список, а затем использовать быструю сортировку или двоичную сортировку. Или когда вы перебираете элементы в базе данных. Обычно гораздо эффективнее использовать в базе данных операции на основе наборов вместо итерации по элементам.

В противном случае, если цикл for, особенно for-each, имеет наибольший смысл и удобочитаем, я бы пошел с ним, а не преобразовал бы его во что-то, что не так интуитивно понятно. Я лично не верю в это религиозное звучание «всегда делай так, потому что это единственный способ». Лучше иметь руководящие принципы и понимать, в каких сценариях их целесообразно применять. Хорошо, что вы спрашиваете Почему!

1
ответ дан 27 November 2019 в 19:52
поделиться

Любая конструкция на любом языке существует не просто так. Это инструмент, который нужно использовать для выполнения задачи. Означает конец. В каждом случае есть способы использовать его надлежащим образом, то есть ясно и кратко, в соответствии с духом языка И манерами злоупотреблять им. Это относится к сильно смещенному оператору goto , а также к вашей головоломке цикла for , а также к while , do-while , switch / case , if-then-else и т. Д. Если цикл for - правильный инструмент для того, что вы делаете, ИСПОЛЬЗУЙТЕ ЭТО и ваши коллеге нужно будет принять ваше дизайнерское решение.

0
ответ дан 27 November 2019 в 19:52
поделиться

Цикл for всегда можно заменить рекурсивной функцией, не использующей цикл. Рекурсивная функция - это более функциональный способ программирования.

Но если вы вслепую замените циклы for рекурсивными функциями, то и котята, и щенки умрут миллионами, а вас прикончит велоцираптер.

Хорошо, вот пример. Но имейте в виду, что я не сторонник внесения этого изменения!

Цикл for

for (int index = 0; index < args.Length; ++index)
    Console.WriteLine(args[index]);

Может быть изменен на этот рекурсивный вызов функции

WriteValuesToTheConsole(args, 0);


static void WriteValuesToTheConsole<T>(T[] values, int startingIndex)
{
    if (startingIndex < values.Length)
    {
        Console.WriteLine(values[startingIndex]);
        WriteValuesToTheConsole<T>(values, startingIndex + 1);
    }
}

Это должно работать точно так же для большинства значений, но гораздо менее понятно, менее эффективно и может исчерпать стек, если массив слишком большой.

1
ответ дан 27 November 2019 в 19:52
поделиться

Иногда вы не убиваете только одного котенка.

for (int i = 0; i

Иногда вы убиваете торговый центр.

6
ответ дан 27 November 2019 в 19:52
поделиться

Ваш коллега ошибается в том, что циклы плохи во всех случаях, но поправьте, что их можно переписать функционально.

Допустим, у вас есть метод расширения, который выглядит так:

void ForEach<T>(this IEnumerable<T> collection, Action <T> action)
{
    foreach(T item in collection)
    {
        action(item)
    }
}

Затем вы можете написать такой цикл:

mycollection.ForEach(x => x.DoStuff());

Это может быть не очень полезно сейчас. Но если вы затем замените свою реализацию метода расширения ForEach на использование многопоточного подхода, вы получите преимущества параллелизма.

Очевидно, что это не всегда срабатывает, эта реализация работает только в том случае, если итерации цикла полностью независимы друг от друга, но это может быть полезно.

Также: всегда остерегайтесь людей, которые говорят, что какая-то программная конструкция всегда неверна.

3
ответ дан 27 November 2019 в 19:52
поделиться

В циклах for нет ничего плохого, но вот некоторые из причин, по которым люди могут предпочесть функциональные / декларативные подходы, такие как LINQ, где вы объявляете то, что хотите, а не то, как вы получить: -

  1. Функциональные подходы потенциально легче распараллелить вручную с помощью PLINQ или компилятором. По мере того, как процессоры переходят на еще большее количество ядер, это может стать более важным.

  2. Функциональные подходы упрощают выполнение ленивой оценки в многоэтапных процессах, поскольку вы можете передать промежуточные результаты на следующий шаг как простую переменную, которая еще не была оценена полностью, вместо того, чтобы полностью оценивать первый шаг и затем передавать коллекцию к следующему шагу (или без использования отдельного метода и оператора yield для достижения того же процедурно).

  3. Функциональные подходы часто короче и легче читаются.

  4. Функциональные подходы часто исключают сложные условные тела внутри циклов for (например, операторы if и операторы continue), потому что вы можете разбить цикл for на логические шаги - выбор всех совпадающих элементов, выполнение над ними операций, .. .

14
ответ дан 27 November 2019 в 19:52
поделиться

Ибо петли не убивают людей (или котят, или щенков, или триблей). Люди убивают людей. Ибо петли сами по себе неплохи. Однако, как и все остальное, плохим может быть то, как вы их используете.

8
ответ дан 27 November 2019 в 19:52
поделиться

Ваш коллега не прав. Ибо петли сами по себе неплохи. Они чистые, читаемые и не особенно подвержены ошибкам.

3
ответ дан 27 November 2019 в 19:52
поделиться
Другие вопросы по тегам:

Похожие вопросы: