@@ -17,12 +17,12 @@ object Logger {
1717
1818 private var indent = 0
1919
20- inline def log [T ](msg : => String )(op : => T ): T =
20+ inline def log [T ](msg : String , indentMargin : => Int )(op : => T ): T =
2121 if (Config .logging) {
2222 println(s " ${" " * indent}start $msg" )
23- indent += 1
23+ indent += indentMargin
2424 val result = op
25- indent -= 1
25+ indent -= indentMargin
2626 println(s " ${" " * indent}$msg = $result" )
2727 result
2828 }
@@ -34,24 +34,28 @@ The `Config` object contains a definition of the **inline value** `logging`.
3434This means that ` logging ` is treated as a _ constant value_ , equivalent to its
3535right-hand side ` false ` . The right-hand side of such an ` inline val ` must itself
3636be 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.
3939
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.
4242
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.
4647
4748Here's an example:
4849
4950``` 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) {
5255 if (n == 0 ) 1
5356 else n * factorial(n - 1 )
5457 }
58+ }
5559```
5660
5761If ` Config.logging == false ` , this will be rewritten (simplified) to
@@ -63,61 +67,33 @@ def factorial(n: BigInt): BigInt = {
6367}
6468```
6569
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:
6777
6878``` scala
6979def 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" )
7688 result
7789}
7890```
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
9191
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 ` .
11895
119- By contrast, if ` op ` is a call-by-value parameter, it would be evaluated
120- separately as a closure.
96+ ### Recursive Inline Methods
12197
12298Inline methods can be recursive. For instance, when called with a constant
12399exponent ` n ` , the following method for ` power ` will be implemented by
@@ -333,30 +309,30 @@ inline def defaultValue[T] = inline erasedValue[T] match {
333309 case _ : Double => Some (0.0d )
334310 case _ : Boolean => Some (false )
335311 case _ : Unit => Some (())
336- case _ : t >: Null => Some (null )
337312 case _ => None
338313}
339314```
340315
341316Then:
342317``` 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 ]
347322```
348323
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:
352328
353329``` 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
356332 case _ : Succ [n] => toIntT[n] + 1
357333}
358334
359- final val two = toInt [Succ [Succ [Zero ]]]
335+ final val two = toIntT [Succ [Succ [Zero . type ]]]
360336```
361337
362338` erasedValue ` is an ` erased ` method so it cannot be used and has no runtime
0 commit comments