rust-unofficial / patterns

A catalogue of Rust design patterns, anti-patterns and idioms
https://rust-unofficial.github.io/patterns/
Mozilla Public License 2.0
7.85k stars 354 forks source link

Add Functional Programming - Generics as Type Classes #249

Closed jhwgh1968 closed 3 years ago

jhwgh1968 commented 3 years ago

I put this under functional languages, since it is really an example of the concept of "type classes" in Rust. If this specific example is more of a design pattern, I would be happy to move it.

If no one beats me to it, I should be able to fix the two FIXMEs at the bottom sometime next week.

jhwgh1968 commented 3 years ago

Hmm, the errors seem to be caused by broken links. I thought relative links would work in mdbook.

Any guidance on how to fix those?

simonsan commented 3 years ago

This is related to #100 isn't it? Maybe you can take the links from there, I posted some articles in it, that describe the pattern.

jhwgh1968 commented 3 years ago

Thanks, @simonsan. I actually missed that PR somehow.

I have added the bottom of the two links you mentioned. Now my question is, how do I break up the link for the hard wrap limit? It is longer than 80 characters by itself.

simonsan commented 3 years ago

Maybe this one is also a nice addition to Read More: https://web.archive.org/web/20210328164854/https://rustype.github.io/notes/notes/rust-typestate-series/rust-typestate-index

jhwgh1968 commented 3 years ago

The second link makes sense to me, @simonsan, and seem to cover the type theory in more depth. I also put in a small wording change to be more precise, and helpful if people Google this.

I think it's a solid first version that is ready to merge, unless you have other additions.

simonsan commented 3 years ago

The second link makes sense to me, @simonsan, and seem to cover the type theory in more depth. I also put in a small wording change to be more precise, and helpful if people Google this.

I think it's a solid first version that is ready to merge, unless you have other additions.

I need to give it a full read still, haven't completely reviewed it due to time constrains and just helped you fix the CI tests. Will take care of it the next two days. Cheers.

MarcoIeni commented 3 years ago

What's the advantage of using this approach for state machines with respect to the one covered by the rust book ?

jhwgh1968 commented 3 years ago

The main advantage over the book, @MarcoIeni, is that this allows for shared data and IMO more readable shared impls.

I will type up that explanation in the PR.

pickfire commented 3 years ago

@jhwgh1968 Thanks for the PR and sorry for the wait. Although the example is simple, I believe it could be easier for someone with less computer science foundation to learn without having to know what is an interpreter. I gave an example (taken from somewhere else) but another other example with states could work.

Also, I discussed another way to do this typestate pattern without using generic which could provide both a more ergonomic API and easier to understand in the docs. I forgot the link to the original article but it was a good read.

jhwgh1968 commented 3 years ago

Thanks for the comments, @pickfire.

This example is a little clunky, I agree. But unfortunately, all the times I have used it have been pretty specialized.

The latest is some work I'm doing for the warc crate. In that project, the generics are used to keep a common API for a Record, making the generic variants the way the body data of the record is accessed, based on how the record was created.

That is the uniquely Rust (or Haskell) way to approach the problem. It keeps the common parts of the API in one place, and surfaces any API misuse of the body accessors at compile time.

Those common parts are really key to the example, and they turned out to be missing when I actually tried to make it start working. Examples of that are extra hard to pick.

I will see if I can come up with a better example over the next several days, and report back.

jhwgh1968 commented 3 years ago

I had a brain wave this evening: type states are not really what I'm trying to demonstrate. It's type classes using generics.

Given that, I came up with an example much closer to the times I have reached for this feature. It was not to build type states.

Hopefully it will make more sense and be more useful now!

(Switching to draft until the rewrite is done.)

simonsan commented 3 years ago

Reopened #100 due to change of scope of this PR

jhwgh1968 commented 3 years ago

Alright, I have improved this significantly, and added examples.

Most important, I feel I have finally narrowed in on what is most relevant: the functional languages concept that Rust supports, and many non-function programmers (including my past self) would benefit from when they are designing types.

jhwgh1968 commented 3 years ago

The failure to build is:

  --> /tmp/mdbook-3OvrCB/functional/generics-type-classes.md:121:17
   |
17 |     use super::{bootp, nfs};
   |                 ^^^^^  ^^^ no `nfs` in the root
   |                 |
   |                 no `bootp` in the root

error: aborting due to previous error

I defined those modules in the example, so I presume this is something weird with resolving in rustdoc/mdbook. How do I fix this?

MarcoIeni commented 3 years ago

I defined those modules in the example, so I presume this is something weird with resolving in rustdoc/mdbook. How do I fix this?

The problem is that mdbook automatically adds fn main() { } around everything because it doesn't exist.

image

You may want move the underlying main function in the same code block. Or you could implement a new main function for the happy case, it's up to you.

MarcoIeni commented 3 years ago

Also please, do not force push. It's easier for us to see what changes from review to review. Everything will be squashed in one commit anyway when we merge this.

MarcoIeni commented 3 years ago

image Can you specify that with code size we mean the size of the binary? Feel free to rephrase this as you prefer of course.

MarcoIeni commented 3 years ago

This looks great. Thanks! Other than the minor comments I left it looks good to me.

jhwgh1968 commented 3 years ago

I have a couple more review comments to go, but I am getting close.

Can you specify that with code size we mean the size of the binary?

Done.

Also please, do not force push. It's easier for us to see what changes from review to review. Everything will be squashed in one commit anyway when we merge this.

On other PRs I saw maintainers doing "merge commits" with master, which usually creates a giant mess in git. I will keep that in mind that you always squash, and not worry about it.

The problem is that mdbook automatically adds fn main() { } around everything because it doesn't exist.

Ah, that makes sense. I will fix it.

jhwgh1968 commented 3 years ago

The main function I added to the compiling example is currently empty. I am planning to put in a simple example of using the types, but I will have to think about it (because construction is something I skipped over).

MarcoIeni commented 3 years ago

On other PRs I saw maintainers doing "merge commits" with master, which usually creates a giant mess in git. I will keep that in mind that you always squash, and not worry about it.

We set this in December: image

I have a couple more review comments to go, but I am getting close.

Yes, you are! 💪

simonsan commented 3 years ago

Cool! I'll also read through it again in the next days. <3

MarcoIeni commented 3 years ago

The CI is failing for reasons unrelated to this PR: image

jhwgh1968 commented 3 years ago

Thanks, @MarcoIeni! I unfortunately could not think of an easy constructor for main, but I think this example is clear enough as it is.

simonsan commented 3 years ago

Nice, thank you! :)