Improving Rust code with combinators

Поділитися
Вставка
  • Опубліковано 15 бер 2022
  • Today we are doing some functional programming in Rust! Specifically we will be discussing how to improve Rust code using combinators.
    code: github.com/letsgetrusty/combi...
    #rust #functional #programming
  • Наука та технологія

КОМЕНТАРІ • 144

  • @letsgetrusty
    @letsgetrusty  2 роки тому +4

    📝Get your *FREE Rust cheat sheet* :
    www.letsgetrusty.com/cheatsheet

  • @jaysistar2711
    @jaysistar2711 2 роки тому +54

    Yes, more functional programming in Rust videos, please!

  • @naughtrussel5787
    @naughtrussel5787 2 роки тому +66

    I'd like to see more videos about functional programming in Rust. I personally do not program in Rust, but the concepts you teach are extremely useful for a programmer of any language.

  • @Zytaco
    @Zytaco 2 роки тому +35

    Your non combinator code can be made a lot prettier by not using is_some() methods. Instead use an if let. For example:
    if let Some(name) = s.next() {
    //Do what you want here.
    }
    This checks if there's a name and immediately puts it in the the variable 'name' on the same line.
    Sadly, if let chains are not yet stable so you can't do these multiple times in the same if statement. (Although you can on nightly rust.) But you can still use match statements like:
    match (s.next(), s.next()) {
    (Some(name), Some(gpa) => {
    //Do what you want here.
    },
    _ => ().
    }

    • @tomaspecl1082
      @tomaspecl1082 2 роки тому +17

      You can write
      if let (Some(name),Some(gpa)) = (name,gpa) { /* code */ }
      You dont need match for this.

    • @alagaika8515
      @alagaika8515 Рік тому +5

      Also, the nested ifs can be avoided by negating them and using continue for the cases where the student in question is not a good student.

  • @adamhenriksson6007
    @adamhenriksson6007 2 роки тому +47

    In rust there is a difference in performance between the first and second method depending on how you design combinators.
    You should show if this code is as perfromant as the first piece of code. Sometimes it's also more perfromant because of vectorization, but that also does not always happen.

    • @UltimateMonkeyman
      @UltimateMonkeyman 2 роки тому +9

      would like to see performance comparison for with vs without combinators as well. cheers

    • @ibrahimshehuibrahim918
      @ibrahimshehuibrahim918 2 роки тому +8

      i think rust has zero cost abstraction similar to c++

    • @adamhenriksson6007
      @adamhenriksson6007 2 роки тому +3

      @@ibrahimshehuibrahim918 yeah but that is a compiler optimization. Not sure if this applies in all cases or just the more common/trivial cases.

    • @G11713
      @G11713 2 роки тому

      The combinator code could also be optimized such as into a bunch of maps and/or delay creation of the Students structure until after the filter. Of course, who knows how the compiler optimization might evolve the code.

    • @yoannguillard6877
      @yoannguillard6877 2 роки тому +2

      As far as I know combinators are more optimized concerning the array. It will statically allocate the right size for the array, which requires to use everytime a capacitor if you do it sequencially

  • @canewsin
    @canewsin 2 роки тому +32

    instead for map.flatten.filter, filter_map can do it with single combinator.

    • @maximebeaudoin4013
      @maximebeaudoin4013 2 роки тому +1

      Don't keep it to yourself, what's your suggestion?

    • @psychic-lizard
      @psychic-lizard 2 роки тому +13

      @@maximebeaudoin4013Basically, filter_map accepts a closure that returns an Option. If an item returns the Some variant, it will keep the mapped item, but if it returns the None variant, it will be filtered out. It's basically like calling map that turns the elements into Option types, and then filtering out the None variants.
      The rust docs explain it much better than I can: doc.rust-lang.org/std/iter/trait.Iterator.html#method.filter_map

    • @canewsin
      @canewsin 2 роки тому +7

      @@maximebeaudoin4013
      >
      let students = vec!["Z 5", "A 10", "B 20", "C 30"];
      let good_students = students
      .iter()
      .filter_map(|s| {
      let mut parts = s.split_whitespace();
      let name = parts.next()?.to_owned();
      let score = parts.next()?.parse::().unwrap();
      if score

    • @31redorange08
      @31redorange08 2 роки тому +1

      @@canewsin unwrap() 🤔

    • @WolvericCatkin
      @WolvericCatkin 2 роки тому

      @@canewsin Just contributing my personal implementation...
      #[derive(Debug)]
      struct Student {name: String, gpa: u8}
      let students = vec!["A 5", "B 10", "C 15", "D 20"];
      let good_students: Vec = students.iter()
      .filter_map(|x|{
      let mut x = x.split_whitespace();
      let name = x.next()?;
      let gpa = x.next()
      .and_then(|gpa|gpa.parse().ok())
      .filter(|&gpa|gpa > 10)?;
      let name = name.to_owned();
      Some(Student{name, gpa})
      })
      .collect();
      good_students.iter()
      .for_each(|x|println!("{:?}", x));

  • @JayKnight
    @JayKnight Рік тому +2

    I started learning rust with Advent of Code, and I wish I had seen this video earlier. More than half of my time was spent figuring out how to parse the input into the structure I needed!

  • @codetothemoon
    @codetothemoon 2 роки тому +3

    Definitely up for more FP Rust videos!

  • @artgocode
    @artgocode 2 роки тому +12

    Thank you so much! Your content is concise and very helpful for newbies.

  • @damonpalovaara4211
    @damonpalovaara4211 2 роки тому +7

    I almost never use for loops now that I've gotten used to functional programming with Rust. Parsing data is really easy to do with functional programming.

  • @theondono
    @theondono 2 роки тому +20

    Pro tip: if you have a struct with a lot of booleans (think something like a config). Instead of having a “new” function with dozens of parameters, set new to return a config with all booleans to false. Then create combinators to set each variable.
    You can then just chain the ones that need to be set after your new call. Much more ergonomic and the compiler will optimize the calls away. 👌

    • @letsgetrusty
      @letsgetrusty  2 роки тому +2

      Awesome tip!

    • @theondono
      @theondono Рік тому

      @@marcyeo1 Yes, but the point of the tip was making them chain-able. Instead of having:
      let config = Config::new();
      config.flag1(true);
      config.flag2(true);
      config.flag3(false);
      config.flag4(true);
      You have:
      let config = Config::new()
      .flag1(true)
      .flag2(true)
      .flag4(true);
      Depending on your flags and their names, it also makes a lot more of sense to talk about the properties something has, than on those it doesn't.

    • @bencarville1045
      @bencarville1045 8 місяців тому

      @@theondono I would prefer to `impl Default for Config` and then
      let config = Config {
      flag1: true,
      flag2: true,
      flag4: true,
      ..default()
      }
      so you don't have to define a method for each flag.

    • @theondono
      @theondono 8 місяців тому

      @@bencarville1045 I’ve also used this option. I’m realizing too that I explained mine particularly badly, because the point of it is to avoid having the boolean values in the first place. What I should have written is something in the lines of:
      let cat = Cat::new()
      .has_stripes()
      .has_sharp_claws()
      .has_temper();
      Where all of this options are booleans that are set to false by default. In this scenario you just add the features you want the cat to have, and avoid having to write anything about the ones it doesn’t, so that reduces visual clutter IMO. It also completely hides the actual “polarity” of the Boolean used in case it’s useful internally.

  • @HUEHUEUHEPony
    @HUEHUEUHEPony Рік тому +7

    thats not combinator thats just functional programming

  • @SebastianSastre
    @SebastianSastre 2 роки тому +1

    Now I know that in Smalltalk I never use anything that isn't a combinator. And now I know how to preserve that good style in Rust. That was awesome, thanks for sharing this.

  • @oyewodayo
    @oyewodayo Рік тому

    I so much appreciate videos with use-cases like this. Do more of this please. Thanks.

  • @jeffg4686
    @jeffg4686 2 роки тому +1

    +1 for flatten()ing Option based iterators - hadn't come across that yet.
    I had previously thought it was only purposed for flattening nested iterables

    • @durnsidh6483
      @durnsidh6483 Рік тому +2

      Option is an iterable, if it's a Some variant it returns Some(value) followed by None, otherwise it returns None.

    • @jeffg4686
      @jeffg4686 Рік тому +1

      @@durnsidh6483 - interesting, thanks.

  • @Maartz
    @Maartz 2 роки тому +5

    Coming from Elixir/Erlang, it's sweet to see that Rust has a nice FP side. Thanks for the video! Love it.

    • @JayGodseOnPlus
      @JayGodseOnPlus 2 роки тому +1

      As with Rust, one of the early language designers of Elixir is also a Ruby guy (Jose Valim, I believe).

    • @Maartz
      @Maartz 2 роки тому +2

      @@JayGodseOnPlus yes indeed! I didn’t knew that one designer of Rust was coming from Ruby.
      But definitely you can feel the FP touch in Rust and it’s delightful.

  • @cramhead
    @cramhead 2 роки тому

    Great vid! More functional and idiomatic programming content please. These bite sized vids are great as they are easy to fit into a packed schedule.

  • @hmbil694
    @hmbil694 2 роки тому +2

    This is really cool I’d love to see more functional rust stuff!

  • @hongotopiadada7574
    @hongotopiadada7574 2 роки тому

    I loved that video and it would be awesome to see more stuff about combinators! thx

  • @manis0us
    @manis0us 2 роки тому +2

    Thanks, please add more functional programming concepts in rust.

  • @eduingarciacordero9255
    @eduingarciacordero9255 2 роки тому

    Very good. This flatten function change everything. Thank's

  • @mateusvmv
    @mateusvmv 2 роки тому +6

    You could also have used flat_map, as it does what map, flatten and filter are supposed to do all at once!

  • @JayGodseOnPlus
    @JayGodseOnPlus 2 роки тому

    I'd love to see more such videos of functional programming using combinators, especially using map, reduce, and filter. And then show it using the Rayon crate to harness multiple cores in parallel.

  • @moiattube
    @moiattube 2 роки тому

    So clear explanation! Thanks a lot

  • @mrlordbrutish
    @mrlordbrutish Рік тому +2

    Nice! Only thing I would do differently would be to impl From (or maybe even &&str) on Student and move that conversion logic into there.

  • @MartinClausen
    @MartinClausen Рік тому

    Great stuff, would love to see more functional rust videos.

  • @BenHawkins18
    @BenHawkins18 2 роки тому +1

    I would LOVE to see more functional rust!!

  • @epiderpski
    @epiderpski 2 роки тому

    coming from languages that don't use combinators, so this was very helpful. Thanks!

  • @jonathanmoore5619
    @jonathanmoore5619 2 роки тому

    Beautiful. Yep more please.

  • @user-if7lb8zy8i
    @user-if7lb8zy8i 2 роки тому +10

    you didn't say lets get rusty :(

  • @SuperQuwertz
    @SuperQuwertz 5 місяців тому

    Great tutorial! Thank you!

  • @josemanuelberrios7390
    @josemanuelberrios7390 2 роки тому

    Thank you!!
    This is so awesome

  • @joelmontesdeoca6572
    @joelmontesdeoca6572 2 роки тому

    Would love a series in functional programming. 👍🏼👍🏼👋🏼

  • @STatic4009
    @STatic4009 2 роки тому

    Oh... this is how I would do the equivalent list comprehensions in python. Excellent video!

  • @MrPetzold123
    @MrPetzold123 Рік тому +1

    This was nice ! Much harder was when I tried to find the best student using iterators and GPA, because f32 doesn't implement Ord due to floats being weird :-). Edit: and Rust does not allow half-bakedness...

  • @bjugdbjk
    @bjugdbjk 2 роки тому

    Just love it !!
    Do you have any pointers where all these combinators r available for read and trying it out ?

  • @DougFort
    @DougFort 2 роки тому +1

    +1 for functional rust

  • @GlobalYoung7
    @GlobalYoung7 2 роки тому

    Thank you very much 🥳🙌

  • @DipankarMajumder
    @DipankarMajumder 2 роки тому

    Thanks for demonstrating the functional paradigm... this looks much like JS/TS...

  • @Amejonah
    @Amejonah 2 роки тому +3

    3:30 why no if let?

  • @empeaTV
    @empeaTV 2 роки тому

    Pretty good channel Bogdan. Keep it going. Cheatsheet is great as well.

  • @Tferdz
    @Tferdz 2 роки тому +1

    You could use guard clauses to avoid nesting the first part

  • @JTCF
    @JTCF Рік тому

    Didn't know what combinators are, but it turns out I've preferred to use them for a loooong time. Cool.

  • @yogabija
    @yogabija 2 роки тому

    Great video, more videos especially with functional coding techiques :-)

  • @abhikbanerjee3719
    @abhikbanerjee3719 2 роки тому +1

    Which rust VS Code extension are you using in this tutorial?

  • @emoutraspalavras-marloncou4459
    @emoutraspalavras-marloncou4459 2 роки тому +1

    Thanks Bogdan. We could have used for_each here to print out the result, couldn't we? But if I am not mistaking, in this case without storing the value in a variable and collecting the iteration outcome, right?

  • @cron3x
    @cron3x 2 роки тому

    nice Video, i will use this.
    could you make a video about the VSCode Extensions you have? that would be cool

  • @arroba3218
    @arroba3218 2 роки тому

    I'm happy I already knew these concepts coming from Javascript. Generally, I think it's good to filter before mapping.

    • @KohuGaly
      @KohuGaly 2 роки тому +1

      In this case, the filtering can only happen after parsing the string, and there's very little point in parsing the string twice. Technically you could save one allocation (for the name string) per rejected item, but that can also be achieved by mapping twice (first mapping returns (&str,f32) then filter, then second mapping converts &str to owned and creates the struct)

    • @arroba3218
      @arroba3218 2 роки тому

      @@KohuGaly That's a valid point.

  • @EngineerNick
    @EngineerNick 2 роки тому

    yeeeesss the delicious iterators! Thankyou for the great video. If only rust was able to more easily pass uncollected iterators around between functions... Sometimes its possible I think possible but it often causes me trouble. I guess that's the price of compiling stuff and predictable memory layout etc

    • @chinoto1
      @chinoto1 2 роки тому

      Functions can accept and return `impl Iterator` and `Box`, which could resolve your issue without needing to state all the underlying types.
      impl Trait arguments can produce better optimized code through monomorphization, while Box is better if your functions will be accepting a variety of iterator types to avoid bloat in the executable.

  • @flogginga_dead_horse4022
    @flogginga_dead_horse4022 2 роки тому

    Great content

  • @diegof4083
    @diegof4083 2 роки тому +14

    Nice, but flatmap will help you instead of map + flatten

    • @khai96x
      @khai96x 2 роки тому +6

      For Option, I prefer filter_map.

    • @diegof4083
      @diegof4083 2 роки тому +1

      @@khai96x Great

    • @alxjones
      @alxjones 2 роки тому +4

      Both flat_map and filter_map will work, but filter_map has size hints because the result is guaranteed to be bounded above by the input size, while flat_map may increase size without bound in general (such as a nested vec structure). So, they're not functionally identical but practically identical for most purposes. That said, readers from other languages will probably appreciate a distinction being made between the two, as most languages don't have as robust a flat map combinator as Rust does.

  • @josemanuelberrios7390
    @josemanuelberrios7390 2 роки тому

    Please make more videos about combinators!!!

  • @Blure
    @Blure 2 роки тому +1

    Thanks for sharing your knowledge mate. May I ask what's the plugin doing the floating diagnostics and inlay hints for types?

  • @soberhippie
    @soberhippie 2 роки тому

    Great video, nice approach to map-reduce, different to that in Python. On an unrelated note, the way you say "cheat sheet" sounds like it is something Chinese probably spelt "QiXi" )

  • @jackwells9893
    @jackwells9893 2 роки тому

    Great video

  • @SamArmstrong-DrSammyD
    @SamArmstrong-DrSammyD 2 роки тому

    Could you run a reduce operation with unique ids to turn that into a hash map?

  • @emvdl
    @emvdl 2 роки тому

    Thanks 🤙

  • @ShreksSpliff
    @ShreksSpliff 2 роки тому

    I want a language support like rust has but only functions.
    Thanks for doing the better part of the basics.

    • @therealjezzyc6209
      @therealjezzyc6209 2 роки тому

      so haskell?

    • @mrocto329
      @mrocto329 2 роки тому +1

      yeah you're asking for Haskell, it's an amazing language designed to be purely functional.

  • @bitflogger
    @bitflogger 2 роки тому +2

    Great video! If your map function produces a vector with each iteration, how can you combine the returned vectors into one vector?

    • @KohuGaly
      @KohuGaly 2 роки тому +1

      Look at fold and reduce methods. In fact, I recommend you read the entire documentation on interators in standard library, and possibly in the itertools crate. Iterator (and option) combinators are so prevalent in rust, that it definitely pays off having a general overview of their features.

  • @powerclan1910
    @powerclan1910 2 роки тому

    i would like to see you make a larger project if possible at all ;)

  • @bjugdbjk
    @bjugdbjk 2 роки тому

    More of functional programming !!

  • @kovalenkokirill
    @kovalenkokirill 2 роки тому

    great videos, thank you! may i ask something off topic? i somewhat interested in languages and linguistic and have a question. why do you pronounce some ST words such as struct and string as SHtruct and SHtring, while in some other cases such as student, start, stop, you don't make SH (Ш/Щ) sound? what is the origin of this?

  • @marktaylor7162
    @marktaylor7162 2 роки тому

    Yes, more rust FP vids, please.

  • @letcodeitnow
    @letcodeitnow 2 роки тому

    Good content....What's the name of the extension that monitors your exception

  • @doomguy6296
    @doomguy6296 2 роки тому

    Yes. More functional programming.

  • @duke605
    @duke605 3 місяці тому

    Not sure it `filter_map` was a function when this video was created but `filter_map` would do the work of `map`, `flatten`, and `filter` all in one go

  • @usmanmughal5916
    @usmanmughal5916 2 роки тому +1

    Which extension do you use for the highlighting of data types?

  • @FreeTheUyghurs
    @FreeTheUyghurs 2 роки тому

    Being the iterating happens only once collect() is called, does each combinator add an additional iteration over the vec or is it equivalent to one for loop?

    • @KohuGaly
      @KohuGaly 2 роки тому +1

      It's just one loop. Iterators are lazy. They are basically just structs with .next method in them. For example .map method returns a Map struct. The struct has two fields 1. the inner iterator struct, 2. the closure. The .next method basically looks like this:
      fn next(&mut self) {
      if let Some(value) = self.inner_iterator.next() { //calls next on the inner iterator
      Some( (self.closure)(value) ) //maps the value, by passing it to the closure
      } else {
      None
      }
      }

  • @altairbueno5637
    @altairbueno5637 2 роки тому

    I always forget that ? Works on optionals too

  • @beyaz1404
    @beyaz1404 2 роки тому

    Really good presentation. Can you explain error management ?

  • @ixion2001kx76
    @ixion2001kx76 Рік тому

    8:16 no that is not a lot cleaner. That’s a chain of mysteries for future programmers to wrap their heads around.

  • @anindyasundarmanna6683
    @anindyasundarmanna6683 Рік тому

    I have to say, the point of readability feels biased. The later has advantages like being concise, bit more performant maybe. But is it really MORE readable and easy to understand, specially to a beginner?? I'm not so sure. Great video as always! :)

  • @jeanehu-pludain6640
    @jeanehu-pludain6640 2 роки тому

    but which speedess ?

  • @SyamaMishra
    @SyamaMishra 2 роки тому

    Is using unwrap after is_ok like this considered idiomatic rust or a code smell?

    • @piaIy
      @piaIy 2 роки тому +2

      I'm new to Rust but I think using if let would be the idiomatic way in this case.

    • @scotty3024
      @scotty3024 2 роки тому +3

      It's code smell, you are better off using a match statement in which the None or Result variant returns from the scope, ( the ? operator that he used does exactly that but shorter )

  • @Yotanido
    @Yotanido 2 роки тому

    I'm not a big fan of the if is_some and unwrap pattern. With later refactorings, you might accidentally remove that check and then end up panicking. Much better to use if let instead.

  • @xrafter
    @xrafter 2 роки тому

    What's a combonator?

  • @alexandersemionov5790
    @alexandersemionov5790 2 роки тому

    That’s some JavaScript stuff I like but in rust. TrustyScript :)

    • @31redorange08
      @31redorange08 2 роки тому

      That's not JS stuff. JS doesn't have an equivalent in its standard library.

  • @jobosan4855
    @jobosan4855 2 роки тому +2

    FP > OOP

  • @scotty3024
    @scotty3024 2 роки тому +3

    To everyone here saying he should have used filter_map. You forget about the separation of concerns that he's trying to make, the map is converting the iter, then the filter is another concern that does another thing.

  • @madhavan_raja
    @madhavan_raja 2 роки тому

    6:14 why did you use .ok()? Instead of just the ?

    • @gt3293
      @gt3293 2 роки тому +1

      Because the ? operator on a result requires the containing function to return a result, I believe. Ok() converts it into an option, which is what this function returns.

    • @madhavan_raja
      @madhavan_raja 2 роки тому

      @@gt3293 According to the docs, parse() returns a Result. Why do we need to convert it into an option though? Aren't we getting a Result here already? Apologies if I am missing/misunderstanding something.

    • @gt3293
      @gt3293 2 роки тому

      @@madhavan_raja The issue is that we can't use the Result. We need an Option (since that is what that closure returns). The .ok() method converts it so that we don't get a type error.

    • @madhavan_raja
      @madhavan_raja 2 роки тому +1

      @@gt3293 Ah I see. Totally forgot to consider the fact that it was inside a closure. Thank you!

  • @ClearerThanMud
    @ClearerThanMud 2 роки тому

    LOL -- The captions around @7:20 have Bogdan say "all the nun variants." I'm not Catholic; how many kinds of nuns are there?

  • @MichaelZimmermann
    @MichaelZimmermann 2 роки тому +1

    My experience is that most of the time these are superior in readability but that you shouldn't try to force it since they can start looking insane if you overdo it 😁

  • @Codeaholic1
    @Codeaholic1 2 роки тому

    filter_map!

  • @max_ishere
    @max_ishere Рік тому

    3:08 `cargo clippy`:
    Unnecessary unwrap, use if let Some(_) = name

  • @edelsmurf
    @edelsmurf Рік тому

    Useful content, but, no, the combinator variation doesn’t seem more readable to me.

  • @dangducthangg1013
    @dangducthangg1013 2 роки тому

    hacked host info

  • @kevinbatdorf
    @kevinbatdorf 2 роки тому

    imperative vs declarative programming.

  • @porky1118
    @porky1118 2 роки тому

    Seeing this code is pain :P

  • @flogginga_dead_horse4022
    @flogginga_dead_horse4022 2 роки тому

    Be nicer to Wallace!!! lol

  • @_Redu
    @_Redu Рік тому

    Use of this example for the purpose of demonstrating the combinators is fine however in this particular case your approach is naive and inefficient. You don't need to map the ones with GPA < 3.5. It would be best done with a single pass of `fold` instead of `map` and `filter`.

  • @JayGodseOnPlus
    @JayGodseOnPlus 2 роки тому

    In Ruby it would be something like :
    students.map{|s| s.split(' ')}.map{|t| {student: t[0], gpa: t[1].to_f}}.select{|u| u[gpa] >=3.5}
    I like that Rust's syntax for closures lends itself for easy chaining of combinators, just like Ruby's syntax does. (I believe that one of the core committers of Rust is a Ruby developer too). This is one reason I prefer Rust or Ruby over Go and Python.

    • @KohuGaly
      @KohuGaly 2 роки тому +1

      I was also very pleasantly surprised by the iterator combinators and their similarity to Ruby. Honestly, I did not know it was possible to have them in such a low-level language. Especially with zero overhead.