We tend to say “desugar” to mean “syntax sweetener” with no change in behavior.
If we assume incorrectly that x
is syntactically the same as (x)
, then we are confused why parens make a difference, because we’re used to wrapping subexpressions in parens for clarity.
The error message is pretty challenging. Here is an example session with Scala 3.
scala> f andThen g(_, '[', ']')
-- [E081] Type Error: --------------------------------------------------------------------------------------------------
1 |f andThen g(_, '[', ']')
| ^
| Missing parameter type
|
| I could not infer the type of the parameter _$1 of expanded function:
| _$1 => f andThen g(_$1, '[', ']').
1 error found
scala> f andThen g(_: String, '[', ']')
-- [E007] Type Mismatch Error: -----------------------------------------------------------------------------------------
1 |f andThen g(_: String, '[', ']')
| ^^^^^^^^^^^^^^^^^^^^^^
| Found: String
| Required: String => Any
|
| longer explanation available when compiling with `-explain`
1 error found
scala> :replay -explain
Unknown command: ":replay", run ":help" for a list of commands
scala> :help
The REPL has several commands available:
:help print this summary
:load <path> interpret lines in a file
:quit exit the interpreter
:type <expression> evaluate the type of the given expression
:doc <expression> print the documentation for the given expression
:imports show import history
:reset [options] reset the repl to its initial state, forgetting all session entries
:settings <options> update compiler options, if possible
scala> :settings -explain
scala> f andThen g(_: String, '[', ']')
-- [E007] Type Mismatch Error: -----------------------------------------------------------------------------------------
1 |f andThen g(_: String, '[', ']')
| ^^^^^^^^^^^^^^^^^^^^^^
| Found: String
| Required: String => Any
|---------------------------------------------------------------------------------------------------------------------
| Explanation (enabled by `-explain`)
|- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
| Tree: g(_$1:String, '[', ']')
| I tried to show that
| String
| conforms to
| String => Nothing
| but the comparison trace ended with `false`:
|
| ==> String <: String => Nothing
| ==> String <: String => Nothing
| <== String <: String => Nothing = false
| <== String <: String => Nothing = false
|
| The tests were made under a constraint with:
| uninstantiated variables:
| constrained types: [A](g: String => A): String => A, [A](g: String => A): String => A
| bounds:
| A
| A
| ordering:
---------------------------------------------------------------------------------------------------------------------
1 error found
scala>
scala> :settings -explain -Vprint:parser,typer
Flag -explain set repeatedly
scala> f andThen g(_: String, '[', ']')
-- [E007] Type Mismatch Error: -----------------------------------------------------------------------------------------
1 |f andThen g(_: String, '[', ']')
| ^^^^^^^^^^^^^^^^^^^^^^
| Found: String
| Required: String => Any
|---------------------------------------------------------------------------------------------------------------------
| Explanation (enabled by `-explain`)
|- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
| Tree: g(_$1:String, '[', ']')
| I tried to show that
| String
| conforms to
| String => Nothing
| but the comparison trace ended with `false`:
|
| ==> String <: String => Nothing
| ==> String <: String => Nothing
| <== String <: String => Nothing = false
| <== String <: String => Nothing = false
|
| The tests were made under a constraint with:
| uninstantiated variables:
| constrained types: [A](g: String => A): String => A, [A](g: String => A): String => A
| bounds:
| A
| A
| ordering:
---------------------------------------------------------------------------------------------------------------------
[[syntax trees at end of typer]] // rs$line$6
package <empty> {
final lazy module val rs$line$6: rs$line$6 = new rs$line$6()
final module class rs$line$6() extends Object() { this: rs$line$6.type =>
val res3: String => String => Nothing =
{
def $anonfun(_$1: String): String => Nothing =
{
def $anonfun(s: String): String = f(s)
closure($anonfun)
}.andThen[Nothing](g(_$1:String, '[', ']'))
closure($anonfun)
}
}
}
1 error found
Here is the tree in Scala 2 under -Vprint:parser,typer
:
scala> (f _) andThen g(_: String, '[', ']')
[[syntax trees at end of parser]] // <console>
package $line7 {
sealed class $read extends _root_.scala.Serializable {
def <init>() = {
super.<init>();
()
};
val $line3$read: $line3.$read.INSTANCE.type = $line3.$read.INSTANCE;
import $line3$read.$iw.g;
val $line4$read: $line4.$read.INSTANCE.type = $line4.$read.INSTANCE;
import $line4$read.$iw.f;
sealed class $iw extends _root_.java.io.Serializable {
def <init>() = {
super.<init>();
()
};
val res2 = ((x$1: String) => (f: (() => <empty>)).andThen(g((x$1: String), '[', ']')))
};
val $iw = new $iw()
};
object $read extends scala.AnyRef {
def <init>() = {
super.<init>();
()
};
val INSTANCE = new $read()
}
}
^
error: type mismatch;
found : String
required: String => ?
[[syntax trees at end of typer]] // <console>
package $line7 {
sealed class $read extends AnyRef with Serializable {
def <init>(): $line7.$read = {
$read.super.<init>();
()
};
private[this] val $line3$read: INSTANCE.type = $line3.$read.INSTANCE;
<stable> <accessor> def $line3$read: INSTANCE.type = $read.this.$line3$read;
import $read.this.$line3$read.$iw.g;
private[this] val $line4$read: INSTANCE.type = $line4.$read.INSTANCE;
<stable> <accessor> def $line4$read: INSTANCE.type = $read.this.$line4$read;
import $read.this.$line4$read.$iw.f;
sealed class $iw extends AnyRef with java.io.Serializable {
def <init>(): $iw = {
$iw.super.<init>();
()
};
private[this] val <res2: error>: String => <error> = ((x$1: String) => ((s: String) => $read.this.$line4$read.$iw.f(s)).andThen[A](g((x$1: String), '[', ']')));
<stable> <accessor> def <res2: error>: String => <error> = $iw.this.<res2: error>
};
private[this] val $iw: $iw = new $read.this.$iw();
<stable> <accessor> def $iw: $iw = $read.this.$iw
};
object $read extends scala.AnyRef with java.io.Serializable {
def <init>(): type = {
$read.super.<init>();
()
};
private[this] val INSTANCE: $line7.$read = new $read();
<stable> <accessor> def INSTANCE: $line7.$read = $read.this.INSTANCE;
<synthetic> private def writeReplace(): Object = new scala.runtime.ModuleSerializationProxy(classOf[$line7.$read$])
}
}
It’s too bad the parser output comes before the error message and caret. Scala 3 underlines the whole expression, which is nice, while Scala 2 just puts the caret at the left paren of the application of g
.
val res2 = ((x$1: String) => (f: (() => <empty>)).andThen(g((x$1: String), '[', ']')))
In this form, and maybe also in Scala 3’s closure notation, you can see that the String
value it found is the result of g
. Although Scala 3 underlines it, I agree that it’s confusing. It doesn’t tell us where a String
value came from, and it doesn’t tell us what requires a String => Any
or String => ?
.
It could also detect that a function expansion is involved and show us the expansion. For “Missing parameter type”, it shows us the expansion in an addendum.
It could also notice that the expansion does not align with where a function type is expected but not found.
If it told us that much, -explain
would really pull its weight.