At first I wrote clunky code. I learned more. I wrote clever code. I learned even more. Years later *I write clunky code on purpose*. Everyone on my team can trivially read it. Six months from now, I can trivially read it.
My old CS Prof's motto was "Code should be correct, clear and efficient. Prefer simple. Avoid clever" Years later I realize how much weight that carries
At first my pants were always dirty. I became more careful. Cleaned my clothes. Became even more careful. Years later, I *stopped caring about all the dirt on my pants* and purposefully run in dirt puddles. My friends were relieved that I didn't change, and that there is always someone beneath them as a human. Six months from now, I can trivially walk naked in the daylight.
Unfortunately this was difficult to follow as code would be displayed for only brief periods. I think it's fine to always show code and reduce screen switches - users are unlikely to get bored. Regarding content, I'd like to see a perf or "panic-safety" comparison. Is there any difference between these solutions?
I usually error on the side of letting people pause the video if they want to read it in depth. There is also code linked below in the description. (there would be a blog post as well to go with the video, but I'm re-launching the site the blog post would be on right now, so I'll have to link that later). As for panics, none of the options should panic. The big difference between the options that use .unwrap() and the ones that don't is whether the programmer has to keep the potential for a panic in mind while programming. A panic would only happen in those cases if the program was modified in the future to remove the starting assumptions, for example. perf could make an interesting video itself. The options weren't made with specific perf requirements in mind, but we could evaluate their runtime and heap usage to see if its worth changing anything.
@@chrisbiscardiI agree with the feedback. I wonder if highlighting what has changed would help. You were using selection when speaking about bits of each new version, but it was hard to see what all had changed. What if you leave out the text to parse and give before and then after with diff-like output to clarify changes?
This is less "Rustier" Rust and more of a nom tutorial. Also, it seemed to me that the more Rusty the code got, the more unreadable and opaque it became. Anyone can understand for loops and if statements. Fewer can understand fold / reduce. Even fewer will understand group_by and coalesce and all of the other itertools methods. Even if you understand the functional concept, you'll still have to reach for documentation to make sure the programmed behaviour lines up with your expectation.
The fact that you happen to be heavily exposed to for loops and not exposed to other approaches as much doesn't mean that fewer people can understand a fold. A group_by is not harder to understand than an equivalent for loop. Unfamiliar I could totally understand, but not harder... and this is kind of the point. You're clearly familiar with for loops and are fine using them to the exclusion of other approaches... but you should be able to read and use other functions from the standard library that better match what you're trying to achieve rather than re-implementing that functionality over and over using for loops. The core point of the video is that there are functions which do what you're trying to do and you should go look for them. Whether that's finding a new function you're unfamiliar with in itertools, a function on a type in the std lib, or in a third party crate like nom. for loops are fine, but they are far from the best solution to many problems, especially if those for loops access and mutate surrounding variables and leak into the rest of the program.
@chrisbiscardi I don't disagree that more people are familiar with for loops, but that's kind of the whole point. The fact that one concept, the for loop, has become so universally understood speaks to the flexibility and readability of the for loop. I'm familiar myself with the basic iterator functions like map, filter, reduce, but when the toolkit goes from 1-2 concepts to 20+ concepts, I'd argue that we've gone from simple and readable code to short but obtuse code. It is probably a philosophy difference, but idiomatic Rust code as you described it just looks much harder to read and write. We could argue that for loops are more error prone, but I strongly believe that that is equally true for iterator concepts, especially when you start introducing so many operators and combinators. Even in your video, you've kind of proven that point: you brought up three different methods of doing the same thing: fold, group_by, and coalesce. Writing Rustier Rust like you described becomes less about the business logic and more about arguing over which iterator concept is "better".
Great explanations! I would recommend using fewer transitions from Code + Thumbnail to only you without the code. It makes it harder to follow, especially with the transition effects.
This video was awesome! You did a great job of walking from "acceptable" to "ideal", explaining why we might make those changes. I would love to see more videos like this, demonstrating some nice features from useful crates as you make the code better.
@5:41 I agree with your reasoning here. It may be nice to use expect() vs unwrap() so if the panic is reached due to the unwraps it can include your reasoning.
at 8:40 it's because concatenating Strings needs allocation, and the definition of coalesce enforces the same type for the closure args and for the closure's return types, hence we already need to feed it Strings instead of &str. I would however use !s.is_empty() consistently in this example, as checking len() for not zero is less readable ;)
Very informative video on just writing code that works and the ideal way to to write code(that also works). Got to learn a thing or two and also get to know about crates I never knew about before. Keep making more videos like this. We appreciate it
Great video! A feedback to have video like that more enjoyable. The parts where you are explaining going fullscreen with webcam, it would be great to see a diff of the change (git style) it makes it much easier so understand at a glance. Have a nice day and thanks again!
Solid information! I think it would be much easier to follow the information in the video if you didn't hide the code so quickly in certain parts. Having to go back and pause isn't ideal, and having to only listen to the audio at times feels pretty meaningless.
The advanced code just exploded in complexity and introduced an external library. I would prefer the iterator solution on lines 25-31at 7:45, but with ```lines=input.trim().lines()``` instead of using an external crate to split. Or ```input.trim().split("
").map(|p| p.replace(" ", " ")).collect()```, which was already suggested in the comments. The "Windows newline issue" is a fair counterpoint though, so a solution that can utilize ```input.lines()``` would be preferable IMO.
yes, you can absolutely overfit the problem with a specific solution. As I mentioned in the video though, this is a parsing problem usually contained as part of a larger parser, such as markdown, which would require extending the parser with both inline and other block level content. Dependencies are good and cargo/the ecosystem is one of Rust's major strengths. You can always spend time rewriting a dependency into a more fitted solution, but you can't get the time you spent rewriting pre-existing functionality back. This is only an increase in complexity when viewed from the over-fitted "this passes the one test in the video" viewpoint. The complexity is reduced by using the external library if you consider that you'll have to build more parsing functionality in addition to this one test example. At which point the benefit of building parsers that compose together with a familiar function signatures and patterns outweighs the need to make a new decision about how to parse every new piece of functionality.
What if your input starts with two new lines? or what if your have more than two new lines between paragraphs? Now suppose you want to add the ability to parse anything else, say a code block (that may have multiple subsequent newlines). How do you handle that? I think the conclusion of the video is on point. Basically, anything is fine if it fits your requirements, but it's always good to know more than one solution to a problem. That one liner is fine if you're certain that you're not gonna want more complexity and if you're constraining your input a bit, while the composable parsers approach is very flexible at the expense of having some learning curve.
This is like Wolfenstein, difficulty selection, first you have agent Blaskowicz with teether, normal guy, and masochist view, with blood over his face,... it has same energy as this video :D :D But fun aside, I would prefer, the first version of code. Yes the second part is more "rusty" (maybe also performant in 0.00001 milis, who knows,..), but I can't imagine junior (or senior migrating to rust) coming to work on my project and seeing it. Also when someone works on multiple projects, in multiple languages, its better to unity code style as much as possible, even in price of violating the given language rules,..... (which is possible with first style almost in every language).
heh, the difficulty selection/gradient of options was definitely the goal :D I'm not sure which version you're referring to as "the second part". Are you saying that using .fold() is too much for a senior engineer? or are you referring to using nom at the end?
@@chrisbiscardisecond part i mean second part of video , “rusty” code alternative. The whole, i dont know how to call it “get” instead of “push” architecture,.. with iter, collect,.. like its not hard of course, but when you are old school developer, you dont get a lot of simmilar aproaches in schools with c# and java,… (yeah today those languages has it too in never versions)
@@chrisbiscardi Sorry I didn`t saw the "fold" alternetive, I was jumping across the video.... :D :D 1-2 fine, 3th is hard... not saying its exactly hard for senior, but not so easy to mount on new project. For example I'm c++ / typescript / python dev, and I had to jump on c# project which I was able over a night,...
Nice. Ideally, `tuple((newline, newline))` should be its own parser, `blank_line` or `break`, since you may have additional white space between line breaks which aught to be ignored.
The way I’d do it intuitively is fn paragraph(inp: &str) -> Vec { let mut lines = inp.lines(); iter::from_fn(move || { (!lines.is_empty).then-some(lines.by_ref().take_while(|x| !x.is_empty()).collect::()) }).collect() } iter::from_fn is just a more readable way of writing (0..).map(|_| …), ie an iterator where you selectively advance another iterative to generate values. Alternatively, I’d replace the return Vec with return impl Iterator, so the caller can decide to collect or use the Iterator however they want. I haven’t checked this, might well not work.
The approach you've taken here is the group_by example from the video but slightly obscured (and also not working but I'm going off the gist of what you're intending). input .lines() .group_by(|line| !line.is_empty()) .into_iter() .map(|(_key, mut group)| group.join(" ")) .filter(|s| !s.is_empty()) .collect();
Not sure exactly which part you're referring to, but for the nom version its a trivial fix to accommodate that (many1(newline) instead of newline): play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=c7873701298b352fca56596eaf53b25c
@@i--i4933 I remove all of the optional UI elements and a lot of the popups/auto intelligence/etc in the settings so that it works better for presenting code
Regex tend to be "write only", especially as you get into larger regex applications, while parser combinators are individually testable/composable functions that can hold their own documentation and such.
@@chrisbiscardi ah I see, more scalable, more modular. Describing regex as write only made it make sense to me, I wouldn't want to try merging two complicated expressions
You may not have heard the apocryphal advice about regexes: 1. You have a problem. 2. You think, "I know! I can use a regex for this!" 3. Now you have two problems. :-)
Excellent material, and very interesting and informative, but, as a person with an ADHD brain I found this painful to watch - the frequent changes and transition effects made it unbelievably hard to follow, and I had to give up after about 5 mins. I watched those 5 mins twice because the content itself was very good, but actually trying to watch it was so uncomfortable, I had to basically close my eyes and just listen. Normally I do better with videos than blog posts, but this was too much for me. Please don't take this as a criticism - I may not be representative of your target audience (although I suspect neurodivergent folk are particularly well represented among the set of people watching youtube videos about Rust programming), but perhaps bear it in mind for future productions.
its fine for the specific case in the video, but harder to extend to be platform independent ( vs ) and harder to use if there are items inside it like markdown would have (todo items, etc). I'd still replace .split(' ') with .lines() in this approach, as it accounts for and
Here's how I'd do the problem:
fn paragraphs(input: &str) -> Vec {
input
.split("
")
.map(|p| p.replace("
", " "))
.collect()
}
How would you handle windows line endings?
@@chrisbiscardi replace("
", "
") in the beginning 😁
Was looking for this
At first I wrote clunky code. I learned more. I wrote clever code. I learned even more. Years later *I write clunky code on purpose*. Everyone on my team can trivially read it. Six months from now, I can trivially read it.
My old CS Prof's motto was
"Code should be correct, clear and efficient. Prefer simple. Avoid clever"
Years later I realize how much weight that carries
At first my pants were always dirty. I became more careful. Cleaned my clothes. Became even more careful. Years later, I *stopped caring about all the dirt on my pants* and purposefully run in dirt puddles. My friends were relieved that I didn't change, and that there is always someone beneath them as a human. Six months from now, I can trivially walk naked in the daylight.
@@twothreeoneoneseventwoonefour5wack analogy
@@davidrudpedersen5622 wack reply
@@davidrudpedersen5622it was funny though 😂
Unfortunately this was difficult to follow as code would be displayed for only brief periods.
I think it's fine to always show code and reduce screen switches - users are unlikely to get bored.
Regarding content, I'd like to see a perf or "panic-safety" comparison. Is there any difference between these solutions?
I usually error on the side of letting people pause the video if they want to read it in depth. There is also code linked below in the description. (there would be a blog post as well to go with the video, but I'm re-launching the site the blog post would be on right now, so I'll have to link that later).
As for panics, none of the options should panic. The big difference between the options that use .unwrap() and the ones that don't is whether the programmer has to keep the potential for a panic in mind while programming. A panic would only happen in those cases if the program was modified in the future to remove the starting assumptions, for example.
perf could make an interesting video itself. The options weren't made with specific perf requirements in mind, but we could evaluate their runtime and heap usage to see if its worth changing anything.
@@chrisbiscardi Frequent screen switches are just annoying. The content is very interesting, but I had difficulty perceiving it because of these
@@chrisbiscardiI agree with the feedback. I wonder if highlighting what has changed would help. You were using selection when speaking about bits of each new version, but it was hard to see what all had changed. What if you leave out the text to parse and give before and then after with diff-like output to clarify changes?
Bruh just press pause???
This is less "Rustier" Rust and more of a nom tutorial.
Also, it seemed to me that the more Rusty the code got, the more unreadable and opaque it became.
Anyone can understand for loops and if statements. Fewer can understand fold / reduce. Even fewer will understand group_by and coalesce and all of the other itertools methods.
Even if you understand the functional concept, you'll still have to reach for documentation to make sure the programmed behaviour lines up with your expectation.
The fact that you happen to be heavily exposed to for loops and not exposed to other approaches as much doesn't mean that fewer people can understand a fold. A group_by is not harder to understand than an equivalent for loop. Unfamiliar I could totally understand, but not harder... and this is kind of the point. You're clearly familiar with for loops and are fine using them to the exclusion of other approaches... but you should be able to read and use other functions from the standard library that better match what you're trying to achieve rather than re-implementing that functionality over and over using for loops.
The core point of the video is that there are functions which do what you're trying to do and you should go look for them. Whether that's finding a new function you're unfamiliar with in itertools, a function on a type in the std lib, or in a third party crate like nom. for loops are fine, but they are far from the best solution to many problems, especially if those for loops access and mutate surrounding variables and leak into the rest of the program.
@chrisbiscardi I don't disagree that more people are familiar with for loops, but that's kind of the whole point.
The fact that one concept, the for loop, has become so universally understood speaks to the flexibility and readability of the for loop. I'm familiar myself with the basic iterator functions like map, filter, reduce, but when the toolkit goes from 1-2 concepts to 20+ concepts, I'd argue that we've gone from simple and readable code to short but obtuse code.
It is probably a philosophy difference, but idiomatic Rust code as you described it just looks much harder to read and write. We could argue that for loops are more error prone, but I strongly believe that that is equally true for iterator concepts, especially when you start introducing so many operators and combinators.
Even in your video, you've kind of proven that point: you brought up three different methods of doing the same thing: fold, group_by, and coalesce. Writing Rustier Rust like you described becomes less about the business logic and more about arguing over which iterator concept is "better".
Great explanations!
I would recommend using fewer transitions from Code + Thumbnail to only you without the code. It makes it harder to follow, especially with the transition effects.
This video was awesome! You did a great job of walking from "acceptable" to "ideal", explaining why we might make those changes. I would love to see more videos like this, demonstrating some nice features from useful crates as you make the code better.
glad you enjoyed it! I'll have to come up with some other useful crates to make examples for.
@5:41 I agree with your reasoning here. It may be nice to use expect() vs unwrap() so if the panic is reached due to the unwraps it can include your reasoning.
I could watch a ton of videos like this.
One idea: teach the standard library by refactoring imperative code into the stdlib alternatives.
at 8:40 it's because concatenating Strings needs allocation, and the definition of coalesce enforces the same type for the closure args and for the closure's return types, hence we already need to feed it Strings instead of &str. I would however use !s.is_empty() consistently in this example, as checking len() for not zero is less readable ;)
5:29 We don't need to make an anonymous function, we can just pass in the is_empty function: is_some_and(String::is_empty)
Very informative video on just writing code that works and the ideal way to to write code(that also works). Got to learn a thing or two and also get to know about crates I never knew about before. Keep making more videos like this. We appreciate it
I really like this video format by the way. I hadn't seen you do it before -- but hope to see you do more.
thanks, I think the format is good, there are things I'd like to improve upon for the next time I use it.
Great video! A feedback to have video like that more enjoyable. The parts where you are explaining going fullscreen with webcam, it would be great to see a diff of the change (git style) it makes it much easier so understand at a glance.
Have a nice day and thanks again!
Solid information!
I think it would be much easier to follow the information in the video if you didn't hide the code so quickly in certain parts. Having to go back and pause isn't ideal, and having to only listen to the audio at times feels pretty meaningless.
You basically introduced nom parser to me in your "advent of code" series. Thanks!
1:15 poor lines.. 💔
I felt like I'm in a Flutter's official tutorials, switching screens doesn't make difficult to follow for me but might be for others.
i got sick because of this camera shaking. Thanks for video
The advanced code just exploded in complexity and introduced an external library.
I would prefer the iterator solution on lines 25-31at 7:45, but with ```lines=input.trim().lines()``` instead of using an external crate to split.
Or ```input.trim().split("
").map(|p| p.replace("
", " ")).collect()```, which was already suggested in the comments. The "Windows newline issue" is a fair counterpoint though, so a solution that can utilize ```input.lines()``` would be preferable IMO.
yes, you can absolutely overfit the problem with a specific solution. As I mentioned in the video though, this is a parsing problem usually contained as part of a larger parser, such as markdown, which would require extending the parser with both inline and other block level content.
Dependencies are good and cargo/the ecosystem is one of Rust's major strengths. You can always spend time rewriting a dependency into a more fitted solution, but you can't get the time you spent rewriting pre-existing functionality back.
This is only an increase in complexity when viewed from the over-fitted "this passes the one test in the video" viewpoint. The complexity is reduced by using the external library if you consider that you'll have to build more parsing functionality in addition to this one test example. At which point the benefit of building parsers that compose together with a familiar function signatures and patterns outweighs the need to make a new decision about how to parse every new piece of functionality.
What if your input starts with two new lines? or what if your have more than two new lines between paragraphs? Now suppose you want to add the ability to parse anything else, say a code block (that may have multiple subsequent newlines). How do you handle that? I think the conclusion of the video is on point. Basically, anything is fine if it fits your requirements, but it's always good to know more than one solution to a problem. That one liner is fine if you're certain that you're not gonna want more complexity and if you're constraining your input a bit, while the composable parsers approach is very flexible at the expense of having some learning curve.
I have implemented group_by algorithm at my js job some time age. It's about cards that are grouped into blocks by their background
Audio recorderoutside was impressive good and crisp 🙌
Very cool analysis of non-idiomatic code and its improvements!
This is like Wolfenstein, difficulty selection, first you have agent Blaskowicz with teether, normal guy, and masochist view, with blood over his face,... it has same energy as this video :D :D But fun aside, I would prefer, the first version of code. Yes the second part is more "rusty" (maybe also performant in 0.00001 milis, who knows,..), but I can't imagine junior (or senior migrating to rust) coming to work on my project and seeing it. Also when someone works on multiple projects, in multiple languages, its better to unity code style as much as possible, even in price of violating the given language rules,..... (which is possible with first style almost in every language).
100% couldn't agree more
heh, the difficulty selection/gradient of options was definitely the goal :D
I'm not sure which version you're referring to as "the second part". Are you saying that using .fold() is too much for a senior engineer? or are you referring to using nom at the end?
@@chrisbiscardisecond part i mean second part of video , “rusty” code alternative. The whole, i dont know how to call it “get” instead of “push” architecture,.. with iter, collect,.. like its not hard of course, but when you are old school developer, you dont get a lot of simmilar aproaches in schools with c# and java,… (yeah today those languages has it too in never versions)
@@DJenriqez so you are saying that .fold() is too much for a senior engineer? I think we disagree on that at least.
@@chrisbiscardi Sorry I didn`t saw the "fold" alternetive, I was jumping across the video.... :D :D 1-2 fine, 3th is hard... not saying its exactly hard for senior, but not so easy to mount on new project. For example I'm c++ / typescript / python dev, and I had to jump on c# project which I was able over a night,...
Gonna have to watch this one a couple times hahah, tons of info. I guess you could say I'll... iterate :D
feel free to drop any questions you have :)
Perfectly well put together video. Thank you.
Very useful, thanks. It will be great to know more bout _nom_ parser combinator, through an example. 👍
Nice choices! Thanks for sharing!
Nice.
Ideally, `tuple((newline, newline))` should be its own parser, `blank_line` or `break`, since you may have additional white space between line breaks which aught to be ignored.
Good video but... If you are explaining code, remove video effects, quit hiding the code all the time, slow down your presentation.
I wonder if by binary sniffing we would find that the beginner code is the most concise.
Yeah this is why I like Go.
The way I’d do it intuitively is
fn paragraph(inp: &str) -> Vec {
let mut lines = inp.lines();
iter::from_fn(move || {
(!lines.is_empty).then-some(lines.by_ref().take_while(|x| !x.is_empty()).collect::())
}).collect()
}
iter::from_fn is just a more readable way of writing (0..).map(|_| …), ie an iterator where you selectively advance another iterative to generate values.
Alternatively, I’d replace the return Vec with return impl Iterator, so the caller can decide to collect or use the Iterator however they want.
I haven’t checked this, might well not work.
The approach you've taken here is the group_by example from the video but slightly obscured (and also not working but I'm going off the gist of what you're intending).
input
.lines()
.group_by(|line| !line.is_empty())
.into_iter()
.map(|(_key, mut group)| group.join(" "))
.filter(|s| !s.is_empty())
.collect();
If you have multiple empty lines, won't that create empty paragraphs?
Not sure exactly which part you're referring to, but for the nom version its a trivial fix to accommodate that (many1(newline) instead of newline): play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=c7873701298b352fca56596eaf53b25c
fold or reduce or inject
question
what editor/ide are you using?
VSCode
@@chrisbiscardi thanks
it looks so much cleaner than my version of it :)
@@i--i4933 I remove all of the optional UI elements and a lot of the popups/auto intelligence/etc in the settings so that it works better for presenting code
@@chrisbiscardi cool, your vids are great , really helped me with understanding rust
much love
This feels like a regex problem, why would you prefer this solution over regex? The approach to "rustifying" code in a broader sense was cool though
Regex tend to be "write only", especially as you get into larger regex applications, while parser combinators are individually testable/composable functions that can hold their own documentation and such.
@@chrisbiscardi ah I see, more scalable, more modular. Describing regex as write only made it make sense to me, I wouldn't want to try merging two complicated expressions
You may not have heard the apocryphal advice about regexes:
1. You have a problem.
2. You think, "I know! I can use a regex for this!"
3. Now you have two problems.
:-)
I don't think using iterator determines a beginner or advance code. IMO
great video to only reduced by way too many sliding transitions, please stop, very hard to follow
Rust. Ruster. Rustest.
Great videos thanks a lot
This is excellent content 🙌
types ❤
Excellent material, and very interesting and informative, but, as a person with an ADHD brain I found this painful to watch - the frequent changes and transition effects made it unbelievably hard to follow, and I had to give up after about 5 mins. I watched those 5 mins twice because the content itself was very good, but actually trying to watch it was so uncomfortable, I had to basically close my eyes and just listen. Normally I do better with videos than blog posts, but this was too much for me. Please don't take this as a criticism - I may not be representative of your target audience (although I suspect neurodivergent folk are particularly well represented among the set of people watching youtube videos about Rust programming), but perhaps bear it in mind for future productions.
Rusty Luke Smith
What about
input.split("
").map(|p| p.split('
').join(" ")).collect()
its fine for the specific case in the video, but harder to extend to be platform independent (
vs
) and harder to use if there are items inside it like markdown would have (todo items, etc).
I'd still replace .split('
') with .lines() in this approach, as it accounts for
and