Is `with` really good, idiomatic Kotlin?

On the idioms page of the official documentation is this example for the usage of with:

val myTurtle = Turtle()
with(myTurtle) { //draw a 100 pix square
    penDown()
    for (i in 1..4) {
        forward(100.0)
        turn(90.0)
    }
    penUp()
}

However, I don’t think that it is good code! In larger code blocks it leads to a more or less unreadable mess, because it is unclear what calls refer to the implicit receiver given in the with block. I don’t see a good reason why one wants to use this (terrible!) coding style. To save a few key strokes?

The only valid scenario, I can imagine, is, when all nested calls refer to the object given by with:

val myTurtle = Turtle()
with(myTurtle) {
    penDown()
    forward(100.0)
    turn(90.0)
    penUp()
}

But how often do we see such a simple code? I’d say, not so often. And just to support rare simple cases, it is not worth to offer a tool that deludes developers to write bad code.

There is a reason why with has been fallen out of favor in the JavaScript community:

Using with is not recommended, and is forbidden in ECMAScript 5 strict mode. The recommended alternative is to assign the object whose properties you want to access to a temporary variable.

I would even go as far as deprecating with in Kotlin! But at least I wouldn’t recommend it as “idiomatic”. It is just bad style.

What is your opinion?

1 Like

I @medium,
I partly agree with you.

I suppose you made some mistakes in your post, primary you start from two different language (Kotlin and Javascript) to deduce the same assertion.

The with issue affects also apply and run.

turtle.apply { ... }
turtle.run { .. }

Unfortunately it is pretty easy to put instances in the scope, this is a Kotlin’s feature partly improved in DSL.
This is an interesting feature but it is pretty easy to abuse.

1 Like

What is so bad about with? What are the alternatives for the code you say is bad?

2 Likes

How easy it is to abuse these features is exactly my point. I’ve seen terrible code overusing these “scope functions” in Kotlin. It is a bit like Scala has lots of clever features, that are really easy to abuse in hard to imagine ways. Long story short: I think functions like with do more harm than good.

What do you mean with this statement?

Bad is that calls in a with block are not limited to the one receiver object. Actually this block can contain arbitrary code and it is really hard to read what call refers to the receiver object and what is a usual function call. Scala received a lot of criticism for “implicit parameters” for similar reasons. One of the most important quality aspects of code is that it is easy to reason about.

1 Like

it is unclear what calls refer to the implicit receiver given in the with block

How is with any different from other receiver functions in this regard?

3 Likes
2 Likes

Yes, there are reasons why with is broken in JavaScript, but those reasons cannot be applied to Kotlin’s with because it works fundamentally different to JavaScript’s with.

In JavaScript with enables ways to introduce bugs that cannot happen with Kotlin’s with.

2 Likes

with is not special in this regard. The same problem can happen with let and other scope functions. While useful, there should be some kind of warning in the documentation. Maybe I’ll make a pull request to add it to the documentation, but I wanted to make sure, that I’m not alone with my point of view.

Maybe my analogy with JavaScript’s with was not a good match, so better forget it. My point has actually nothing to do with JavaScript.

I don’t think we should blame the tool if developers abuse it. I like using with to shorten assignment statements.
val noteKey = with(advisingNote) { NoteKey(emplid, institutionId, noteId) }

The alternative would be:
val noteKey = NoteKey(advisingNote.emplid, advisingNote.institutionId, advisingNote.noteId)

I feel like it reduces noise.

2 Likes

I agree, I use often let, run, also, apply, but I never use with because I don’t find good usage for this. Also it can be replaced by run.

I prefer with over run, when I can start a function expression body with it:

fun Bar.foo(baz: Baz) = with(baz) {

}
1 Like

Another interesting usecase for with is context-oriented programming - I think @darksnake wrote an article about it: An introduction to context-oriented programming in Kotlin | by Alexander Nozik | ProAndroidDev
I think kmath is designed that way.

2 Likes

Indeed, there is a second one with more advanced examples. In Kmath we use scope-contexts (using with or run) to define lexical scopes with specific mathematic rules. And multiple receivers will make the framework even more advanced.

4 Likes

I hope this is just a bad example, because declaring an extension-function and then changing the receiver to the argument instead of the extended type seems like exactly the code that the OP is criticizing…

Other than that, I think receiver functions help to reduce noise and make the code more readable.

It was a deliberately chosen example.

First of, I don’t think there is a big difference regarding this argument, as to what can be done with “with” and what can be done with “importing package-level members”. I like to see the “with” as some kind of import.

Second, my example is not bad for the same reasons why it is bad in JavaScript. When using “with” in JavaScript you cannot be sure at compile time which property you are currently accessing. In contrast, the property is determined at compile time in Kotlin, even in my example.

Having said that, I agree that using it can lead to bad code. I just don’t agree that using inherently leads to bad code.

5 Likes

I agree with @medium . Basically I find that all constructs with lambdas that change what this is was a bad language decision for Kotlin and I avoid it where I can. I mentioned it before, I called it this-confusion.

One needs the IDE to assist one with pointing out what this is, and the fact that one needs the IDE to assist here already speaks for itself.

What I find acceptable, even if it is within another class, would be something like this:

fun Turtle.drawCornerRight() {
    penDown()
    forward(100.0)
    turn(90.0)
    penUp()
}

because one can directly read on the function signature what is this. When using with etc, one needs to know that already.

1 Like

Sorry I’m late to this party. If it interests anyone:

  • Modula-2 had a similar with statement long ago. For reasons I don’t understand, Wirth must not have liked it, since he dropped those semantics when he simplified Modula-2 to Oberon, while redefining with to mean something else.

  • Ada has a nice alternative IMHO. You can do this:

     declare T renames myTurtle; -- with Ada 202x you don't have to declare T's type
      begin
          T.penDown; 
          for each in 1 .. 4 loop T.Forward(100.0); T.Turn(90.0); end loop;
          T.penUp;
      end;
    

    The receiver is now clear.

    You can do the same thing in Kotlin, can’t you? I’d think that val T = myTurtle would do the trick, but I’m not sure it would make a reference instead of a copy when myTurtle is on the stack.

But Ada’s renames statement is not like Kotlin’s with, because the receiver is not implicit. I think an explicit receiver would make things more clear in Kotlin, too.

But Ada’s renames statement is not like Kotlin’s with , because the receiver is not implicit.

Correct; that’s why I wrote, “Ada has a nice alternative IMHO.” You criticized (as I understood) the fact that the receiver in a with expression is implicit. The receiver in the Ada approach is explicit but you don’t have to write longVariableName over and over. As far as I can tell, that’s the only reason to use with.

If I misunderstood your intent, then never mind.