Skip to content

Commit d0bf3a0

Browse files
author
Dean Wampler
committed
Expanded and improved metaprogramming examples
1 parent fe6979c commit d0bf3a0

File tree

9 files changed

+137
-37
lines changed

9 files changed

+137
-37
lines changed

src/main/scala/progscala3/meta/Invariant.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ object invariant:
88
inline def apply[T](
99
inline predicate: => Boolean, message: => String = "")( // <2>
1010
inline block: => T): T =
11-
if !ignore then
11+
inline if !ignore then
1212
if !predicate then fail(predicate, message, block, "before") // <3>
1313
val result = block
1414
if !predicate then fail(predicate, message, block, "after")
@@ -28,8 +28,8 @@ object invariant:
2828
private def failImpl[T](
2929
predicate: Expr[Boolean], message: Expr[String],
3030
block: Expr[T], beforeAfter: Expr[String])(
31-
using Quotes): Expr[String] = // <5>
32-
'{ throw InvariantFailure(
31+
using Quotes): Expr[String] =
32+
'{ throw InvariantFailure( // <5>
3333
s"""FAILURE! predicate "${${showExpr(predicate)}}" """
3434
+ s"""failed ${$beforeAfter} evaluation of block:"""
3535
+ s""" "${${showExpr(block)}}". Message = "${$message}". """)

src/main/scala/progscala3/meta/Invariant1.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ object invariant1:
1212
inline def apply[T]( // <2>
1313
inline predicate: => Boolean)(
1414
inline block: => T): T =
15-
if !ignore then // <3>
15+
inline if !ignore then // <3>
1616
if !predicate then throw InvariantFailure("before")
1717
val result = block
1818
if !predicate then throw InvariantFailure("after")
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// src/main/scala/progscala3/meta/Tracer.scala
2+
package progscala3.meta
3+
import scala.quoted.*
4+
5+
/**
6+
* A simple Logger abstraction used by tracer.
7+
*/
8+
trait Logger:
9+
import java.time.Instant.now
10+
def trace(message: => Any): Unit = log(s"TRACE ($now): $message")
11+
// Other logging methods
12+
protected def log(s: String): Unit
13+
14+
object ConsoleLogger extends Logger:
15+
protected def log(s: String): Unit = println(s)
16+
17+
/**
18+
* Implement a "tracer" that logs entry and exit points for an expression.
19+
* Compare with our attempts to do this with just the codeOf method here:
20+
* src/script/scala/progscala3/meta/compiletime/CodeOf.scala
21+
* In fact, this implementation doesn't fix the issue with "1 + 1 == 2" being
22+
* printed as "true". Therefore, using CodeOf is simpler, although it's possible
23+
* this implementation will have less byte code and runtime overhead.
24+
* A real implementation would make the Logger configurable.
25+
* See TryTracer.scala for an example program that uses tracer.
26+
*/
27+
object tracer:
28+
inline def apply[T](inline expr: => T): T =
29+
val s = traceStr(expr)
30+
ConsoleLogger.trace(s"->: $s")
31+
val t = expr
32+
ConsoleLogger.trace(s"<-: $s")
33+
t
34+
35+
inline def traceStr[T](inline expr: => T): String = ${ traceStrImpl('expr) }
36+
37+
def traceStrImpl[T](expr: Expr[T])(using Quotes): Expr[String] =
38+
val code: String = expr.show
39+
Expr(code)
40+
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// src/main/scala/progscala3/meta/TryTracer.scala
2+
package progscala3.meta
3+
4+
@main def TryTracer =
5+
tracer(1 + 1 == 2) // This still just prints as "true"
6+
tracer("A string")
7+
tracer {
8+
val up = "Hello World!".toUpperCase
9+
println(up)
10+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// src/script/scala/progscala3/meta/compiletime/CodeOf.scala
2+
// Example mentioned in the book, but not discussed there.
3+
import scala.compiletime.codeOf
4+
import java.time.Instant.now
5+
6+
// If you copy and paste these lines into the REPL, you'll see the
7+
// generated strings contain terminal color-coding!
8+
// Instead, use "scala .../CodeOf.scala", which will run the following
9+
// main:
10+
@main def TryCodeOf =
11+
println("The following lines print the expression strings we want:")
12+
println(codeOf(1 + 1 == 2))
13+
println(codeOf("A string"))
14+
println(codeOf {
15+
val up = "Hello World!".toUpperCase
16+
println(up)
17+
})
18+
19+
// It would be great if the following trace method worked like the previous
20+
// println statements. It almost works but unfortunately, despite using a
21+
// by-name parameter and three inlines, the output "expr" string is computed
22+
// after evaluating the expression, at least for the simpler `1 + 1 == 2` case.
23+
// We have to use a macro for this to work. See the implementation of tracer
24+
// (Tracer.scala) and invariant (Invariant.scala) later in the Metaprogramming
25+
// chapter.
26+
inline def trace[T](inline expr: => T): T =
27+
inline val ce = codeOf(expr)
28+
printf(s"Trace -> ($now): $ce\n")
29+
val t = expr
30+
printf(s"Trace <- ($now): $ce\n")
31+
t
32+
33+
println("\nCalls to 'trace', which don't work the way we want:")
34+
trace(1 + 1 == 2)
35+
trace("A string")
36+
trace {
37+
val up = "Hello World!".toUpperCase
38+
println(up)
39+
}

src/script/scala/progscala3/meta/compiletime/ConstValue.scala

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// src/script/scala/progscala3/meta/compiletime/ConstValue.scala
22
// Example mentioned in the book, but not discussed there.
3-
import scala.compiletime.{constValue, constValueOpt}
3+
import scala.compiletime.{constValue, constValueOpt, constValueTuple}
44

55
// Verify that the types in [...] are constants. The *Opt variant won't throw
66
// an exception if the expression is not a constant, but return None.
@@ -9,9 +9,9 @@ def tryInt =
99
import compiletime.ops.int.* // Scope this. import to this method
1010
assert(constValue[1] == 1)
1111
assert(constValueOpt[1] == Some(1))
12-
13-
assert(constValueOpt[1+2] == Some(3))
1412
assert(constValue[1+2] == 3)
13+
assert(constValueOpt[1+2] == Some(3))
14+
assert(constValueTuple[(1+2,3*4)] == (3,12))
1515

1616
def tryBoolean =
1717
import compiletime.ops.boolean.*
@@ -30,6 +30,7 @@ def tryBoolean =
3030
assert(constValue[true ^ false] == true)
3131
assert(constValue[false ^ true] == true)
3232
assert(constValue[false ^ false] == false)
33+
assert(constValueTuple[(true || false, true && false)] == (true, false))
3334

3435
def tryString =
3536
import compiletime.ops.string.*
@@ -38,3 +39,5 @@ def tryString =
3839

3940
assert(constValue["foo"+"bar"] == "foobar")
4041
assert(constValueOpt["foo"+"bar"] == Some("foobar"))
42+
43+
assert(constValueTuple[("foo","bar")] == ("foo", "bar"))
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// src/script/scala/progscala3/meta/compiletime/SummonAll.scala
2+
import scala.compiletime.summonAll
3+
4+
trait C; trait D; trait E
5+
given a: C with {}
6+
given b: D with {}
7+
8+
summonAll[C *: D *: EmptyTuple]
9+
summonAll[C *: D *: E *: EmptyTuple] // <1>
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// src/script/scala/progscala3/meta/compiletime/SummonFrom.scala
2+
import scala.compiletime.summonFrom
3+
4+
trait A; trait B
5+
6+
inline def trySummonFrom(label: String, expected: Int): Unit = // <1>
7+
val actual = summonFrom {
8+
case given A => 1
9+
case given B => 2
10+
case _ => 0
11+
}
12+
printf("%-9s trySummonFrom(): %d =?= %d\n", label, expected, actual)
13+
14+
def tryNone = trySummonFrom("tryNone:", 0) // <2>
15+
16+
def tryA = // <3>
17+
given A with {}
18+
trySummonFrom("tryA:", 1)
19+
20+
def tryB =
21+
given B with {}
22+
trySummonFrom("tryB:", 2)
23+
24+
def tryAB =
25+
given A with {}
26+
given B with {}
27+
trySummonFrom("tryAB:", 1)
28+
29+
tryNone; tryA; tryB; tryAB

src/script/scala/progscala3/meta/compiletime/SummonFromAll.scala

Lines changed: 0 additions & 30 deletions
This file was deleted.

0 commit comments

Comments
 (0)