Add Functional Programming - Generics as Type Classes - #249
Conversation
|
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
left a comment
There was a problem hiding this comment.
I think it's just that the patterns subdirectory was missing
|
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. |
|
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. |
|
Maybe this one is also a nice addition to |
|
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. |
simonsan
left a comment
There was a problem hiding this comment.
@marcoieni @pickfire Want to give it a read as well?
|
|
||
| Here is what that looks like: | ||
|
|
||
| ```rust,ignore |
There was a problem hiding this comment.
I would prefer to remove this ignore flag, even if it requires copy pasting from previous code blocks.
What other people think about this?
There was a problem hiding this comment.
Could be nice to have a working example here, indeed. So people can copy and paste it and try it out/play around with it.
There was a problem hiding this comment.
I only did that to avoid repeating all the typing. I can easily put that back in and make it work.
There was a problem hiding this comment.
Hmm, this is turning out to be trickier than I thought. I may have to refactor the example to make it simple enough. That will take me a bit.
There was a problem hiding this comment.
If you want to you can do that in another PR as well (regarding removing the ignore). It would be nice to have a working example that is checked, but not always necessary. Also this is quite complex so I'm fine with just refactoring it at a later stage, maybe even by someone else. @marcoieni thoughts?
There was a problem hiding this comment.
It's up to jhwgh. If you want you can do it in another PR :)
|
What's the advantage of using this approach for state machines with respect to the one covered by the rust book ? |
|
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. |
|
|
||
| Here is what that looks like: | ||
|
|
||
| ```rust,ignore |
There was a problem hiding this comment.
If you want to you can do that in another PR as well (regarding removing the ignore). It would be nice to have a working example that is checked, but not always necessary. Also this is quite complex so I'm fine with just refactoring it at a later stage, maybe even by someone else. @marcoieni thoughts?
| interp.compile_script("print('2 + 2')").unwrap(); | ||
| interp.exec().unwrap(); | ||
| } | ||
| ``` |
There was a problem hiding this comment.
Example seemed complicated. If someone like me who had no experience with state machine, this will be hard to understand. Just wondering could there be a simpler example?
Say, empty letter -> written letter -> closed letter?
| struct Interpreter<S: State> { | ||
| // same fields for execution, but now add: | ||
| state_data: S, | ||
| } |
There was a problem hiding this comment.
Seemed extra to me. Why not just Interpreter -> LoadedInterpreter -> ReadiedInterpreter or something similar since they won't share common stuff?
Or maybe this is just to show <S: State>? Or maybe we can show both methods.
|
|
||
| ```rust,ignore | ||
| fn main() { | ||
| let mut interp = Interpreter::<Init>::default().init(); |
There was a problem hiding this comment.
Having this turbofish is the downside of what I mentioned. Not ergonomics and what if user does Interpreter::<Loaded>::default() instead? At least default isn't there in this case which is safe.
There was a problem hiding this comment.
The default only being in the init state is precisely what I intended, for that exact reason.
I agree it's clunky, but also I think it's reasonable to recomment. If you don't put the turbofish in at all, I think the current stable has a good suggestion to lead you in the right direction.
|
@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. |
|
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 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. |
|
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.) |
Co-authored-by: Marco Ieni <11428655+MarcoIeni@users.noreply.github.com>
|
I have a couple more review comments to go, but I am getting close.
Done.
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.
Ah, that makes sense. I will fix it. |
|
The |
|
Cool! I'll also read through it again in the next days. <3 |
marcoieni
left a comment
There was a problem hiding this comment.
This is great! I approved it. simonsan, merge this if it looks good to you once you have read it :)
|
Thanks, @marcoieni! I unfortunately could not think of an easy constructor for main, but I think this example is clear enough as it is. |
|
Nice, thank you! :) |


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.