Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Matching function expressions #3010

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
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.
arainko authored Apr 21, 2024
commit 23e24e843a49629977c4b88f330469c1eaf118ca
62 changes: 61 additions & 1 deletion _overviews/scala3-macros/tutorial/quotes.md
Original file line number Diff line number Diff line change
@@ -245,7 +245,67 @@ case ...

### Matching function expressions

*Coming soon*
Let's start with the most straightforward example, matching an identity function expression:

```scala
def matchIdentityFunction[A: Type](func: Expr[A => A])(using Quotes): Unit =
func match
case '{ (arg: A) => arg } =>
```
The above matches function expressions that just return their arguments, like:

```scala
(value: Int) => value
```

We can also match more complex expressions, like method call chains:

```scala
def matchMethodCallChain(func: Expr[String => String])(using Quotes) =
func match
case '{ (arg: String) => arg.toLowerCase.strip.trim } =>
```

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)?

#### Iterative deconstruction of a function expression

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:

```scala
trait FieldName:
def uppercase: FieldName
def lowercase: FieldName
```

The implementation itself would look like this:

```scala
def collectUsedMethods(func: Expr[FieldName => FieldName])(using Quotes): List[String] =
def recurse(current: Expr[FieldName => FieldName], acc: List[String])(using Quotes): List[String] =
current match
// $body is the next tree with the '.lowercase' call stripped away
case '{ (arg: FieldName) => ($body(arg): FieldName).lowercase } =>
recurse(body, "lowercase" :: acc) // body: Expr[FieldName => FieldName]

// $body is the next tree with the '.uppercase' call stripped away
case '{ (arg: FieldName) => ($body(arg): FieldName).uppercase } =>
recurse(body, "uppercase" :: acc) // body: Expr[FieldName => FieldName]

// this matches an identity function, i.e. the end of our loop
case '{ (arg: FieldName) => arg } => acc
end recurse

recurse(func, Nil)
```

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).

If we were to use this on an expression like this one:
```scala
(name: FieldName) => name.lowercase.uppercase.lowercase
```
the result would evaluate to `List("lowercase", "uppercase", "lowercase")`.

### Matching types