Macro define

Source
macro_rules! define {
    (
        $(
            $(#[$meta: meta])*
            $(@[$modmeta: meta])*
            $vis: vis fn $identifier: ident 
                $(< $($typearg: ident),+ >)? 
            ::= { $($definition: tt)+ }
            $(where $($lhs: tt : $rhs: tt),+ $(,)?)?  
        ;
        )*
    ) => { ... };
    (
        @item $(#[$meta: meta])* $name: ident ; $($($typearg: ident)+)?
    ) => { ... };
    (
        @impl $ident: ident ; $($arg: ident)*; 
        { $name: ident . $name2: ident . $($definition: tt)+ } $(where $($lhs: tt:$rhs: tt),+)? 
    ) => { ... };
    (
        @impl $ident: ident ; $($args: ident)*; 
        { $name: ident . $definition: tt } $(where $($lhs: tt:$rhs: tt),+)?
    ) => { ... };
}
Expand description

Helper macro for easily defining types that implement crate::Function.

§Syntax

The body of the macro should contain an arbitrary amount of “function type” items, ending in semicolons.

These “function type” items may have an arbitrary amount of attributes.

Due to how the macro is implemented, cfg attributes must be specified with a @ instead of #, like this: @[cfg(feature = "abc")]

The body of one of these items is as follows:

pub fn Name<A, B> ::= { C. D. { A, B {C, D}}}
    where A: B, {A, B}: {C, D}, /* ... */;

It’s quite a bit to take in, but it’s actually quite simple! Let’s break it down.

  • The visibility specifier pub is optional, defaulting to private, as expected of any other item.

  • The Name is the name of the type that’s publicly exported by the macro. As you can see, type parameters are also supported, but const generics are unfortunately not.

  • Following the ::= is the actual definition enclosed in {} braces. Everything within the brackets is implicitly passed to crate::call.

  • The where clauses are an unfortunate consequence of using a declarative macro instead of a procedural one, as the macro can’t generate one for us due to the inability to use macro calls as parameter guards. Both sides of each clause are implicitly passed to crate::call.

    In order for the compiler to accept your function, you must tell the compiler in these clauses that all arguments used in the definition are actually able to be used in the way they’re used.

    A good strategy is to write each call out in the where clause while you’re writing, like this:

    fn F ::= { A. B. C. { C, {B, A}, C }} where
         B: A, // B calls A
         C: {B, A}, // C calls {B, A}
         {C, {B, A}}: C; // {C, {B, A}} calls C

Unfortunately, due to implementation complexity, anonymous functions like this:

// λn.λf.λx. n (λg.λh. h (g f)) (λu.x) (λu.u)
pub fn Predecessor ::= { N. F. X. { N {G. H. H { G, F }}, Constant<X>, Identity } };

are unsupported. Instead, you can break it up into smaller definitions, like this:

pub fn Predecessor ::= { N. F. X. { N, Pred_1<F>, Constant<X>, Identity } };
fn Pred_1<F> ::= { G. H. { H { G, F } } };

which is actually the definition used in crate::math::Predecessor, sans the where clauses.

Reading through how the crate defines function types is highly recommended to get a grasp on the syntax!

This macro is hygienic.