Methods & Parameters
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the section bibliography. |
This page is written against Scala 3.10 (3.10.0, 2026-10-12) and every example was run with Scala CLI and Scala 3.10.0
on JDK 26 on 2026-10-15 (the examples were not re-run on 3.9 or 3.3). None of the features used is newer than 3.4,
so they are available on the 3.9 LTS and 3.3 LTS lines as well; only the wording of some diagnostics and the x: _*
deprecation (3.4) depend on the compiler version. A
method is a def: a named, reusable block of code that belongs to a class, an object, a trait or the top level of a
file. Methods are the unit of behaviour in Scala, and almost everything that looks like syntax in other languages
(operators, indexing with (), + on numbers) is an ordinary method call. This page covers how a method is declared and
called; the related topics have their own pages: Currying & Parameter Lists
for several parameter lists, Laziness for lazy val and by-need evaluation,
Functions as Values for the difference between a method and a
function value, Extension Methods for adding methods to existing
types, and Inline for methods expanded at compile time.
The examples use the trailhead scenario of this section: a toolkit that plans hikes on a network of mountain trails.
Defining methods
A method is introduced by def, followed by the name, the parameter list with a type for every parameter, an optional
result type, = and the body. The body is an expression, and the value of its last expression is the result; there is no
need for return.
//> using scala 3.10.0
def walkingMinutes(km: Double, ascentM: Int): Int =
// Naismith's rule: 12 minutes per km plus 10 minutes per 100 m of ascent
math.round(km * 12 + ascentM / 100.0 * 10).toInt
def describe(name: String) = s"Trail $name" // return type inferred: String
def logStart(): Unit = println("planning...")
def sign(n: Int): String =
if n > 0 then "up"
else if n < 0 then "down"
else "flat"
@main def e1(): Unit =
logStart()
println(walkingMinutes(7.5, 640))
println(describe("Aguja"))
println(sign(-3))
val f: Int => String = sign // eta-expansion: method becomes a function value
println(f(5))
planning...
154
Trail Aguja
down
up
Source: Scala 3 Book: Methods
The rules worth knowing:
-
Parameter types are mandatory. Scala infers a method’s result type from its body but never the types of its parameters. The result type is optional for non-recursive methods, but writing it is the convention for every public method, because it keeps the inferred type from changing silently when the body changes.
-
A recursive method must declare its result type, as must an overloaded one; see the pitfalls section for the error.
-
Parameters are immutable. Assigning to one is a compile error, unlike in Java, where parameters are ordinary local variables.
-
Top-level methods (as above) need no enclosing object since Scala 3.0; they are compiled into a synthetic class named after the file. See Packages, Imports & Top-Level Definitions.
-
Unitis the "no useful result" type, the Scala counterpart of Java’svoid. AUnitmethod exists for its side effects. -
A method is not a value. Using a method where a function is expected converts it to a function value automatically (eta-expansion, as
val f: Int => String = signshows). Scala 3 also does it when no function type is expected:val g = signis a function value of typeInt => String(verified on 3.10.0), where Scala 2 requiredsign _. -
defis evaluated at every use,valonce,lazy valonce on first use. This is the difference to keep in mind when you choose between them; the next example makes it visible.
//> using scala 3.10.0
var calls = 0
def tick(): Int =
calls += 1
calls
@main def e23(): Unit =
val v = tick() // evaluated once, now
def d = tick() // evaluated at every use
lazy val l = tick() // evaluated once, at first use
println(s"v=$v d=$d d=$d l=$l l=$l")
val f: () => Int = () => tick() // an explicit function value, needs ()
println(f())
v=1 d=2 d=3 l=4 l=4
5
Source: Scala 3 Book: Methods
Empty parameter lists and uniform access
A method can be declared with no parameter list (def current: Int) or with an empty one (def next(): Int). The two
are different, and Scala 3 enforces the difference at every call:
-
declare
def name: Tfor a pure accessor, something a client could not tell from a field (the uniform access principle), and call it asname; -
declare
def name(): Tfor a method with a side effect and call it asname().
//> using scala 3.10.0
class Counter:
private var n = 0
def next(): Int =
n += 1
n
def current: Int = n
@main def e15(): Unit =
val c = Counter()
println(c.next)
println(c.current())
println("abc".length()) // Java-defined: both forms are accepted
println("abc".length)
[error] ./e15.scala:12:11
[error] method next in class Counter must be called with () argument
[error] println(c.next)
[error] ^^^^^^
[error] ./e15.scala:13:11
[error] method current in class Counter does not take parameters
[error] println(c.current())
[error] ^^^^^^^^^
Compilation failed
Methods defined in Java (and the ones that override them, or that come from Scala 2 libraries) are exempt, which is why
"abc".length and "abc".length() both compile: Java cannot make the distinction, so Scala does not insist on it for
Java methods. Kotlin, by contrast, has properties for accessors and always requires () on a function call; Java always
requires ().
Default arguments
A parameter can have a default value, written after the type. A caller may leave out any argument that has a default, and the compiler fills in the default expression at the call site. The default may refer to parameters of earlier parameter lists, but not to earlier parameters of the same list.
//> using scala 3.10.0
def pace(
distanceKm: Double,
ascentM: Int = 0,
restMinutes: Int = 15,
groupSize: Int = 1
): String =
s"$distanceKm km, +$ascentM m, rest $restMinutes min, group of $groupSize"
@main def e2(): Unit =
println(pace(8.0))
println(pace(8.0, 400))
println(pace(8.0, groupSize = 4))
println(pace(groupSize = 2, distanceKm = 5.5, ascentM = 120))
println(pace(10.0, 300, restMinutes = 30))
8.0 km, +0 m, rest 15 min, group of 1
8.0 km, +400 m, rest 15 min, group of 1
8.0 km, +0 m, rest 15 min, group of 4
5.5 km, +120 m, rest 15 min, group of 2
10.0 km, +300 m, rest 30 min, group of 1
How defaults work, and how they differ from other languages:
-
The compiler generates, for each default, a hidden method named
name$default$N(pace$default$2for the second parameter ofpace). The call site invokes it; the default is therefore not baked into the callee’s bytecode, and a Java caller cannot omit arguments. Java has no default arguments at all and uses overloads instead; Kotlin has them and, as in Scala, they are not visible to Java callers unless you ask for overloads (@JvmOverloads). -
Because the default is a method, an override may change it. The call below uses the default of the object’s runtime class, not the one written in the static type.
-
Only one overloaded alternative may define defaults (see the pitfalls section for the error).
//> using scala 3.10.0
class Planner:
def plan(km: Double, extra: Int = 1): String = s"base $km $extra"
class FastPlanner extends Planner:
override def plan(km: Double, extra: Int = 99): String = s"fast $km $extra"
@main def e5(): Unit =
val p: Planner = FastPlanner()
println(p.plan(2.0))
println(FastPlanner().plan(2.0))
fast 2.0 99
fast 2.0 99
Named arguments
Any argument can be passed by parameter name, in any order. Named and positional arguments can be mixed, with one rule: a positional argument may follow a named one only when the named argument is in its own position. The compiler also rejects unknown names and a parameter given twice.
//> using scala 3.10.0
def pace(distanceKm: Double, ascentM: Int = 0, groupSize: Int = 1): String =
s"$distanceKm/$ascentM/$groupSize"
@main def e6(): Unit =
println(pace(distanceKm = 5.0, 300)) // named argument in its own position, then positional
5.0/300/1
The same call with the named argument out of position, and two other mistakes, give these real compiler messages (each snippet was compiled separately):
println(pace(ascentM = 100, 5.0))
println(pace(5.0, ascent = 100))
println(pace(5.0, ascentM = 1, ascentM = 2))
[error] positional after named argument
[error] println(pace(ascentM = 100, 5.0))
[error] ^^^
[error] method pace: (distanceKm: Double, ascentM: Int, groupSize: Int): String does not have a parameter ascent
[error] println(pace(5.0, ascent = 100))
[error] ^^^^^^^^^^^^
[error] parameter ascentM of method pace: (distanceKm: Double, ascentM: Int, groupSize: Int): String is already instantiated
[error] println(pace(5.0, ascentM = 1, ascentM = 2))
[error] ^^^^^^^^^^^
Named arguments make the parameter names part of a method’s public API: renaming a parameter breaks callers that name it. Named arguments work for Scala-defined methods only; a Java method’s parameter names are not available to Scala callers by default, so they cannot be named. Kotlin and Python also have named arguments; Java does not.
By-name parameters
A parameter whose type is written => T is by-name: the argument expression is not evaluated at the call. It is
evaluated every time the method body uses the parameter, and not at all if the body never does. An ordinary parameter is
by-value, evaluated once, before the method runs. By-name parameters are how Scala writes control structures as
library methods, such as Option.getOrElse, Try, Future and the boundary helpers, and how a message is built only
when it is needed.
//> using scala 3.10.0
var evaluations = 0
def forecast(): Boolean =
evaluations += 1
println(" (calling the weather service)")
true
def byValue(clear: Boolean): String = if clear then "go" else "stay"
def byName(clear: => Boolean): String = if clear then "go" else "stay"
def twice(clear: => Boolean): Int = if clear && clear then 2 else 0
def when(open: Boolean)(msg: => String): Unit =
if open then println(msg)
@main def e7(): Unit =
println("by value:")
println(byValue(forecast()))
println("by name, used once:")
println(byName(forecast()))
println("by name, used twice:")
println(twice(forecast()))
println(s"evaluations = $evaluations")
println("lazy message:")
when(false)({ println(" (building the message)"); "never shown" })
when(true)("hut is open")
by value:
(calling the weather service)
go
by name, used once:
(calling the weather service)
go
by name, used twice:
(calling the weather service)
(calling the weather service)
2
evaluations = 4
lazy message:
hut is open
Details:
-
A by-name parameter behaves like a parameterless function
() => Tthat the compiler creates and calls for you. Unlike a by-need (lazy) value it re-evaluates on every use; if you want "at most once", copy it into alazy valinside the method (see Laziness). -
The argument is evaluated in the caller’s scope, so it can use the caller’s variables, and a side effect inside it happens as many times as the method uses it (twice for
twiceabove). -
By-name applies to parameters of methods and of classes (not of
case classconstructors orval/varclass parameters, which must be values). There is also a by-name flavour for context parameters,using x: => T(Givens & Using Clauses). -
A by-name parameter is erased to
Function0, so two overloads that differ only in=> Aversus=> Bclash; see@targetNamebelow. Java and Kotlin have no by-name parameters; they pass aSupplieror a() -> Tlambda and the call site writes the lambda.
Repeated parameters and vararg splices
A parameter type followed by * is a repeated parameter (a vararg): the method accepts zero or more arguments of that
type, and inside the body the parameter is a Seq[T] (an immutable Seq since Scala 2.13). Only the last parameter of a list may be repeated. To pass an
existing collection as the varargs, splice it with a postfix * after the expression: xs*.
//> using scala 3.10.0
def totalKm(legs: Double*): Double = legs.sum
def label(prefix: String, names: String*): String =
names.map(n => s"$prefix$n").mkString(", ")
@main def e8(): Unit =
println(totalKm())
println(totalKm(3.5))
println(totalKm(3.5, 2.0, 4.25))
val legs = List(1.0, 2.0, 3.0)
println(totalKm(legs*)) // splice a Seq with xs*
println(totalKm(0.5 +: legs*)) // splice an expression
println(label("trail-", "A", "B"))
val arr = Array("x", "y")
println(label("p-", arr*))
0.0
3.5
9.75
6.0
6.5
trail-A, trail-B
p-x, p-y
Splicing changed syntax in Scala 3. Scala 2 wrote xs: _*; Scala 3 writes xs*, "analogously to how a vararg
parameter is declared". The old form is deprecated since Scala 3.4 and on 3.10.0 still compiles but warns, and the
compiler can rewrite it (the rewrite changed the source file shown below, the -source:3.4-migration -rewrite
options are in the first two lines):
//> using scala 3.10.0
//> using option -source:3.4-migration -rewrite
def totalKm(legs: Double*): Double = legs.sum
@main def e21(): Unit =
val legs = List(1.0, 2.0)
println(totalKm(legs: _*))
[warn] ./e21.scala:8:25
[warn] The syntax `x: _*` is no longer supported for vararg splices; use `x*` instead
[warn] This construct can be rewritten automatically under -rewrite -source 3.4-migration.
[warn] println(totalKm(legs: _*))
[warn] ^
[patched file e21.scala]
3.0
After the rewrite the call line reads println(totalKm(legs*)). The same * is used in patterns (case List(0, 1, xs*));
see Pattern Matching.
Two limits apply when you splice. A sequence cannot be passed where a single element is expected, and a splice must be the only argument for the repeated parameter, on 3.10.0 without any flags:
println(totalKm(legs))
println(totalKm(1.0, legs*))
[error] Found: (legs : List[Double])
[error] Required: Double
[error] println(totalKm(legs))
[error] ^^^^
[error] Sequence argument type annotation `*` cannot be used here:
[error] it is not the only argument to be passed to the corresponding repeated parameter Double*
[error] println(totalKm(1.0, legs*))
[error] ^^^^^
Mixing elements and a splice (totalKm(1.0, legs*)) is what the flexible varargs proposal (SIP-70) addresses; it was
experimental on 3.9 and is not covered here, so use 1.0 +: legs* as in the example. Java sees a repeated parameter
as a Seq, not an array. Java’s T... is an array, and Kotlin’s vararg is an array with the spread operator *arr.
Nested methods
A method can be defined inside another method, or inside any block. A nested method sees the enclosing method’s parameters and local values (it captures them), but nothing outside sees it. This is the usual way to hide a helper, to write an accumulating loop, and to avoid giving a one-off function a place in the public API.
//> using scala 3.10.0
def climb(ascents: List[Int]): Int =
def sum(rest: List[Int], acc: Int): Int = // nested helper
rest match
case Nil => acc
case h :: t => sum(t, acc + h)
sum(ascents, 0)
def scaled(xs: List[Int], factor: Int): List[Int] =
def times(n: Int) = n * factor // captures factor from the enclosing method
xs.map(times)
@main def e11(): Unit =
println(climb(List(120, 340, 80)))
println(scaled(List(1, 2, 3), 10))
540
List(10, 20, 30)
Source: Tour of Scala: Nested Methods
Nested methods can be recursive (and tail-recursive, see Recursion & Tail Calls) and, like any recursive method, need an explicit result type. Java has no nested methods (only lambdas and local classes); Kotlin has local functions much like Scala’s.
Operators are methods
Scala has no operators in the language sense. a + b is the method call a.+(b), and any method with a single
parameter can be written infix, a method b. The method names +, ::, <<, && and so on are ordinary identifiers
built from symbolic characters, which means you can define your own the same way. The compiler turns the symbolic names
into alphanumeric names in the bytecode (+ becomes $plus).
//> using scala 3.10.0
import scala.annotation.targetName
case class Km(value: Double):
@targetName("plus")
def +(other: Km): Km = Km(value + other.value)
@targetName("times")
def *(factor: Double): Km = Km(value * factor)
def unary_- : Km = Km(-value)
infix def per(days: Int): Double = value / days
def max(other: Km): Km = if value >= other.value then this else other
@main def e12(): Unit =
val a = Km(4.0)
val b = Km(2.5)
println(a + b)
println(a.+(b))
println(a * 2)
println(-a)
println(a per 2) // allowed: declared infix
println(a.max(b))
println(a max b) // alphanumeric infix without the modifier
[warn] ./e12.scala:22:13
[warn] Alphanumeric method max is not declared infix; it should not be used as infix operator.
[warn] Instead, use method syntax .max(...) or backticked identifier `max`.
[warn] The latter can be rewritten automatically under -rewrite -source 3.4-migration.
[warn] println(a max b) // alphanumeric infix without the modifier
[warn] ^^^
Km(6.5)
Km(6.5)
Km(8.0)
Km(-4.0)
2.0
Km(4.0)
Km(4.0)
Source: Tour of Scala: Operators
The pieces, from the example:
-
Infix, prefix and postfix.
a + bis infix. A prefix operator is a method namedunary_+,unary_-,unary_!orunary_~(only those four), called as-a; note the space before the colon indef unary_- : Km, which stops the:from becoming part of the name. Postfix calls (a max) are a feature that needs an import and a warning and are best avoided. -
Alphanumeric methods used infix (
a max b) need theinfixmodifier on their definition; without it the 3.10.0 compiler warns, as shown. See below. -
Backticks make any text an identifier:
is a legal method name, and a backticked name can also be used to call a reserved word defined elsewhere. Operators must be defined with a name that is legal on the JVM or have atrail length in km@targetName.
Precedence and associativity
When an expression chains operators, the first character of each operator decides its precedence. From the lowest to the highest:
| Precedence (lowest first) | First character of the operator |
|---|---|
1 |
any letter, |
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
any other special character |
Parentheses override it. The last character decides associativity: an operator ending in : is right-associative
(a :: b :: c is a :: (b :: c), and x :: xs is the method call xs.::(x) on the right operand), and every other
operator is left-associative. Also, an operator ending in = that is not a comparison (+=, ++=, ::=) is an
assignment operator: if the left side has no method with that name, a += b is rewritten to a = a + b and a must be
a var.
//> using scala 3.10.0
@main def e13(): Unit =
println(1 + 2 * 3)
println((1 + 2) * 3)
println(2 :: 3 :: Nil) // right-associative: ends with ':'
println(Nil.::(3).::(2)) // what the compiler turns it into
println(10 - 3 - 2) // left-associative
println(1 << 2 + 1) // + binds tighter than <<
println(1 + 1 == 2 && 3 > 2 || false)
val xs = List(1, 2, 3)
println(0 +: xs :+ 4) // mixing prepend and append
7
9
List(2, 3)
List(2, 3)
5
8
true
List(0, 1, 2, 3, 4)
You can define a right-associative operator and an assignment-style use the same way. Here a Route grows from either
end:
//> using scala 3.10.0
import scala.annotation.targetName
case class Route(stops: List[String]):
@targetName("prepend")
def ::(stop: String): Route = Route(stop :: stops) // right-associative: ends with ':'
@targetName("append")
def +(stop: String): Route = Route(stops :+ stop)
def `trail length in km`(): Double = 12.5
@main def e22(): Unit =
val r = "Refugio" :: "Collado" :: Route(List("Pico"))
println(r)
var route = r
route += "Valle" // expands to route = route + "Valle"
println(route)
println(`trail length in km`())
Route(List(Refugio, Collado, Pico))
Route(List(Refugio, Collado, Pico, Valle))
12.5
Source: Scala 3 Book: Methods and Tour of Scala: Operators
Java and Kotlin differ here. Java has a fixed set of operators with fixed precedence and no user-defined ones (not even
+ on your own types); Kotlin allows overloading a fixed set of operators through operator fun plus(...) and
infix fun for named ones. In Scala you choose the name, and its first and last character decide the precedence and
associativity, so keep your own operators few and conventional.
The infix modifier
Scala 3 lets the author of a method decide whether it may be written infix, so every use site reads the same way. An
infix method must take exactly one parameter in its first non-receiver parameter list. The modifier is also allowed on
types with exactly two type parameters (infix type or[X, Y], usable as String or Int). Rules, from the reference:
-
Symbolic operators are always allowed infix, so
infixis redundant on them. -
Alphanumeric methods used infix are deprecated unless the method carries
infix, was compiled with Scala 2, or the call is followed by an opening brace. The deprecation starts from Scala 3.1; on 3.10.0 it is the warning shown in thea max bexample, with a rewrite to the backticked formaavailable undermaxb-rewrite -source 3.4-migration. -
infixis a soft modifier: it is only a keyword in modifier position, soinfixremains a legal identifier. -
An override must agree: if a method overrides another, both are
infixor neither is.
Libraries use infix to keep a fluent style legal, as in the a per 2 call above and in the route DSL covered in
Domain Specific Languages. Kotlin’s infix fun is the
same idea; Scala 2 had no such modifier and allowed any one-parameter method infix.
@targetName
@targetName("external") (package scala.annotation) gives a definition a different name in the generated bytecode. The
Scala name is unchanged, and Scala callers keep writing the symbolic name; the external name is what Java sees, what
stack traces print, and what disambiguates definitions that would clash after erasure. The reference recommends it for
every symbolic operator, and a definition with a backticked name that is not legal on the platform also needs one.
It is allowed on every kind of definition except a top-level class, trait or object, and the name must be legal on the
host platform. javap shows the effect:
//> using scala 3.10.0
import scala.annotation.targetName
object Ops:
def plain(x: Int): Int = x
def +(x: Int, y: Int): Int = x + y
@targetName("add")
def ++(x: Int, y: Int): Int = x + y
def pace(km: Double, ascent: Int = 0): String = s"$km/$ascent"
public final class Ops$ {
public static final Ops$ MODULE$;
public static {};
public int plain(int);
public int $plus(int, int);
public int add(int, int);
public java.lang.String pace(double, int);
public int pace$default$2();
}
The listing is the output of javap -cp <classes> 'Ops$' after compiling the object with Scala 3.10.0 (it also shows the
pace$default$2 method that carries the default argument). For a top-level object, Scala also emits static forwarders,
so a Java caller writes Ops.add(1, 2) for ++ instead of the cryptic Ops.$plus$plus(1, 2), while + (with no
annotation) keeps its encoded name Ops.$plus. @targetName also resolves clashes: two overloads that differ only in a by-name parameter
type both erase to Function0 and are rejected, and one annotation fixes it.
//> using scala 3.10.0
import scala.annotation.targetName
def f(x: => String): Int = x.length
def f(x: => Int): Int = x + 1
[error] ./e24.scala:5:5
[error] Conflicting definitions:
[error] def f(x: => String): Int in the top-level definitions in package <empty> at line 4 and
[error] def f(x: => Int): Int in the top-level definitions in package <empty> at line 5
[error] have the same type (x: Function0): Int after erasure.
[error]
[error] Consider adding a @targetName annotation to one of the conflicting definitions
[error] for disambiguation.
[error] def f(x: => Int): Int = x + 1
[error] ^
With @targetName("fString") on the first definition the program compiles, and (f("abc"), f(41)) prints (3,42).
An override must keep the target name of the method it overrides: adding or changing a @targetName on an override is an
error, because the erased names must agree.
Dropped: procedure syntax and auto-application
Two Scala 2 idioms are gone.
Procedure syntax, a method body in braces without =, was a way to declare a Unit method. In Scala 3 it is a syntax
error; write : Unit =. The compiler can rewrite old code, and Scalafix also has a rule for it.
//> using scala 3.10.0
def hello() {
println("hello")
}
@main def e16(): Unit = hello()
[error] ./e16.scala:3:13
[error] '=' expected, but '{' found
[error] def hello() {
[error] ^
Compilation failed
With //> using option -source:3.0-migration -rewrite the same file compiles, prints hello, and the compiler rewrites
the declaration to def hello(): Unit = {.
Auto-application, calling next for def next(): T and having the compiler insert (), is an error in Scala 3 (see the
Counter example above). The call must match the declaration exactly, and an override must agree with the overridden
method on (), with the Java and Scala 2 exceptions described there. Existing code can be compiled with
-source:3.0-migration, and -rewrite fixes the calls.
Putting it together
The planner below combines a symbolic operator with @targetName, an infix method, a repeated parameter spliced with
xs*, a second parameter list, and named and default arguments. The assertion at the end checks the per result.
//> using scala 3.10.0
import scala.annotation.targetName
case class Km(value: Double):
@targetName("plus")
def +(other: Km): Km = Km(value + other.value)
infix def per(days: Int): Km = Km(value / days)
def plan(legs: Km*)(name: String = "Untitled", restMinutes: Int = 15): String =
val total = legs.foldLeft(Km(0))(_ + _)
s"$name: ${total.value} km, rest $restMinutes min, ${legs.size} legs"
@main def e26(): Unit =
val legs = List(Km(4.0), Km(2.5))
println(plan(legs*)(name = "Aguja loop"))
println(plan(Km(1.0))(restMinutes = 5))
assert((Km(6.0) per 3) == Km(2.0))
Aguja loop: 6.5 km, rest 15 min, 2 legs
Untitled: 1.0 km, rest 5 min, 1 legs
Source: Scala 3 Book: Methods
Pitfalls
-
Recursive methods need a result type. Without it the compiler cannot infer the type of the recursive call.
-
Parameters cannot be reassigned. Copy to a local
varif you must. -
Only one overload may declare default arguments.
-
Positional after named is rejected unless the named argument sits in its own position.
The first three compile errors, from compiling the snippets below:
def bump(n: Int): Int =
n = n + 1
n
def fact(n: Int) = if n <= 1 then 1 else n * fact(n - 1)
class Trailhead:
def plan(km: Double, rest: Int = 10): String = "a"
def plan(name: String, rest: Int = 10): String = "b"
[error] Reassignment to val n
[error] n = n + 1
[error] ^^^^^^^^^
[error] Overloaded or recursive method fact needs return type
[error]
[error] Run with -explain-cyclic for more details.
[error] def fact(n: Int) = if n <= 1 then 1 else n * fact(n - 1)
[error] ^
[error] two or more overloaded variants of method plan have default arguments
[error] def plan(name: String, rest: Int = 10): String = "b"
[error] ^
Source: Scala 3 Book: Methods
-
-5.absparses with a warning (Illegal literal) on 3.10.0; write(-5).absor5.absfor clarity. -
Alphanumeric infix (
a max b) warns unless the method isinfixor was compiled with Scala 2. -
Operators change precedence by their first character. A custom operator that begins with
|binds looser than one beginning with+; check the table before relying on a chain. -
Default values are evaluated at every call in which they are used, like a by-name parameter. A default such as
startedAt: Long = System.currentTimeMillisreads the clock on each call.
Where the books are outdated
The books consulted for this section (see the section bibliography) were written for Scala 2 or for the first Scala 3 releases. For methods and parameters, treat these points as superseded by the pages cited here:
-
Procedure syntax and auto-application appear in Scala 2 code and in the older books. Both are errors in Scala 3.
-
xs: _*is how the older books splice a collection; Scala 3 usesxs*and warns about the old form on 3.10.0. -
Infix use of alphanumeric methods is shown without a modifier; Scala 3 expects
infixand warns otherwise. -
@targetNameandinfixdo not exist in the Scala 2 books at all. -
By-name parameters, default and named arguments, nested methods and operator precedence are unchanged since Scala 2 and the explanations still hold; only the surrounding syntax (braceless blocks, top-level methods,
then) differs.
The run results above are from 2026-10-15 on Scala 3.10.0 and were not re-run on the 3.9 LTS or 3.3 LTS releases.