WaDelma / poisson

n-dimensional poisson-disk distribution generation for rust.
MIT License
10 stars 8 forks source link

Builder Pattern applied to the Builder struct, errors instead of panics #8

Open ConnyOnny opened 7 years ago

ConnyOnny commented 7 years ago

implements #7

ConnyOnny commented 7 years ago

Thanks for the reply.

The MultiplyDefined errors are a matter of taste. I can remove them if you want it that way.

The Unspecified errors would be spotted early in development of a simple application, so using a panic instead seems not too bad at first. But if the setup code for the Builder becomes more complex - say the radius is set in branches of a match statement and the erroneous case (where the radius is not set) is not well-tested - this could crash the whole application, which could be something large - like a game server or web server - with expensive outages. I try to do everything I can in order to avoid crashes. In my world, a Rust program is supposed to never crash, even if some parts are not properly tested. Okay, if the user just calls unwrap on the Result, there's nothing we can do, but IMHO we should do everything we can to not panic. Also the Error enum is in place anyway, so doing this doesn't really hurt.

I like session types. I don't like that implementing them involves so much manual labor. Tracking the Builder API changes all over the code base already was quite a pain. However if we do session types, then defining the same option multiple times should really not be permitted by the type system, in my opinion.

About the naming of the configuration struct you are absolutely right! I'll change it, probably tomorrow.

ConnyOnny commented 7 years ago

What is your opinion on the with_poisson_type function: Should it be required or should the Builder just use Type::Normal if it is not given?

WaDelma commented 7 years ago

I get what you mean by not wanting to crash ever, but if your code comes up into case where no radius is specified what can you do to recover from it? I do agree now that panicing was bad choice anyways: It doesn't show up in the type signature.

I should really play around with the session type idea as that would fix the problem. Maybe macros could be used to reduce the boiler plate involved? Would have to use generics for the builder because macros cannot create indents.

Maybe this error type solution should merged meanwhile until viability of session types is confirmed...

I think that using Type::Normal would be fine as default. If it's used default for builder Default trait should propably be implemented for it.

WaDelma commented 7 years ago

Here is the start of implementing the session type based builder pattern with macro. Setters aren't implemented yet as they need more complex token tree munching. Also to implement error checking there needs to be a way to provide custom build method.

WaDelma commented 7 years ago

and here is version that has working setters.

It doesn't yet work with generic or optional variables and doesn't include custom build method.

ConnyOnny commented 7 years ago

Wow, that's some impressive macro magic!

but if your code comes up into case where no radius is specified what can you do to recover from it?

For example there could be a popup "error generating tree positions" and the game could run without trees.

WaDelma commented 7 years ago

and now it has optional variables.

WaDelma commented 7 years ago

Thanks! macro_rules is powerful, but pain to work with. Good thing that there is resources like this to help with them. (I mostly learned how to use it by coding this monster of a macro)

Ah.. Interesting. I didn't think anything like that. That is basically same idea that led to how html/php/javascript or any web technology handles errors. Which is one reason I don't really like them and are attracted by static typing (Keeping broken program running is asking for trouble imho). But I do understand that you don't really want to crash your server. But in other hand you propably are catching panics coming from job threads in them anyways.

I can now see where you are coming from and having those errors would be good. Or just using session type builder :P

WaDelma commented 7 years ago

I think I have sunken into rabbit hole... Now it has support for single constraints on generic values. Note: Playpen times out sometimes when trying to build that. Not sure why as it works perfectly fast locally.

ConnyOnny commented 7 years ago

That is basically same idea that led to how html/php/javascript or any web technology handles errors.

It's a huge difference whether the language tries hard not to crash or you try not to crash. If at some point an array is filled with NaNs instead of trees, because the language thought it was smart to fail as late as possible, you're f...rankly not in a very nice situation. I do not defend the concept fail-late or its employment by any language such as JavaScript, PHP, Ruby, Python etc. But if you know that an error occured and know what the error is, the situation is very different, because you might be able to do something reasonable (which a programming language cannot). To stay in the tree generation example, it could also say "map loading failed" and the user could try to play on another map, where the problem maybe does not occur.

I think I have sunken into rabbit hole

Nice. See what you can find down there and maybe put it in a crate and bring it back up, so everyone can profit from your journey. I just recently thought about the "session types" concept: "Why is there no crate for this?"

WaDelma commented 7 years ago

Now it has full generic support. I had to restructure it slightly and so it now generates this kind of API:

pub mod Builder {
    use super::*;
    pub struct O;
    pub struct I;
    pub struct Builder<T1, T2, ..., Tm, C1, C2, ..., Cn>
       where //bound that affects Cs
    {...}
    pub fn new<T1, T2, ..., Tm, C1, C2, ..., Cn>() -> Builder<O, O, ..., O, C1, C2, ..., Cn>
       where //bound that affects Cs
    {...}
    // Setters. These require that there is O in the place that is set
    // and return Builder where that place is replaced with I
    impl Builder<I, I, I, ...> {
        pub fn build(self) -> Config {...}
    }
    pub struct Config<C1, C2, ..., Cn>
       where //bound that affects Cs
    {...}
    impl Config<C1, C2, ..., Cn>
       where //bound that affects Cs
    {
        // Getters
    }
}
pub use Builder::new as Builder;
pub use Builder::Config;

which is kinda funky:

WaDelma commented 7 years ago

Ah... I see your point clearly now. Lets remove all possible panics that user can cause and replace them with session types and error types then.

WaDelma commented 7 years ago

As custom derives become stable with macros 1.1, I went and implemented builder generation library with them. It should work on most use cases (even more than then the macro solution), but it's pretty ugly code.

ConnyOnny commented 7 years ago

Using bob is currently blocked https://github.com/WaDelma/bob/issues/1 Otherwise bob looks great! (except that it could really use some comments 😄)

WaDelma commented 7 years ago

The issue should be resolved and I added some documentation. I would love if you could give feedback on the documentation.

WaDelma commented 7 years ago

I updated the library to use alga (which means it works with newer nalgebra versions). This causes some conflicts with this pr.

Should I update the pr or are you willing/wanting to do it yourself? Also the issue that you reported for bob which prevented using it should be resolved. Have you had time to look at it? Would like some feedback on it before putting it to crates.io

ConnyOnny commented 7 years ago

Sorry, I didn't have time to look into it. bob is magic for me, as I don't know much about Rust's mighty macro system. I'm afraid in the near future I won't have the time to look deeply into bob and to update this PR.