Skip to content

Commit 5b7406f

Browse files
committed
New section on inline
1 parent fa420fe commit 5b7406f

File tree

3 files changed

+168
-0
lines changed

3 files changed

+168
-0
lines changed

docs/docs/reference/inline.md

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
---
2+
layout: doc-page
3+
title: Inline
4+
---
5+
6+
`inline` is a new modifier that guarantees that a definition will be
7+
inlined at the point of use. Example:
8+
9+
object Config {
10+
inline val logging = false
11+
}
12+
13+
object Logger {
14+
15+
private var indent = 0
16+
17+
inline def log[T](msg: => String)(op: => T): T =
18+
if (Config.logging) {
19+
println(s"${" " * indent}start $msg")
20+
indent += 1
21+
val result = op
22+
indent -= 1
23+
println(s"${" " * indent}$msg = $result")
24+
result
25+
}
26+
else op
27+
}
28+
29+
The `Config` object contains a definition of an `inline` value
30+
`logging`. This means that `logging` is treated as a constant value,
31+
equivalent to its right-hand side `false`. The right-hand side of such
32+
an inline val must itself be a constant expression. Used in this way,
33+
`inline` is equivalent to Java and Scala 2's `final`. `final` meaning
34+
"constant" is still supported in Dotty, but will be phased out.
35+
36+
The `Logger` object contains a definition of an `inline` method `log`.
37+
This method will always be inlined at the point of call.
38+
39+
In the inlined code, an if-then-else with a constant condition will be
40+
rewritten to its then- or else-part. Here's an example:
41+
42+
def factorial(n: BigInt): BigInt =
43+
log(s"factorial($n)") {
44+
if (n == 0) 1
45+
else n * factorial(n - 1)
46+
}
47+
48+
If `Config.logging == false`, this will be rewritten to
49+
50+
def factorial(n: BigInt): BigInt = {
51+
def msg = s"factorial($n)"
52+
def op =
53+
if (n == 0) 1
54+
else n * factorial(n - 1)
55+
op
56+
}
57+
58+
Note that the arguments corresponding to the parameters `msg` and `op`
59+
of the inline method `log` are defined before the inlined body (which
60+
is in this case simply `op`). By-name parameters of the inlined method
61+
correspond to `def` bindings whereas by-value parameters correspond to
62+
`val` bindings. Do if `log` was defined like this:
63+
64+
inline def log[T](msg: String)(op: => T): T = ...
65+
66+
we'd get
67+
68+
val msg = s"factorial($n)"
69+
70+
instead. This behavior is designed so that calling an inline method is
71+
semantically the same as calling a normal method: By-value arguments
72+
are evaluated before the call wherea by-name arguments are evaluated
73+
each time they are referenced. As a consequence, it is often
74+
preferrable to make arguments of inline methods by-name in order to
75+
avoid unnecessary evaluations.
76+
77+
Inline methods can be recursive. For instance, when called with a constant
78+
exponent `n`, the following method for `power` will be implemented by
79+
straight inline code without any loop or recursion.
80+
81+
inline def power(x: Double, n: Int): Double =
82+
if (n == 0) 1.0
83+
else if (n == 1) x
84+
else {
85+
val y = power(x, n / 2)
86+
if (n % 2 == 0) y * y else y * y * x
87+
}
88+
89+
power(expr, 10)
90+
// translates to
91+
//
92+
// val x = expr
93+
// val y1 = x * x // ^2
94+
// val y2 = y1 * y1 // ^4
95+
// val y3 = y2 * x // ^5
96+
// y3 * y3 // ^10
97+
98+
Parameters of inline methods can themselves be marked `inline`. This means
99+
that the argument of an inline method is itself inlined in the inlined body of
100+
the method. Using this scheme, we can define a zero-overhead `foreach` method
101+
that translates into a straightforward while loop without any indirection or
102+
overhead:
103+
104+
inline def foreach(inline op: Int => Unit): Unit = {
105+
var i = from
106+
while (i < end) {
107+
op(i)
108+
i += 1
109+
}
110+
}
111+
112+
## Relationship to `@inline`.
113+
114+
Existing Scala defines a `@inline` annotation which is used as a hint
115+
for the backend to inline. For most purposes, this annotation is
116+
superseded by the `inline` modifier. The modifier is more powerful
117+
than the annotation: Expansion is guarenteed instead of best effort,
118+
it happens in the fronend instead of in the backend, and it also applies
119+
to method arguments and recursive methods.
120+
121+
Since `inline` is now a keyword, it would be a syntax error to write
122+
`@inline`. Hwoever, one can still refer to the annotation by putting
123+
it in backticks, i.e.
124+
125+
@`inline` def ...
126+
127+
128+
129+
130+

docs/sidebar.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ sidebar:
33
url: blog/index.html
44
- title: Reference
55
subsection:
6+
- title: Implicit Function Types
7+
url: docs/reference/implicit-function-types.md
68
- title: Intersection types
79
url: docs/reference/intersection-types.html
810
- title: Union types
@@ -25,6 +27,8 @@ sidebar:
2527
url: docs/reference/auto-parameter-tupling.html
2628
- title: Named Type Arguments
2729
url: docs/reference/named-typeargs.html
30+
- title: Inline
31+
url: docs/reference/inline.html
2832
- title: Usage
2933
subsection:
3034
- title: sbt-projects

tests/pos/reference/inlines.scala

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package inlines
2+
3+
object Config {
4+
inline val logging = false
5+
}
6+
7+
object Logger {
8+
9+
private var indent = 0
10+
11+
inline def log[T](msg: String)(op: => T): T =
12+
if (Config.logging) {
13+
println(s"${" " * indent}start $msg")
14+
indent += 1
15+
val result = op
16+
indent -= 1
17+
println(s"${" " * indent}$msg = $result")
18+
result
19+
}
20+
else op
21+
}
22+
23+
object Test {
24+
import Logger._
25+
26+
def factorial(n: BigInt): BigInt =
27+
log(s"factorial($n)") {
28+
if (n == 0) 1
29+
else n * factorial(n - 1)
30+
}
31+
32+
def main(args: Array[String]): Unit =
33+
println(factorial(33))
34+
}

0 commit comments

Comments
 (0)