8 ms·
I'm filing this one along with Monads and Haskell as programming concepts that I'll never understand. Rust is my favorite language, but that explanation, lauded
by the__alchemist 4y ago
I'm filing this one along with Monads and Haskell as programming concepts that I'll never understand. Rust is my favorite language, but that explanation, lauded directly below it as This is personally the easiest to understand example I've seen of GATs., was incomprehensible.
- rufusroflpunch 4y agoSame here. I presume that one day I will have to write something using a GAT, and then the understanding will click with me, even if I don't speak the terminology.
- kibwen 4y agoBoth generics and traits are mostly for library authors trying to make expressive APIs. If you're not creating libraries but instead consuming libraries, then it may be natural for you to never need to reach for this feature.
- secondcoming 4y agoMany C++ developers have walked that path, never to be seen again.
- kitd 4y agoYeah. I'd like to meet the 5-year-old that understood that. edit: the "What are GATs" paragraph in the link posted by @leetrout is much more usable.
- nicoburns 4y agoI think the easiest way to understand it is this: Suppose you have a concrete type like Vec<i32> Regular generics enable you to make the contained type generic, so you have: Vec<T> where T can be i32, u32, String, etc. GATs allow you to make the container generic. So you can have: T<i32> where T might be Vec, Option, Box, etc.
- aarobot 4y agoAwesome example.
- fallat 4y agoTHIS is literally the best explanation. My god people have forgotten how to explain anything anymore. Thank you.
- agluszak 4y agoIt is easy to understand because it is an oversimplification :)
- jstx1 4y agoWhich part is inaccurate/incomplete?
- GolDDranks 4y agoThe part that says `T<i32>`. GATs don't allow you to do that, not directly. GATs allow you, as said by Boats in https://news.ycombinator.com/item?id=33506540 https://news.ycombinator.com/item?id=33506540, have a generic type as an associated type, nothing less, nothing more. If you want to express `T<i32>`, GATs _enables_ you to do that as a kind of a distorted encoding, but it's not as simple as https://news.ycombinator.com/item?id=33505810 https://news.ycombinator.com/item?id=33505810 makes it seem.
- howinteresting 4y agoBring able to abstract over Rc/RefCell and Arc/Mutex with marker types seems pretty useful. It's not directly HKT but it enables use cases that would otherwise be supported by HKT.
- dllthomas 4y agoYes, but that doesn't make it somehow accurate to explain HKT and pretend you're explaining GAT...
- kibwen 4y agoYou mention that you use Rust. In that case, you're almost certainly familiar with generics, and you're likely familiar with associated types. If you've ever found yourself struggling to write an associated type that needed a generic parameter, this feature is for you. And if you haven't ever needed to do such a thing, that's totally fine, and you can happily ignore it and content yourself with benefiting from libraries that are now able to expose simpler and more powerful APIs by leveraging this feature under the hood.
- the__alchemist 4y agoI appreciate the explanation. I've been more of a firmware or application developer in Rust than library developer, so will keep this in the hip pocket.
- jackmott42 4y agoYeah basically if the day comes when you need these, you will just naturally use them and it will work and you will never think anything of it. Whereas I stumbled upon a use for these ~4 years ago and was just mad that what I naturally wanted to do didn't work =) But yeah in certain programming domains, probably most, it just doesn't come up.
- mcronce 4y agoYeah - it's almost better to think of GATs as less of a feature and more of a weird restriction being removed. That's obviously super reductive knowing the amount of work that went into it, but from an end developer standpoint, it's an explanation that helped make it make sense to me.
- KallDrexx 4y agoI've yet to figure out why to use associated types instead of generic parameters, and every time I've seen an explanation I nod my head in confusion and carry on.
- 4y ago
- atemerev 4y agoI can program in Scala and Haskell relatively easily. I still struggle with Rust's lifetime abstractions and things like this: impl<I, F> LendingIterator for Map<I, F> where I: LendingIterator, F: for<'a> Mapper<'a, <I::Item as GivesItem<'a>>::Item>, { type Item = dyn for<'this> GivesItem< 'this, Item = <F as Mapper<'this, <I::Item as GivesItem<'this>>::Item>>::Output, >; fn next(&mut self) -> Option<<Self::Item as GivesItem<'_>>::Item> { self.iter.next().map(&mut self.mapper) } } Haskell is so much easier.
- insanitybit 4y agoI feel you, this is some particularly rough Rust code, combining quite a lot of stuff that I don't think I directly interacted with for my first few years. You have `for<'a>`, which I believe is probably the most complicated part of the language. That's a Higher Ranked Trait Bound, https://doc.rust-lang.org/nomicon/hrtb.html https://doc.rust-lang.org/nomicon/hrtb.html It just means "for all possible lifetimes 'a" as opposed to a single specific instance of a lifetime. Honestly, my brain kinda knows when to use it and I've never bothered to push past that point and into "and I understand why it knows when to use it". You have nested types, which always makes things a bit messy, but then you have the "I have a type, it implements a trait, and I want to refer to the associated type for that trait's implementation for that type". ie: <MyType as ATraitItImpls>::Associated Oof. But once you know what it is it's quite clear and explicit. You wouldn't want to do `MyType::Associated` because what if MyType implements multiple traits that have associated values named Associated? Rust prefers explicit. I actually use this all the time now because it makes refactoring so easy to only refer to types through generic paths. If I change "Associated" I don't have to update anything at all. When you nest these it gets extra messy, but it's just the same thing. Mapper's second generic parameter is just the associated type of some other thingy. I wonder if some type Aliasing would help, idk. Then there's `'_`, which I've never bothered to use because I learned rust before it was a thing and so I just don't really care. The irony is that `'_` exists to make things clearer - it's just an annotation that says "there's a lifetime here that we don't actually have to write down, but I'm writing it so that you know what it is and that it's not some other lifetime". https://dev-doc.rust-lang.org/beta/edition-guide/rust-2018/ownership-and-lifetimes/the-anonymous-lifetime.html https://dev-doc.rust-lang.org/beta/edition-guide/rust-2018/o... So yeah, I get it. Rust is very explicit, and when you have a lot of nested stuff like this that explicitness can look a bit overwhelming. But I'll say that, knowing all of this, I can look at that code and trivially parse it because there's no ambiguity whatsoever.
- Hamuko 4y agoI still haven't fully figured out how lifetimes work.
- withoutboats2 4y agoI hope you don't. To establish my credentials for this comment - GATs were my idea. The entire point of GATs was to carve out a design space that solved peoples' problems without being so high-minded as monads and HKT and so on. Unfortunately, a lot of people who like monads like to talk about GATs in the same way. But its really as simple as this: when you add an associated type to a trait, you can make that type generic, the same way you can make a type alias generic when its not in a trait. The whole point of specifying GATs and not a more general "HKT" is that it's an obvious extension of the existing language. There are lots of useful traits that you can't define if you can't make an associated type generic, so people are excited to have this feature. It turned out that implementing this in rustc took a very long time, because refactoring the typechecker of rustc to support this was challenging. But that doesn't mean the feature itself is complicated or difficult to use. The whole point of GATs is that it shouldn't really feel like a feature once it's done: naturally, if you are writing a trait with a method that returns `Self::Foo`, but you need it to be `Self::Foo<T>` or `Self::Foo<'a>`, you can just do that, rather than the compiler telling you that it's not supported.
- flippinburgers 4y agoNot trying to make you feel like you have failed, but whenever I encounter language features on this level - aka not comprehensible for a mortal like myself - I do tend to want to shamefully turn away from this profession entirely. The good news is that I am most likely a total dunce. I wish I could become reasonable with rust because I do like the concepts that I think I grasp.
- sowbug 4y agoWhen I find a language feature I don't understand even after reading what's out there, I ignore it. Then I plod along with my own code. Eventually I'll notice I've been writing the same boilerplate code again and again, and I'll finally see the use for the feature. At that point it's less a matter of "understanding" in a grand intellectual sense, and more just fitting my needs to the syntax. After a few such instances, I know the feature well enough to unblock someone else who doesn't get it. That's a good practical test of understanding. TL;DR: don't worry about it. When you need it, you'll learn it.
- Ygg2 4y agoWait. Why are Monads complicated? Yes the math definition is complicated, but so is math definition of number 1 in set theory You have a Rust-like enum that contains data like Option or Maybe, you want to abstract over it. There you have a rudimentary Monad.
- _448 4y ago> you want to abstract over it. What does that mean? Any example you could point to?
- howinteresting 4y agoAre you familiar with flat_map? It's the ability to take, for example, an iterator, then map over each element which itself can return an iterator. Similarly, you have Option::and_then, which takes a callback that takes T and returns an Option<T>. Or Result::and_then. "Monad" is simply the umbrella term for all flat_maps, and things like it, in existence. Why would you want to abstract over it? For one, it's a surprisingly common design pattern in programming. You also quickly notice, for example, that filter_map is actually just a special case of flat_map since Option implements Iterator. You can also study the properties of how such patterns behave. Monads are particularly interesting for a number of reasons. Monads are a kind of "most general" and least constrained form of sequencing, and it turns out that if your large-scale system is monad-like it can have a significant performance impact! You don't need to have a thing called "Monad" in your type system to do any of the above. Having a thing called "Monad" in your type system does let you write code that abstracts over all flat_maps (and things like it) in existence, which has been used in the Haskell world to build a number of interesting libraries. But "monads" as a concept are useful to everyone in programming.
- howinteresting 4y agoBy the way, as a simple example of what I mean when I say that monads are the least constrained form of sequencing: flat_map is strictly more general than map. In Rust, if an iterator I implements ExactSizeIterator, then Map<I, F> implements ExactSizeIterator: https://doc.rust-lang.org/std/iter/struct.Map.html#impl-ExactSizeIterator-for-Map%3CI,%20F%3E https://doc.rust-lang.org/std/iter/struct.Map.html#impl-Exac... However, there is no such implementation for FlatMap<I, F>: https://doc.rust-lang.org/std/iter/struct.FlatMap.html https://doc.rust-lang.org/std/iter/struct.FlatMap.html This intuitively makes sense! If a single input can generate zero, one or more than one outputs, then there's no way to know the exact size of it. The simple map is more constrained -- it has to generate exactly one value -- which means that it can support more operations. The monad-like flat_map is less constrained, and it can do more, which means that it can't support as many operations. There's a catchphrase in the programming language community to describe this sort of thing: "constraints liberate, liberties constrain". This can make a huge difference in production systems. For example, if you have a distributed system that walks over an execution graph, and nodes in the graph can create new nodes (flat_map/monad-like), the properties of your system are very different from if nodes can't do that.
- kibwen 4y agoAs was noted at the beginning of the referenced explanation: "I would offer a bit [of a] non-trivial example" Which is to say, this comment is deliberately trying to go above and beyond the ELI5 request from the OP. You may find the answer below that one a bit easier to understand: https://old.reddit.com/r/rust/comments/ynvm8a/could_someone_explain_the_gats_like_i_was_5/ivayo42/ https://old.reddit.com/r/rust/comments/ynvm8a/could_someone_...