How to replace for-loops with a functional statement in C#?

前端 未结 18 1724
不思量自难忘°
不思量自难忘° 2021-01-31 08:59

A colleague once said that God is killing a kitten every time I write a for-loop.

When asked how to avoid for-loops, his answer was to use a functional language. However

相关标签:
18条回答
  • 2021-01-31 09:23

    The question is if the loop will be mutating state or causing side effects. If so, use a foreach loop. If not, consider using LINQ or other functional constructs.

    See "foreach" vs "ForEach" on Eric Lippert's Blog.

    0 讨论(0)
  • 2021-01-31 09:24

    If you abstract the for loop directly you get:

    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);
        }
    }
    

    The problem I have with this from a functional programming perspective is the void return type. It essentially means that for loops do not compose nicely with anything. So the goal is not to have a 1-1 conversion from for loop to some function, it is to think functionally and avoid doing things that do not compose. Instead of thinking of looping and acting think of the whole problem and what you are mapping from and to.

    0 讨论(0)
  • 2021-01-31 09:27

    Your colleague is wrong about for loops being bad in all cases, but correct that they can be rewritten functionally.

    Say you have an extension method that looks like this:

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

    Then you can write a loop like this:

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

    This may not be very useful now. But if you then replace your implementation of the ForEach extension method for use a multi threaded approach then you gain the advantages of parallelism.

    This obviously isn't always going to work, this implementation only works if the loop iterations are completely independent of each other, but it can be useful.

    Also: always be wary of people who say some programming construct is always wrong.

    0 讨论(0)
  • 2021-01-31 09:27

    Your colleague may be suggesting under certain circumstances where database data is involved that it is better to use an aggregate SQL function such as Average() or Sum() at query time as opposed to processing the data on the C# side within an ADO .NET application.

    Otherwise for loops are highly effective when used properly, but realize that if you find yourself nesting them to three or more orders, you might need a better algorithm, such as one that involves recursion, subroutines or both. For example, a bubble sort has a O(n^2) runtime on its worst-case (reverse order) scenario, but a recursive sort algorithm is only O(n log n), which is much better.

    Hopefully this helps.

    • Jim
    0 讨论(0)
  • 2021-01-31 09:29

    Why are for-loops bad? Or, in what context are for-loops to avoid and why?

    If your colleague has a functional programming, then he's probably already familiar with the basic reasons for avoiding for loops:

    Fold / Map / Filter cover most use cases of list traversal, and lend themselves well to function composition. For-loops aren't a good pattern because they aren't composable.

    Most of the time, you traverse through a list to fold (aggregate), map, or filter values in a list. These higher order functions already exist in every mainstream functional language, so you rarely see the for-loop idiom used in functional code.

    Higher order functions are the bread and butter of function composition, meaning you can easily combine simple function into something more complex.

    To give a non-trivial example, consider the following in an imperative language:

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

    In a functional language, we'd write map g (map f x), or we can eliminate the intermediate list using map (f . g) x. Now we can, in principle, eliminate the intermediate list from the imperative version, and that would help a little -- but not much.

    The main problem with the imperative version is simply that the for-loops are implementation details. If you want change the function, you change its implementation -- and you end up modifying a lot of code.

    Case in point, how would you write map g (filter f x) in imperatively? Well, since you can't reuse your original code which maps and maps, you need to write a new function which filters and maps instead. And if you have 50 ways to map and 50 ways to filter, how you need 50^50 functions, or you need to simulate the ability to pass functions as first-class parameters using the command pattern (if you've ever tried functional programming in Java, you understand what a nightmare this can be).

    Back in the the functional universe, you can generalize map g (map f x) in way that lets you swap out the map with filter or fold as needed:

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

    And call it using apply2 map g filter f or apply2 map g map f or apply2 filter g filter f or whatever you need. Now you'd probably never write code like that in the real world, you'd probably simplify it using:

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

    Higher-order functions and function composition give you a level of abstraction that you cannot get with the imperative code.

    Abstracting out the implementation details of loops let's you seamlessly swap one loop for another.

    Remember, for-loops are an implementation detail. If you need to change the implementation, you need to change every for-loop.

    Map / fold / filter abstract away the loop. So if you want to change the implementation of your loops, you change it in those functions.

    Now you might wonder why you'd want to abstract away a loop. Consider the task of mapping items from one type to another: usually, items are mapped one at a time, sequentially, and independently from all other items. Most of the time, maps like this are prime candidates for parallelization.

    Unfortunately, the implementation details for sequential maps and parallel maps aren't interchangeable. If you have a ton of sequential maps all over your code, and you want swap them out for parallel maps, you have two choices: copy/paste the same parallel mapping code all over your code base, or abstract away mapping logic into two functions map and pmap. Once you're go the second route, you're already knee-deep in functional programming territory.

    If you understand the purpose of function composition and abstracting away implementation details (even details as trivial as looping), you can start to appreciate just how and why functional programming is so powerful in the first place.

    0 讨论(0)
  • 2021-01-31 09:29

    It depends upon what is in the loop but he/she may be referring to a recursive function

        //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 讨论(0)
提交回复
热议问题