I'm attempting to condense some repetitive code that has structure similar to:
match self.foo() {
None => self.bar(),
Some(MyStruct { foo: x, .. }) => match x {
Pattern1 => result,
Pattern2 => {
block_result
}
}
}
which I would like to write as something like:
my_macro!(
Pattern1 => result,
Pattern2 => {
block_result
}
)
avoiding the repetitive None handling and MyStruct destructuring.
This seems like it should be pretty simple, as it's essentially just substituting the macro body into a match expression, but I can't actually see any way to do this.
I attempted to write the macro as follows:
macro_rules! my_macro (
($($pat:pat => $result:expr,)*) => (
match self.foo() {
None => self.bar(),
Some(MyStruct { foo: x, .. }) => match x {
$(
$pat => $result,
)*
},
}
);
)
but this fails, as the RHS of a match arm can be an expression or a block (and it also doesn't deal with optionally omitting the comma for the last arm). As far as I'm aware there's no way to specify that part of a macro pattern can be a block or an expression, so I can't think of a way to resolve this.
Ideally I'd like to do this without having to write complicated patterns that destructure match bodies only to stick them back together again, but I don't think there's any designator that will accept the body of a match expression.
How would you go about writing this macro? Is it even possible without writing a compiler plugin?
I'm not sure why you decided that
this fails, as the RHS of a match arm can be an expression or a block
In Rust match arms are always expressions, and it just happened that blocks are also expressions.
There are two problems in your macro. First, as you noticed, your macro does not handle omitting last comma. This can be remedied very easily: you just change this pattern:
$($pat:pat => $result:expr,)*
into this one:
$($pat:pat => $result:expr),*
and its usage should be changed as well:
$(
$pat => $result,
)*
to
$(
$pat => $result
),*
The second problem is that unless you define this macro in the scope which contains self identifier (i.e. inside the method), it won't work as you expect because of hygiene - the self identifier you use in self.foo() and self.bar() invocations in the macro body will be different from the one at the macro expansion site. As a general rule, you always need to pass identifiers you want to work with at the macro expansion site as arguments to the macro, unless this macro is defined in the scope where these identifiers are already present.
So, the final variant of the macro is this:
macro_rules! my_macro (
($_self:expr, $($pat:pat => $result:expr),*) => (
match $_self.foo() {
None => $_self.bar(),
Some(MyStruct { foo: x, .. }) => match x {
$(
$pat => $result
),*
},
}
);
)
and it does work exactly as you want.
You can find more information on macros and how to write them in the official guide.
Related
I'm creating a macro that matches two expressions and an identifier. I would like to be able to ignore the identifier if it's not needed, but the compiler seems to complain if I use _ there.
My macro:
macro_rules! if_some {
($x:expr, $id:ident, $expr:expr) => {
match $x {
None => None,
Some($id) => Some($expr),
}
};
}
What I'd like to do:
if_some!(obtain_an_option(), x, do_something_with(x))
and
if_some!(obtain_an_option(), _, do_something())
The second call fails.
I worked around it by defining a second macro if_some_! that doesn't receive an identifier (I could not use a second pattern either). I'm sure there's a way to say "here accept an identifier or just _.
Maybe there's already a macro/function for this (like Option::map now I think about it)... nevertheless it'd be nice to now.
The simplest way is to add a second arm that matches an underscore:
macro_rules! if_some {
($x:expr, _, $expr:expr) => {
match $x {
None => None,
Some(_) => Some($expr),
}
};
($x:expr, $id:ident, $expr:expr) => {
match $x {
None => None,
Some($id) => Some($expr),
}
};
}
And, yes, this sounds like you just want Option::map.
Option::map seems to be the best solution for this particular problem, but when you really need a macro which expect both idents and _ as a pattern, you can also use the $p:pat fragment. The fragment of course accepts a broader range of patterns like (ref x, y), but typically this will be acceptable.
macro_rules! if_some {
($x:expr, $p:pat, $expr:expr) => {
match $x {
None => None,
Some($p) => Some($expr),
}
};
}
fn main() {
println!("{:?}", if_some!(Some(12), x, x + 1)); // Some(13)
println!("{:?}", if_some!(Some(12), _, 1)); // Some(1)
}
macro_rules! mtc {
( $ident:ident ) => ("ident");
( $string:expr ) => ("string");
}
fn main() {
println!("{}", mtc!("hi"));
println!("{}", mtc!(a));
}
This doesn't work. It fails with:
<anon>:7:25: 7:29 error: expected ident, found "hi"
<anon>:7 println!("{}", mtc!("hi"));
The problem is that the macro_rules! machinery cannot back out of a capture. Once it starts trying to match a capture, it either succeeds or the entire macro invocation fails.
In order to do this, you have to provide some kind of literal match prior to the capture that macro_rules! can use to distinguish between the rules. For example:
macro_rules! mtc {
( ident $ident:ident ) => ("ident");
( expr $string:expr ) => ("string");
}
Also, to answer the implicit question: no, there is no way to specifically match a string literal, or indeed any other kind of literal.
like similar question: Convert match statement to partial function when foreach is used. Now similarly, IntelliJ asks me to improve my code. The difference is, that I use values for the matching:
val matchMe = "Foo"
keys.foreach(key =>
key match {
case `matchMe` => somethingSpecial()
case _ => somethingNormal(key, calcWith(key))
})
Refactoring this to a anonymous pattern-matching function would look something like:
keys.foreach {
case `matchMe` => somethingSpecial(_)
case _ => somethingNormal(_, calcWith(_)) //this doesn't work
}
Note that in the second case, I cannot use _ since I need it twice. Is there some way to use an anonymous pattern-matching function here?
You can't use the wildcard _ here, its purpose is to indicate you don't care about the value you're matching against.
You can use a named parameter :
keys.foreach {
case `matchMe` => somethingSpecial(matchMe)
case nonSpecialKey => somethingNormal(nonSpecialKey, calcWith(nonSpecialKey))
}
Without any restrictions placed on it, it will match any value. Do note that the order of cases is important, as case x => ... match anything and will essentially shortcut other case statements.
As an aside, I don't think your somethingSpecial(_) does what you want/expect it to. It's only a short version of x => somethingSpecial(x), not somethingSpecial(matchMe).
This code is from Querying a Dataset with Scala's Pattern Matching:
object & { def unapply[A](a: A) = Some((a, a)) }
"Julie" match {
case Brothers(_) & Sisters(_) => "Julie has both brother(s) and sister(s)"
case Siblings(_) => "Julie's siblings are all the same sex"
case _ => "Julie has no siblings"
}
// => "Julie has both brother(s) and sister(s)"
How does & actually work? I don't see a Boolean test anywhere for the conjunction. How does this Scala magic work?
Here's how unapply works in general:
When you do
obj match {case Pattern(foo, bar) => ... }
Pattern.unapply(obj) is called. This can either return None in which case the pattern match is a failure, or Some(x,y) in which case foo and bar are bound to x and y.
If instead of Pattern(foo, bar) you did Pattern(OtherPattern, YetAnotherPatter) then x would be matched against the pattern OtherPattern and y would be matched against YetAnotherPattern. If all of those pattern matches are successful, the body of the match executes, otherwise the next pattern is tried.
when the name of a pattern is not alphanumeric, but a symbol (like &), it is used infix, i.e. you write foo & bar instead of &(foo, bar).
So here & is a pattern that always returns Some(a,a) no matter what a is. So & always matches and binds the matched object to its two operands. In code that means that
obj match {case x & y => ...}
will always match and both x and y will have the same value as obj.
In the example above this is used to apply two different patterns to the same object.
I.e. when you do
obj match { case SomePattern & SomeOtherPattern => ...}`
first the pattern & is applied. As I said, it always matches and binds obj to its LHS and its RHS. So then SomePattern is applied to &'s LHS (which is the same as obj) and SomeOtherPattern is applied to &'s RHS (which is also the same as obj).
So in effect, you just applied two patterns to the same object.
Let's do this from the code. First, a small rewrite:
object & { def unapply[A](a: A) = Some(a, a) }
"Julie" match {
// case Brothers(_) & Sisters(_) => "Julie has both brother(s) and sister(s)"
case &(Brothers(_), Sisters(_)) => "Julie has both brother(s) and sister(s)"
case Siblings(_) => "Julie's siblings are all the same sex"
case _ => "Julie has no siblings"
}
The new rewrite means exactly the same thing. The comment line is using infix notation for extractors, and the second is using normal notation. They both translate to the same thing.
So, Scala will feed "Julie" to the extractor, repeatedly, until all unbound variables got assigned to Some thing. The first extractor is &, so we get this:
&.unapply("Julie") == Some(("Julie", "Julie"))
We got Some back, so we can proceed with the match. Now we have a tuple of two elements, and we have two extractors inside & as well, so we feed each element of the tuple to each extractor:
Brothers.unapply("Julie") == ?
Sisters.unapply("Julie") == ?
If both of these return Some thing, then the match is succesful. Just for fun, let's rewrite this code without pattern matching:
val pattern = "Julie"
val extractor1 = &.unapply(pattern)
if (extractor1.nonEmpty && extractor1.get.isInstanceOf[Tuple2]) {
val extractor11 = Brothers.unapply(extractor1.get._1)
val extractor12 = Sisters.unapply(extractor1.get._2)
if (extractor11.nonEmpty && extractor12.nonEmpty) {
"Julie has both brother(s) and sister(s)"
} else {
"Test Siblings and default case, but I'll skip it here to avoid repetition"
}
} else {
val extractor2 = Siblings.unapply(pattern)
if (extractor2.nonEmpty) {
"Julie's siblings are all the same sex"
} else {
"Julie has no siblings"
}
Ugly looking code, even without optimizing to only get extractor12 if extractor11 isn't empty, and without the code repetition that should have gone where there's a comment. So I'll write it in yet another style:
val pattern = "Julie"
& unapply pattern filter (_.isInstanceOf[Tuple2]) flatMap { pattern1 =>
Brothers unapply pattern1._1 flatMap { _ =>
Sisters unapply pattern1._2 flatMap { _ =>
"Julie has both brother(s) and sister(s)"
}
}
} getOrElse {
Siblings unapply pattern map { _ =>
"Julie's siblings are all the same sex"
} getOrElse {
"Julie has no siblings"
}
}
The pattern of flatMap/map at the beginning suggests yet another way of writing this:
val pattern = "Julie"
(
for {
pattern1 <- & unapply pattern
if pattern1.isInstanceOf[Tuple2]
_ <- Brothers unapply pattern1._1
_ <- Sisters unapply pattern1._2
} yield "Julie has both brother(s) and sister(s)
) getOrElse (
for {
_ <- Siblings unapply pattern
} yield "Julie's siblings are all the same sex"
) getOrElse (
"julie has no siblings"
)
You should be able to run all this code and see the results for yourself.
For additional info, I recommend reading the Infix Operation Patterns section (8.1.10) of the Scala Language Specification.
An infix operation pattern p op q is a
shorthand for the constructor or
extractor pattern op(p,q). The
precedence and associativity of
operators in patterns is the same as
in expressions.
Which is pretty much all there is to it, but then you can read about constructor and extractor patterns and patterns in general. It helps separate the syntactic sugar aspect (the "magic" part of it) from the fairly simple idea of pattern matching:
A pattern is built from constants,
constructors, variables and type
tests. Pattern matching tests whether
a given value (or sequence of values)
has the shape defined by a pattern,
and, if it does, binds the variables
in the pattern to the corresponding
components of the value (or sequence
of values).
regarding the following scala code, functions m2a and m2b apparently differ only by the case of the parameter, ie abc vs Abc. This seems to make some difference in the result as per example below. When running it with a recent 2.8 compiler, it results in the following (I would have expected all true). Any insights would be appreciated.
m1=true
m2a=true
m2b=false
m3=true
code
package sample
import scala.xml._
object ParamTest extends Application {
def m1(n:Node, abc:String):Boolean = {
n == <id>{Text(abc)}</id>
}
def m2a(n:Node, Abc:String):Boolean = n match {
case <id>{Text(Abc)}</id> => true
case _ => false;
}
// why does this one not work?
def m2b(n:Node, abc:String):Boolean = n match {
case <id>{Text(abc)}</id> => true
case _ => false;
}
def m3(n:Node, abc:String):Boolean = n match {
case Elem(_,"id",_,_, c #_ *) => {
c contains Text(abc)
}
}
def runner(n:Node, f:(Node, String)=>Boolean):Boolean = {
f(n, "x") && !f(n, "y") && !f(n, "");
}
val x = <id>x</id>
println("m1="+runner(x, m1));
println("m2a="+runner(x, m2a));
println("m2b="+runner(x, m2b));
println("m3="+runner(x, m3));
}
The trick here is in how Scala handles variables in case expressions. Lowercase variables in case expressions are taken by the compiler to introduce new variables, which are then pattern matched against. Thus in method m2b, the method parameter "abc" is actually unused. The case expression variable "abc" will match any string, since it is not otherwise constrained. Thus "y" is successfully matched in the first case of m2b. Uppercase variables in case expressions do not introduce new variables, so in m2a the match behaves as you expected.
The easiest way to match against the value of a lowercase variable is to wrap it in backquotes. Thus
def m2b(n:Node, abc:String):Boolean = n match {
case <id>{Text(`abc`)}</id> => true
case _ => false;
}
will give you the results you expected.
In pattern matching, identifiers in the pattern that start with a lower-case letter are taken to be free pattern variables that can be bound to values in the target of the match. Those that start with upper-case letters are so-called stable identifiers and must already be bound in the match expression's context and the value of that binding must equal the subexpression of the match target at the point in that value corresponding to that stable identifier's placement within the pattern expression.
Additionally, and relevant in this particular example, pattern variables (the lower-case names) will shadow any existing binding of the same name that is in effect in the context of the match expression (including the expression supplying the match target value).