6

Just as many people believe tenaciously in small functions, some people believe lambdas should only contain small code fragments.

An often overlooked advantage of lambdas however, is using them you can encapsulate behavior which you would otherwise have to make available to the entire class.

Isn't it is a true advantage to maintain proper encapsulation, regardless of line count? Which possible disadvantages of using anonymous functions with a lot of lines of code am I overlooking?


The following is an actual example. argumentsMatch does very specific argument matching, highly dependent on the behavior of the function in which it is defined. The following code IMHO follows the Single Responsibility Principle. Moving argumentsMatch to a private method would result in it only being called from within this method.

/// <summary>
///   Get the first found matching generic type.
///   The type parameters of the generic type are optional.
///   E.g. Dictionary<,>
///   When full (generic) type is known (e.g. Dictionary<string,string>),
///   the "is" operator is most likely more performant,
///   but this function will still work correctly.
/// </summary>
/// <param name = "source">The source for this extension method.</param>
/// <param name = "type">The type to check for.</param>
/// <returns>
///   The first found matching complete generic type,
///   or null when no matching type found.
/// </returns>
public static Type GetMatchingGenericType( this Type source, Type type )
{
    Type[] genericArguments = type.GetGenericArguments();
    Type rawType = type.IsGenericType ? type.GetGenericTypeDefinition() : type;

    // Used to compare type arguments and see whether they match.
    Func<Type[], bool> argumentsMatch
        = arguments => genericArguments
            .Zip( arguments, Tuple.Create )
            .All( t => t.Item1.IsGenericParameter || // No type specified.
                       t.Item1 == t.Item2 );

    Type matchingType = null;
    if ( type.IsInterface )
    {
        // Traverse across all interfaces to find a matching interface.
        matchingType = 
            (from t in source.GetInterfaces()
             let rawInterface = t.IsGenericType ? t.GetGenericTypeDefinition() : t
             where rawInterface == rawType &&
                   argumentsMatch( t.GetGenericArguments() )
             select t).FirstOrDefault();
    }
    else
    {
        // Traverse across the type, and all it's base types.
        Type baseType = source;
        while ( baseType != null && baseType != typeof( object ) )
        {
            Type rawCurrent = baseType.IsGenericType
                ? baseType.GetGenericTypeDefinition()
                : baseType;
            if ( rawType == rawCurrent )
            {
                // Same raw generic type, compare type arguments.
                if ( argumentsMatch( baseType.GetGenericArguments() ) )
                {
                    matchingType = baseType;
                    break;
                }
            }
            baseType = baseType.BaseType;
        }
    }

    return matchingType;
}
1
  • 1
    All this stuff about long functions is only a rule of thumb, not an axiom as many believe for some unknown reason. There are hundreds of cases where it is entirely justified to write a long (and even a very long) function body, no matter, a named function or an anonymous one.
    – SK-logic
    Jun 6, 2011 at 9:21

5 Answers 5

6

I think the important point here is code locality: Code that should be read together should be close together.

My rule of thumb is: If both the called (lambda) function and the caller can be read, understood, maintained, tested (and possibly, but not necessarily, reused) separately, then extract the lambda function into a separate function or class. If the two are so closely tied together that you can't really make sense of one without knowing what the other does, then keep them as closely together as possible; lambdas are perfect for this, if the language you use doesn't allow nested functions.

1
3

I think it's more dependent on context. In some languages lambdas or functions in general are not an alternative for encapsulation, they are the primary structure for encapsulation. In other language this technique is often overlooked in favor of more idiomatic structures.

Some problems to using lambdas for encapsulation:

  • It may be harder to maintain the code because the code is not idiomatic. Therefore whenever a developer encounters the code construct they will probably need at least additional time to understand the code. However, they may also have only partial understanding of what the construct does (or what it means in a larger context).

For example: I worked with a team of developers where closures and thunks weren't used or understood well. I found this out after some closure code that I wrote was modified in a way that broke functionality. I also received a few complaints that the lambdas over complicated the code, however I think this was more a result of not understanding what closures were rather than complex code.

  • Another issue is coupling yourself to a particular execution timing during a process. Especially if you need to change this later on or provide this functionality in another location.

For example: When I was younger and wanted to be clever I would define code in a self executing lambda. I would then run into a problem later on where I needed to give that code access to resources that were defined later (database handles, socket, etc). Since the code ran immediately all the connections were created early on. It was difficult to modify this code to delay the connection initialization later on because both the self executing lambda, as well as the surrounding code made assumptions based on the timing of the initialization, ie. temporal coupling. I ended up having to curry the lambda in order to delay its execution until a later point.

5
  • I completely didn't understand this. Some examples?
    – DeadMG
    Jun 5, 2011 at 18:53
  • I can't quite follow either starting from "Another issue that may arise is if you couple functionality...". Perhaps an example can clarify this? Readability IMHO is subjective, as you say perhaps because its usage is atypical. I don't quite see why it should be less maintainable, as proper encapsulation increases maintainability. Jun 5, 2011 at 19:13
  • 1
    @Steven Jeuris: I tried to rewrite the last part so it's more intelligible. Unfortunately I don't have a good way to characterize the problem I'm thinking of in general so I tried to give an example of a specific form and what how you end up solving it.
    – snakehiss
    Jun 5, 2011 at 23:30
  • 1
    @Steven Jeuris: Proper encapsulation increases maintainability in a general sense. Atypical code may be less maintainable because it is not idiomatic. Most code you read is like other code you've come across before. Therefore you don't have to think to hard about what it's doing. If you run across a closure for the first time it can take more time to figure out what is going on. You may also not fully understand the construct and what it's doing, but that will usually be correlated with the complexity of the code.
    – snakehiss
    Jun 5, 2011 at 23:31
  • 1
    @DeadMG: Perhaps it's better now.
    – snakehiss
    Jun 6, 2011 at 0:09
3

In C# 7.0, local functions will be introduced which achieve the same as I outlined in the question, but with a much improved syntax.

Sometimes a helper function only makes sense inside of a single method that uses it. You can now declare such functions inside other function bodies as a local function:

public int Fibonacci(int x)
{
    if (x < 0) throw new ArgumentException("Less negativity please!", nameof(x));
    return Fib(x).current;

    (int current, int previous) Fib(int i)
    {
        if (i == 0) return (1, 0);
        var (p, pp) = Fib(i - 1);
        return (p + pp, p);
    }
}
2

It's also a large advantage to write lambda functions inline, and this remains true regardless of the lambda size. Excuse me- I'm using C++ here, not C#, but the same principles apply.

In my opinion, people who are against long lambdas are just looking at it wrong. The purpose of a lambda is that code can be re-used like a variable. Consider what would happen if you were talking about storing int as a variable.

void method() {
    static const int num_clients = 65535;
    Socket sockets[num_clients];
    for(int i = 0; i < num_clients; i++) {
        do_something_with_socket(sockets[i]);
    }
}

This is fine and great, regardless of how many lines it takes to init num_clients. Do I have to have some kind of LoC counter or advantage to justify not repeating myself? Of course not. Lambdas are the same way.

void method() {
    auto lambda = [](int x) { std::cout << x; }
    lambda(1);
    int some_int;
    std::cin >> some_int;
    lambda(some_int);
}

Does it matter how many lines lambda is? Of course not- I didn't repeat myself, no matter how many LoC I didn't repeat. But if this code is never needed from outside this function, then it should stay in this function- especially if the lambda uses captured variables. Should they become member variables for use in a single function? Of course not. Should I have to manually pass them around? I hope I didn't want to capture more than a couple variables, or this is gonna become a real mess.

It's pretty simple. Why should code be accessible to the rest of the class just because I need to re-use it in a single function? That's just shoddy logic. Anyone who thinks that lambdas should be kept short hasn't actually thought about the logic of the situation and is stuck in their own pre-defined subjective opinion of what lambdas are and what they're good for, because there is no objective logical reason that they should be short.

Edit: I really mean, short as in, no reason that an anonymous method should be short, compared to a regular method. I'm not trying to suggest or imply anything about the length of methods, in general.

Edit: This is especially bad in languages like C#, where every function has to be part of a class and you can't just create a quick one at namespace scope.

Here's a better example.

WIN32_FIND_DATA FileData;
auto ProcessRenderLibrary = [&](){
    HMODULE module = LoadLibrary(FileData.cFileName);
    void* ptr = GetProcAddress(module, "CreateRender");
    if (ptr) {
        RendererInfo RI = RendererInfo(module, (RendererCreateFunction)ptr, FileData.cFileName);
        std::wstring text = L"I found a renderer, and it is " + RI.name;
        LogText(text);
        Renderers.emplace_back(std::move(RI));
    } else {
        FreeLibrary(module);
    }
};
HANDLE first = FindFirstFile(L"*Render.dll", &FileData);
ProcessRenderLibrary();
while(FindNextFile(first, &FileData)) {
    ProcessRenderLibrary();
}
FindClose(first);
6
  • Nice comparison with variables, although I find the examples a bit obscure. Jun 5, 2011 at 18:45
  • 1
    @Steven Jeuris: I added an example from my own code.
    – DeadMG
    Jun 5, 2011 at 18:57
  • "you can't just create a quick one at namespace scope": Wouldn't that just make that method available to the entire namespace, being worse than a private method? Jun 5, 2011 at 19:06
  • 1
    @Steven Jeuris: Not in a code definition (.cpp) file. Unlike in C#, in C++ each code file is encapsulated quite strongly and you have to explicitly share code between files. This is an incredible bitch most of the time but does have some advantages. In effect, if you are in a cpp file, then a method defined there and not declared anywhere else is effectively private to that file.
    – DeadMG
    Jun 5, 2011 at 19:10
  • 1
    @DeadMG Yes, but it must be declared as "static". C++ defaults to external linkage for functions, and thus a function not declared "static" will be available to any other compilation unit. All they need to do is declare it themselves.
    – quant_dev
    Jun 6, 2011 at 6:11
1

The basic problem in your example is not how to arrange the code within a class. It's rather the fact, that you violate the single responsibility principle.

If you felt, that the parsing method belonged to the responsibility of the class, you wouldn't mind having it be a method (probably a private one).
More generally speaking: If you feel, that a routine should not be available to a whole class, then factor it out of the class completely. Otherwise, let it be a member of the class.

Hiding the violation in a lambda may have slight advantage, but it's a bit like folding regions. You hide poor organization instead of restructuring.

8
  • 2
    You are missing part of my argumentation: "where encapsulating the specific behavior doesn’t make sense". If the action which needs to be performed isn't reuseable in any other context, why would you factor it out completely? Jun 5, 2011 at 18:26
  • 1
    "where encapsulating the specific behaviour doesn't make sense"... that text is not in the question. If you expect us all to visit your blog to read your question then you, sir, are a spammer! Please don't say that's the case.
    – gbjbaanb
    Jun 5, 2011 at 18:33
  • 2
    @Steven Jeuris: That part of your argumentation doesn't really make much sense, which was my point in the first place. Either the routine should be a method of the class, or it should be passed from outside. Any other situation is hypothetical at best. Also you say "where encapsulating the specific behavior doesn’t make sense" but describe a method to "encapsulate behavior which you would otherwise have to make available to the entire class". Which one is it?
    – back2dos
    Jun 5, 2011 at 18:48
  • 2
    I honestly don't see what the SRP has to do with this at all. The code in the question clearly has a single responsibility. It may be a somewhat verbose responsibility but it is not ambiguous or overloaded in any way.
    – Aaronaught
    Jun 6, 2011 at 1:08
  • 1
    @Aaaronaught: The SRP dictates, that all code in a class should only have one shared responsibility. If there is a need to structure it into a nested hierarchy, it is an indicator, that the code should be decomposed further. Also, the example here is not "the code in question". The question is about building nested code hierarchies. The example illustrates the transformation.
    – back2dos
    Jun 6, 2011 at 8:33

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.