@@ -17,12 +17,12 @@ object Logger {
17
17
18
18
private var indent = 0
19
19
20
- inline def log [T ](msg : => String )(op : => T ): T =
20
+ inline def log [T ](msg : String , indentMargin : => Int )(op : => T ): T =
21
21
if (Config .logging) {
22
22
println(s " ${" " * indent}start $msg" )
23
- indent += 1
23
+ indent += indentMargin
24
24
val result = op
25
- indent -= 1
25
+ indent -= indentMargin
26
26
println(s " ${" " * indent}$msg = $result" )
27
27
result
28
28
}
@@ -34,24 +34,28 @@ The `Config` object contains a definition of the **inline value** `logging`.
34
34
This means that ` logging ` is treated as a _ constant value_ , equivalent to its
35
35
right-hand side ` false ` . The right-hand side of such an ` inline val ` must itself
36
36
be a [ constant expression] ( https://scala-lang.org/files/archive/spec/2.12/06-expressions.html#constant-expressions ) . Used in this
37
- way, ` inline ` is equivalent to Java and Scala 2's ` final ` . ` final ` meaning
38
- _ inlined constant_ is still supported in Dotty, but will be phased out.
37
+ way, ` inline ` is equivalent to Java and Scala 2's ` final ` . Note that ` final ` , meaning
38
+ _ inlined constant_ , is still supported in Dotty, but will be phased out.
39
39
40
- The ` Logger ` object contains a definition of the ** inline method** ` log ` .
41
- This method will always be inlined at the point of call.
40
+ The ` Logger ` object contains a definition of the ** inline method** ` log ` . This
41
+ method will always be inlined at the point of call.
42
42
43
- In the inlined code, an if-then-else with a constant condition will be rewritten
44
- to its then- or else-part. Consequently, in the ` log ` method above
45
- ` if (Config.logging) ` with ` Config.logging == true ` will rewritten into its then-part.
43
+ In the inlined code, an ` if-then-else ` with a constant condition will be rewritten
44
+ to its ` then ` - or ` else ` -part. Consequently, in the ` log ` method above the
45
+ ` if (Config.loggi0ng) ` with ` Config.logging == true ` will get rewritten into its
46
+ ` then ` -part.
46
47
47
48
Here's an example:
48
49
49
50
``` scala
50
- def factorial (n : BigInt ): BigInt =
51
- log(s " factorial( $n) " ) {
51
+ var indentSetting = 2
52
+
53
+ def factorial (n : BigInt ): BigInt = {
54
+ log(s " factorial( $n) " , indentSetting) {
52
55
if (n == 0 ) 1
53
56
else n * factorial(n - 1 )
54
57
}
58
+ }
55
59
```
56
60
57
61
If ` Config.logging == false ` , this will be rewritten (simplified) to
@@ -63,61 +67,33 @@ def factorial(n: BigInt): BigInt = {
63
67
}
64
68
```
65
69
66
- and if ` true ` it will be rewritten to the code below:
70
+ As you notice, since neither ` msg ` or ` indentMargin ` were used, they do not
71
+ appear in the generated code for ` factorial ` . Also note the body of our ` log `
72
+ method: the ` else- ` part reduces to just an ` op ` . In the generated code we do
73
+ not generate any closures because we only refer to a by-name parameter * once* .
74
+ Consequently, the code was inlined directly and the call was beta-reduced.
75
+
76
+ In the ` true ` case the code will be rewritten to:
67
77
68
78
``` scala
69
79
def factorial (n : BigInt ): BigInt = {
70
- val msgVal = s " factorial( $n) "
71
- println(s " ${" " * indent}start $msgVal" )
72
- Logger .inline$indent += 1
73
- val result = op
74
- Logger .inline$indent -= 1
75
- println(s " ${" " * indent}$msgVal = $result" )
80
+ val msg = s " factorial( $n) "
81
+ println(s " ${" " * indent}start $msg" )
82
+ Logger .inline$indent += indentSetting
83
+ val result =
84
+ if (n == 0 ) 1
85
+ else n * factorial(n - 1 )
86
+ Logger .inline$indent -= indentSetting
87
+ println(s " ${" " * indent}$msg = $result" )
76
88
result
77
89
}
78
90
```
79
- TODO: adapt to real code.
80
- Note (1) that the arguments corresponding to the parameters ` msg ` and ` op ` of
81
- the inline method ` log ` are defined before the inlined body (which is in this
82
- case simply ` op ` (2)). By-name parameters of the inline method correspond to
83
- ` def ` bindings whereas by-value parameters correspond to ` val ` bindings. So if
84
- ` log ` was defined like this:
85
-
86
- ``` scala
87
- inline def log [T ](msg : String )(op : => T ): T = ...
88
- ```
89
-
90
- we'd get
91
91
92
- ``` scala
93
- val msg = s " factorial( $n) "
94
- ```
95
-
96
- instead. This behavior is designed so that calling an inline method is
97
- semantically the same as calling a normal method: By-value arguments are
98
- evaluated before the call whereas by-name arguments are evaluated each time they
99
- are referenced. As a consequence, it is often preferable to make arguments of
100
- inline methods by-name in order to avoid unnecessary evaluations. Additionally,
101
- in the code above, our goal is to print the result after the evaluation of ` op ` .
102
- Imagine, if we were printing the duration of the evaluation between the two
103
- prints.
104
-
105
- For instance, here is how we can define a zero-overhead ` foreach ` method that
106
- translates into a straightforward while loop without any indirection or
107
- overhead:
108
-
109
- ``` scala
110
- inline def foreach (op : => Int => Unit ): Unit = {
111
- var i = from
112
- while (i < end) {
113
- op(i)
114
- i += 1
115
- }
116
- }
117
- ```
92
+ Note, that the by-value parameter is evaluated only once, per the usual Scala
93
+ semantics, by binding the value and reusing the ` msg ` through the body of
94
+ ` factorial ` .
118
95
119
- By contrast, if ` op ` is a call-by-value parameter, it would be evaluated
120
- separately as a closure.
96
+ ### Recursive Inline Methods
121
97
122
98
Inline methods can be recursive. For instance, when called with a constant
123
99
exponent ` n ` , the following method for ` power ` will be implemented by
@@ -333,30 +309,30 @@ inline def defaultValue[T] = inline erasedValue[T] match {
333
309
case _ : Double => Some (0.0d )
334
310
case _ : Boolean => Some (false )
335
311
case _ : Unit => Some (())
336
- case _ : t >: Null => Some (null )
337
312
case _ => None
338
313
}
339
314
```
340
315
341
316
Then:
342
317
``` scala
343
- defaultValue [Int ] = Some ( 0 )
344
- defaultValue[ Boolean ] = Some ( false )
345
- defaultValue[ String | Null ] = Some ( null )
346
- defaultValue[ AnyVal ] = None
318
+ val dInt : Some [Int ] = defaultValue[ Int ]
319
+ val dDouble : Some [ Double ] = defaultValue[ Double ]
320
+ val dBoolean : Some [ Boolean ] = defaultValue[ Boolean ]
321
+ val dAny : None . type = defaultValue[ Any ]
347
322
```
348
323
349
- As another example, consider the type-level version of ` toNat ` above: given a
350
- _ type_ representing a Peano number, return the integer _ value_ corresponding to
351
- it. Here's how this can be defined:
324
+ As another example, consider the type-level version of ` toNat ` above the we call
325
+ ` toIntT ` : given a _ type_ representing a Peano number, return the integer _ value_
326
+ corresponding to it. Consider the definitions of numbers as in the _ Inline
327
+ Match_ section aboce. Here's how ` toIntT ` can be defined:
352
328
353
329
``` scala
354
- inline def toInt [N <: Nat ] <: Int = inline scala.compiletime.erasedValue[N ] match {
355
- case _ : Zero => 0
330
+ inline def toIntT [N <: Nat ] <: Int = inline scala.compiletime.erasedValue[N ] match {
331
+ case _ : Zero . type => 0
356
332
case _ : Succ [n] => toIntT[n] + 1
357
333
}
358
334
359
- final val two = toInt [Succ [Succ [Zero ]]]
335
+ final val two = toIntT [Succ [Succ [Zero . type ]]]
360
336
```
361
337
362
338
` erasedValue ` is an ` erased ` method so it cannot be used and has no runtime
0 commit comments