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

custom implementations #14

Open
colin-kiegel opened this issue Dec 27, 2015 · 5 comments
Open

custom implementations #14

colin-kiegel opened this issue Dec 27, 2015 · 5 comments

Comments

@colin-kiegel
Copy link
Contributor

Observation: The main pain point of enums is the repetitiveness of match-statements. Which are scattered around at different locations. This is good for adding and removing a new trait implementation (which is a local change of code). But it is not good for adding and removing enum-variants. Quick-error does code transformations to invert this for a specific set of traits.

Here is the (possibly crazy) idea: What about a code transformation that is generic in terms of the traits, too - like this example

IN

enum_transform!(
  pub enum Foo {
    X => {
      #[define] Bar::baz(/* optional arguments*/) -> { Ok(()) }
    },
    Y => {
      #[define] Bar::baz(/* optional arguments*/) -> { Err(()) }
    }
  }

  impl Bar for Foo {
       fn baz(&self, /* .. */) -> Result<(), ()> {
           // ...
           match *self
           #[insert] { Bar::baz(/* optional arguments*/) }
           // ...
       }
  }
)

OUT

pub enum Foo {
  X,
  Y
}

impl Bar for Foo {
  fn baz(&self, /* .. */) -> Result<(), ()>
    // ...
    match *self {
      X => { Ok(()) };
      Y => { Err(()) };
    }
    // ...
  }
}

If we had such a generic enum_transform macro, then
(a) quick_error could be rewritten in terms of enum_transform AND
(b) quick_error could itself could allow generic substitutions like this

EDIT:

  • Minor corrections
@colin-kiegel
Copy link
Contributor Author

I think I will start this as a completely separate library - if it works out, we can see if it makes sense.

@tailhook
Copy link
Owner

Yes. That's my observation too. But I don't have a time to do it myself. So yes, please start it as a separate library. It's good idea besides the error handling.

@colin-kiegel
Copy link
Contributor Author

Ok, I already started this. Now I am stuck at the generalized problem of FIND_IMPL (instead of FIND_DESCRIPTION_IMPL, etc.). So in a simplified version, I want to do a search via macros

macro_rules! enum_transform{
  (FIND $needle:ident
    {$pop:ident $( $hay:ident )*}
  ) => {
    // PSEUDO-CODE >>
    if($pop == $needle) { 
      true
    } else {
      enum_transform!{FIND $needle {$( $hay )*}
    }
    // << PSEUDO-CODE
  }
  (FIND $needle:ident {}
  ) => {
    false
  }
}

assert_eq!(enum_transform!(FIND bar {foo bar baz}), true);

This can partly be reduced to this problem:

assert!(equal!(foo foo));
assert!(!equal!(foo bar));

Which rust macro is capable to do this check on the AST-level?

Of course I can do something like

macro_rules! equal{
  ($a:ident $b:ident)
  => { stringify!($a) == stringify!($b) }
}

Rust should be able to reduce this to true booleans at compile time - but strictly after the macros have completed. I believe this would not solve all problems, because the compiler would probably complain about invalid expressions, even if they are unreachable like this

if equal!(foo bar) {
  // .. invalid expressions - rust would still complain, although they are unreachable
}

Even if it worked, I would not like this workaround very much.

Hm. This could be a show stopper. What do you think?
I was more or less confident until I realised this problem.

@colin-kiegel
Copy link
Contributor Author

Hm. I should try if macros can dynamically define new local macros and call them. I dont know if Rust allows that, if so, it would be very powerful.

@colin-kiegel
Copy link
Contributor Author

Ok, after playing around with this:

This works in general - which is astonishing, given the limitations of macro_rules. But there are two very big drawbacks doing this whole thing with macro_rules!

  • The recursion depth of my prototype scales linear with the number of tokens O(enum) + O(impl). Due to some macro limitations, I would even need to make two passes over the implementations (first to replace the match-statements with higher-order-macros, this must be done in a macro-buffer before the implementations are written out, then another pass to replace self with self due to macro hygiene, this must be done after the impl-definition has started and the new self is in scope). Currently I try it in one pass, where only one of these succeeds and the other one fails (which one depends on whether I do this pass 'early' or 'late'). I am confident, that both work, if I split it into two passes. But two passes means at least 2 * len(impl) recursions. Even the most trivial cases would then be around the default recursion limit of 64. It is possible and very easy to increase this limit, but it does not seem like a very good idea. Even with one pass it would be problematic.
  • Last but not least, it is not possible to use macros inside macro-invocations (at least not with some additional trickery).

Takeaway: I think this should better be done with a procedural macro - but there is currently no way to write procedural rust macros for the stable toolchain. So I think this must wait until the macro reform.

Btw - This is how far I got: https://github.com/colin-kiegel/enum-transform/blob/master/src/macros.rs. I think this is a dead end for above reasons - but at least I learned a lot about rust macro_rules!. :-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants