Skip to content

Commit 23e24e8

Browse files
authored
Add Matching function expressions
It seems like no one on the internet knows how to do this properly, so after drunkenly stumbling upon the correct syntax I decided to throw this small section together.
1 parent 609e473 commit 23e24e8

File tree

1 file changed

+61
-1
lines changed

1 file changed

+61
-1
lines changed

_overviews/scala3-macros/tutorial/quotes.md

Lines changed: 61 additions & 1 deletion
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` that looks like this:
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 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)