Issue with significant indentation

This sample generates an error:

def sequence[A](list: List[Option[A]]): Option[List[A]] =
    list.foldLeft(Option(List.newBuilder[A])):
        (acc, a) =>
            acc.flatMap: xs =>
                a.map: x =>
                    xs.addOne(x)
        .map(_.result())

The error:

-- [E007] Type Mismatch Error: -------------------------------------------------
7 |        .map(_.result())
  |             ^^^^^^^^^^
  |             Found:    List[A]
  |             Required: scala.collection.mutable.Builder[A, List[A]]

The error is thrown because the code is equivalent to this, and it doesn’t make sense to me:

def sequence[A](list: List[Option[A]]): Option[List[A]] =
    list.foldLeft(Option(List.newBuilder[A])):
        (acc, a) =>
            acc.flatMap: xs =>
                a.map: x =>
                    xs.addOne(x)
            .map(_.result())

To make it clear why it’s confusing, this is supposed to be the same expression, but that .map has less indentation than acc.flatMap.

            acc.flatMap: xs =>
                a.map: x =>
                    xs.addOne(x)
        .map(_.result())

Looking at the rules for indentation, I can’t find anything that can explain this.

Is this a bug or a feature?

1 Like

I may be wrong but reading that document, the : at the end of the line for foldLeft starts a new indentation region, so therefore anything not-indented from the first token on the next line then will be part of the argument of foldLeft

next, lets examine this rule for determining if to insert <outdent>

this rule includes method selection, so the first <outdent> will be inserted because it is to the left of a.map: x =>, then as it is to the left of acc.flatMap: xs =>, another <outdent> is inserted. However it is not to the left of the enclosing (acc, a) => , so no extra outdent is inserted. This makes the resulting text equivalent to what you posted as the “correction”.

So now I have learned that indent/outdent is a stack machine

2 Likes

Thanks for looking into it. I’m guessing this is the same thing:

(a: Int) =>
    println(s"Hello, $a!")
.apply(1)
-- [E008] Not Found Error: -----------------------------------------------------
2 |  println(s"Hello, $a!")
3 |.apply(1)
  |  ^
  |  value apply is not a member of Unit

Here the example with comments that tell where the compiler inserted <INDENT> and OUTDENT tokens:

def sequence[A](list: List[Option[A]]): Option[List[A]] =
    // <INDENT>
    list.foldLeft(Option(List.newBuilder[A])):
        // <INDENT>
        (acc, a) =>
            // <INDENT>
            acc.flatMap: xs =>
                // <INDENT>
                a.map: x =>
                    // <INDENT>
                    xs.addOne(x)
                    // <OUTDENT>
                // <OUTDENT>
            // <OUTDENT>
        .map(_.result())
        // <OUTDENT>
    // <OUTDENT>

Now it’s clear what the problem is: The .map call is at the same indentation level as the argument to foldLeft that starts with (acc, a) =>. So no <OUTDENT> can be inserted. Here is a
correct formatting of sequence, which uses a double indent for the closure, so that one can then express a single outdent in front of the .map.

def sequence[A](list: List[Option[A]]): Option[List[A]] =
    list.foldLeft(Option(List.newBuilder[A])): (acc, a) =>
            acc.flatMap: xs =>
                a.map: x =>
                    xs.addOne(x)
        .map(_.result())

Alternatively, align the .map with list:

def sequence[A](list: List[Option[A]]): Option[List[A]] =
    list.foldLeft(Option(List.newBuilder[A])): (acc, a) =>
        acc.flatMap: xs =>
            a.map: x =>
                xs.addOne(x)
    .map(_.result())

I guess the reason the (acc, a) => was put in this intermediate space is that it is visually a bit jarring to do a double indent for the (acc, a) => ... closure so that one can do a single outdent in front of the .map.It does look better with two space indent, though:

    list.foldLeft(Option(List.newBuilder[A])): (acc, a) =>
        acc.flatMap: xs =>
          a.map: x =>
            xs.addOne(x)
      .map(_.result())

Or, for extra clarity:

    list
      .foldLeft(Option(List.newBuilder[A])): (acc, a) =>
        acc.flatMap: xs =>
          a.map: x =>
            xs.addOne(x)
      .map(_.result())```
3 Likes

Thank you both for your explanations.

1 Like