How to make this macro working? :)

#[macro_export]
macro_rules! fn_range {
    ($v: ident, $f: expr) => {
        match $v {
            Value::Range(r) => {
                Value::from_f64($f(r))
            }
            Value::String(s) => match Decimal::from_str(&s) {
                Ok(d) => Value::Decimal(d),
                Err(_) => nan(),
            },
            _ => $v.clone(),
        }
    }
}

#[inline]
pub fn sum(v: Value) -> Value {
    fn_range!(v, r.iter_f64().sum()) // not found in this scope
}

that syntax expects $f to be a callable taking a parameter, but in your case $f is r.iter_f64().sum() so this expands to (r.iter_f64().sum())(r)

  • with the added complexity that the $f-expanded r does not know about the macro-hidden r variable (this is called macro hygiene).

The solution to both issues is simple: use a closure.

-    fn_range!(v, r.iter_f64().sum())
+    fn_range!(v, |r| r.iter_f64().sum())

(and to be clear, the closure could have been using a name other than r, such as:
|it| it.iter_f64().sum())

1 Like

@Yandros thanks for reply. I was thinking to use closure :).

1 Like

@Yandros tried to use closure, but got consider giving this closure parameter a type. Can I avoid specifying type?

Oh, yes, closures can be annoying because of that.

Generally, there may be times where you need a type annotation and there isn't much you can do about it. Other times, you may work around that limitation with type annotations elsewhere.

  • For instance, if your closure captures no environment, you could have the macro expand to:
    => {
        let f: fn(<type annotation here>) -> _ = $f;
        Value::from_f64(f(r))
    }
    

But luckily, in this case, we can go and transform a bit the macro to work like I think you originally intended:

#[macro_export]
macro_rules! fn_range {(
    $v:expr,
    |$r:ident| $fr:expr $(,)?
) => (
    match $v {
        | Value::Range($r) => {
            Value::from_f64($fr)
        },
        | Value::String(s) => match Decimal::from_str(&s) {
            | Ok(d) => Value::Decimal(d),
            | Err(_) => nan(),
        },
        | ref v => v.clone(),
    }
)}

#[inline]
pub fn sum(v: Value) -> Value {
    fn_range!(v, |r| r.iter_f64().sum()) // not found in this scope
}

Basically the trick is to take your initial syntax $v, $fr, with an expansion to $fr directly instead of $f(r), but that would struggle because of the aforementioned hygiene: the macro binds the contents of Value::Range to a variable it names r, and such name is not visible nor usable by the caller of the macro (this helps a lot of "stupid bugs" that we can have, for instance, with C ands its "dumb" preprocessor).
The solution, in that case, is for the caller of the macro to provide the variable name that the macro will use internally:

fn_range!(v, r, r.iter_f64().sum())
             ^  ^^^^^^^^^^^^^^^^^^
            var     expansion
  • This expansion has now no ambiguity w.r.t, the type of r.

And at that point, since a macro chooses the syntax with which to parse its input, I have changed that to use a closure-looking syntax;

fn_range!(v, |r| r.iter_f64().sum())
              ^  ^^^^^^^^^^^^^^^^^
             var      expansion
3 Likes

@Yandros wonderful, this works. thanks for detailed reply

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.