Implicit lambda names

Problem

  • As it is partly mentioned in this topic, using it as implicit parameter name leads to 2 problems (one is mentioned in that topic):
    • It can be confusing. So there is advice not to use it there.
    • Furthermore, it can be confusing even for small lambdas: .forEach { it.run() } seems less understandable, than .forEach { student : Person -> student.run(); } because we could expect it as some Callable, for example.
      • If you use it somewhere inside some more complicated code, the code becomes absolutely unreadable.
      • Using both .apply and it makes the code unreadable to: someMap.putIfAbsent(it, element.line)?.apply { someHandler(element, it, this) } .
    • The second problem is that using it in nested lambdas is even more confusing. So it would be great to be able to specify the context of it. [There is a solution for the problem: name the parameter explicit, but there is a better solution]

Solution

This solution can solve both problems.

I propose to be able to use any name as an implicit parameter name. The actual name of the parameter can be got from its usage.

  • How that crazy idea can be implemented?
  • Compiler can try to resolve all used variables in the lambda. Then, if there is only one unresolved variable, it is the name of the implicit parameter. This would look somehow like .forEach { student.run() }.
  • This solution provides both laconicism and readability of using of an implicit lambda parameter name.
  • I suppose that suggested syntax is the most comfortable for both reading and writing.
  • Perhaps, you can use the same name in nested lambdas and get the second again, but that case becomes very rare, and can be solved by highlighting in IDE (as it is already done now for it).
  • Comparison of the mentioned approaches:
    Predeclaration:
    open class Person(val id: Int, val name: String, val parent: String)
    class Student(val schoolId: Int, id: Int, name: String, parent: String) : Person(id, name, parent)
    
    • Explicit syntax
      ArrayList<Student>()
          .filter { student -> student.schoolId % 2 == 0 }
          .filterNot { person -> person.id == 0 }
          .map { familyMember -> familyMember.parent.toUpperCase() }
      
    • it based syntax
      ArrayList<Student>()
          .filter { it.schoolId % 2 == 0 }
          .filterNot { it.id == 0 }
          .map { it.parent.toUpperCase() }
      
    • Suggested syntax
      ArrayList<Student>()
          .filter { student.schoolId % 2 == 0 }
          .filterNot { person.id == 0 }
          .map { familyMember.parent.toUpperCase() }
      
  • Limitations:
    • This approach is perfect for small lambdas when it is usually used because a reader can easily resolve its meaning from the context without repeating parameter name (friend -> friend.call()). Nevertheless, the given approach is better even in this case: .map { key[it] } is worse than .map { key[authorizatedUser] }.
    • It can also be used in some cases when it can not be used: when lambda (does not tell)/(tells ambiguously)/(tells wrong way) what it is: if we used it in the previous example, we would think, that it is something implementing Callable, not a person we phone.
    • But it is still a bad idea to use the feature when the code snippet is so big that it makes the reader to find a declaration of the variable
    • As a temporary result, the object of the proposal has a wider range of usages than it, because the reader understands what object it is because of its name, but it is still not as universal as external lambda syntax
  • PS

    Unfortunately, implicit naming leads to another big problem: it becomes sensitive to the naming of other variables in other scopes (both internal and external). It is possible to use labels to specify the context, but this is a lot less obvious than just using explicit parameters then, so the thing we have to do is
    • Making such variable name start with $ or some other unused character to
      • Help reader to understand that it is an implicit parameter name
      • Make such implicit names insensitive to usual variables:
        The problem happened when I renamed key to value because I would try to use external value instead of the implicit parameter.
        val key = 3
        coll.map { value.second }
        
        This way that is not the problem anymore:
        val key = 3
        coll.map { $value.second }
        
        becomes
        val value = 3
        coll.map { $value.second }
        
    • But such names still influence other implicit parameter names, so we could
      • Just not to allow to use same nested implicit names
      • Make them invisible in nested lambdas
      • I choose the second approach because
        • Otherwise, I can not write the lambda without the context and be sure that it is valid because the name can be used somewhere else
        • When we use the it, there is the only reserved name, which is also not a very good one for explicit parameters, so I do not have the same problem
        • Furthermore, it is possible to specify which it is allowed then (in nested lambdas), but we can not specify the list of names because that can be changed.
      • There is also the third approach: to consider variables starting with $ as pseudonyms for it, so using such variables in nested lambdas should be the same with using it in nested lambdas.

I agree with this answer, given to a similar proposal:

Had to search for a while before I could find it :slightly_smiling_face:

When I tried to find similar topics, I didn’t anything. So I created that. Actually, I had already found mentioned problem before you told me, but now I know that I am not along with the idea. Is what I mentioned at the end a solution of the problem? @tieskedh

I like the idea but it has a flow with regard to maintenance: if someone later introduces a variable or property in the containing scope, which has the same name as such a lambda variable, the meaning of the variable changes. If the type of the new entity is incompatible it just won’t compile but sometimes it will, and will do something subtly incorrect. I’m my experience, this sort of thing already happens often enough in long-lived projects with enough staff turnover - or anyone making simple human errors!

To avoid that, how about requiring these variables to have some format of name which can only be used in lambdas, e.g., starting with $ or some character not otherwise used in identifiers?

1 Like
foos.sumBy{
    $foo.bars.sumBy{
        $bar.intValue
    }.sum()
}

or

foos.sumBy{
   //$fooBar.doSomething()
    bars.sumBy{
        $fooBar.intValue
    }.sum()
}

How wouldn’t it change in the last example?

What would be possible is using the labels:

foos.sumBy foos: {
   $foos:fooBar.doSomething()
    bars.sumBy bars: {
        $fooBar.intValue
    }.sum()
}

But in this case, I don’t think you gain anything.

For not-nested lambda’s, I do see some value.
Therefor, I personally wouldn’t mind having implicit lambda names, if and only if a special kind of names are required, such that it’s immediatetly clear that it’s coming from the lambda.

I don’t think I personally would use it, but who knows :wink:

In the second case the existing warning about shadowing variable names would let developers know that there was a possible mistake (just as with standard variable names and scopes), so I think it still works.

1 Like

That is what already written in the last sentence. If I understand you right way.

As you have already noticed there is a problem with nested lambdas that can be solved by labels. But using labels in such case is definetely worse than just explicit naming. So I suppose forbidding using such variables in nested lambdas to be the right solution.

Oops, yes, you’re right - I missed that somehow. Sorry!

I think this idea is even more useful in nested lambdas, so I wouldn’t want to see it forbidden there. You could also solve it by treating the first of such a variable in a lambda as an implicit declaration, in which case the compiler can give you the usual “shadowing declaration” warning. I agree that adding labels would make things harder to understand.

How do you want to guess that it is a shadowing variable, but not just usage of the previous one?

Hmm, good question.

How about … any lambda which uses such variables must mention exactly one new such variable which doesn’t exist in a containing scope, and that mentioned variable is the implicitly declared variable for the lambda.

If you change the containing scope to add such a variable with a name which matches one the lambda, the lambda no longer mentions a new variable, so you get a compiler error.

If you change the containing scope to remove a variable which is mentioned in the lambda (and is therefore not the implicit one), the lambda now has two new such variables, so you get a compiler error.

Does that work?

Actually, no. that leads to other problems: the method taking the lambda can be polymorphic, so it can take both (T) -> X and () -> X, so You would implicitly change the method you call. That is very bad.

Well, I’m saying that we define the behaviour to be that the lambda just won’t compile if it mentions any of these $ variables and doesn’t mention exactly one new one. If you really did mean to have a lambda which does that, which I guess would be unusual (but I may well be wrong) then you have to start the lambda with some ( $_dummy -> ....

I’m not sure that would be pleasant and comprehensible to use but I think it would at least have well-defined behaviour which doesn’t change in unreported ways when you change the outer scope.

We might also want to specify that a lambda which contains any such variables is defined not to have an implicit it. But I don’t think that’s needed and it would make it more ugly to use such variables inside DSL blocks etc.

How do you see interal () -> X lambdas then? They do not introduce any new variable.
(I added one more approach to the text)

The text has been changed a bit. (Its end)

Hadn’t thought of that, either. Maybe some syntax which makes it clear that there are no parameters to the lambda, e.g., { -> bodyOf($theLambda) }, where $theLambda is an implicit variable from an enclosing lambda, and the rule about having to have exactly one new such variable is changed to “zero new such variables” because of the lone arrow.

That’s kinda ugly but it might do :wink:

That would be somethinh like

foos.sumBy {
    -> $foo.bars.sumBy{ -> $bar.intValue }.sum()
}

Are you that it is what you want? Is such syntax obvious? If so, this should be taken under consideration then.

I think it is better jusr=t to specify such lambda with $. Smth like:

foos.sumBy ${
    $foo.bars.sumBy ${ $bar.intValue }.sum()
}

Oh, no.
I liked the reaction after my post :wink:

What do u mean by saying it? I don’t understand it.