Skip to content

Commit 9c755e6

Browse files
authored
Merge pull request #3010 from arainko/patch-1
Add `Matching function expressions`
2 parents d9d17d4 + 21cfaeb commit 9c755e6

File tree

1 file changed

+61
-1
lines changed

1 file changed

+61
-1
lines changed

Diff for: _overviews/scala3-macros/tutorial/quotes.md

+61-1
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,67 @@ case ...
245245

246246
### Matching function expressions
247247

248-
*Coming soon*
248+
Let's start with the most straightforward example, matching an identity function expression:
249+
250+
```scala
251+
def matchIdentityFunction[A: Type](func: Expr[A => A])(using Quotes): Unit =
252+
func match
253+
case '{ (arg: A) => arg } =>
254+
```
255+
The above matches function expressions that just return their arguments, like:
256+
257+
```scala
258+
(value: Int) => value
259+
```
260+
261+
We can also match more complex expressions, like method call chains:
262+
263+
```scala
264+
def matchMethodCallChain(func: Expr[String => String])(using Quotes) =
265+
func match
266+
case '{ (arg: String) => arg.toLowerCase.strip.trim } =>
267+
```
268+
269+
But what about the cases where we want more flexibility (eg. we know the subset of methods that will be called but not neccessarily their order)?
270+
271+
#### Iterative deconstruction of a function expression
272+
273+
Let's imagine we need a macro that collects names of methods used in an expression of type `FieldName => FieldName`, for a definition of `FieldName`:
274+
275+
```scala
276+
trait FieldName:
277+
def uppercase: FieldName
278+
def lowercase: FieldName
279+
```
280+
281+
The implementation itself would look like this:
282+
283+
```scala
284+
def collectUsedMethods(func: Expr[FieldName => FieldName])(using Quotes): List[String] =
285+
def recurse(current: Expr[FieldName => FieldName], acc: List[String])(using Quotes): List[String] =
286+
current match
287+
// $body is the next tree with the '.lowercase' call stripped away
288+
case '{ (arg: FieldName) => ($body(arg): FieldName).lowercase } =>
289+
recurse(body, "lowercase" :: acc) // body: Expr[FieldName => FieldName]
290+
291+
// $body is the next tree with the '.uppercase' call stripped away
292+
case '{ (arg: FieldName) => ($body(arg): FieldName).uppercase } =>
293+
recurse(body, "uppercase" :: acc) // body: Expr[FieldName => FieldName]
294+
295+
// this matches an identity function, i.e. the end of our loop
296+
case '{ (arg: FieldName) => arg } => acc
297+
end recurse
298+
299+
recurse(func, Nil)
300+
```
301+
302+
For more details on how patterns like `$body(arg)` work please refer to a docs section on [the HOAS pattern](https://dotty.epfl.ch/docs/reference/metaprogramming/macros.html#hoas-patterns-1).
303+
304+
If we were to use this on an expression like this one:
305+
```scala
306+
(name: FieldName) => name.lowercase.uppercase.lowercase
307+
```
308+
the result would evaluate to `List("lowercase", "uppercase", "lowercase")`.
249309

250310
### Matching types
251311

0 commit comments

Comments
 (0)