4 ms·
I agree that Rust should have embraced HKTs instead of resisting them every step of the way. It's been my observation that the Rust lang team is pretty much aga
by ragnese 6y ago
I agree that Rust should have embraced HKTs instead of resisting them every step of the way. It's been my observation that the Rust lang team is pretty much against HKTs as a general feature, which makes me sad.
I'm also happy to meet the only other person who had reservations about NLL! :p I have mixed feelings about it, still. It really is super ergonomic and convenient, but I really value language simplicity and NLL makes the language more complex.
I also don't think there's much Rust can do differently in regards to "functional foundations". With Rust's borrow/lifetime model, what would you even do differently with respect to `fn`, `Fn`, `FnMut`, and `FnOnce`?
- steveklabnik 6y agoHKTs were considered, and do not actually solve the problem, in Rust. Rust is not Haskell. The differences matter. Nobody is against HKTs on some sort of conceptual level. They just literally do not work to solve this problem in Rust.
- ragnese 6y agoWhich problem are we referring to? I was only making a general statement that I think Rust would have benefited from HKTs instead of doing umpteen ad-hoc implementations of specific higher-kinded types. I'm far from an expert, so please correct me if I'm wrong: Wouldn't HKTs help us abstract over function/types more easily, including closures? Aren't GATs a special case of HKTs? If Rust somehow had HKTs and "real" monads, we could have a do-notation instead of the Try trait+operator and Future trait+async+await, right? I'm not saying that HKTs would fix (most of) the issues mentioned in the article around tricky closure semantics and whatnot.
- steveklabnik 6y ago> Wouldn't HKTs help us abstract over function/types more easily, including closures? In the sense that HKTs are a higher level abstraction, sure. More later. > Aren't GATs a special case of HKTs? My understanding is that GATs can get similar things done to HKTs for some stuff that Rust cares about, but that doesn't give them a subtyping relationship. Haskell has both higher kinded types and type families. That being said, my copy of Pierce is gathering dust. > If Rust somehow had HKTs and "real" monads, we could have a do-notation instead of the Try trait+operator and Future trait+async+await, right? It depends on what you mean by "somehow." That is, even if Rust had a monad trait, that does not mean that Try and Future could both implement it. This is because, in Haskell, these things have the same signatures. In Rust, they do not have the same signature. For reference: pub trait Iterator { type Item; pub fn next(&mut self) -> Option<Self::Item>; } pub trait Future { type Output; pub fn poll( self: Pin<&mut Self>, cx: &mut Context<'_> ) -> Poll<Self::Output>; } While they are both traits, both with something returning their associated type: 1. Iterator returns Option, while Future returns something like it. These do have the same shape in the end though, so maybe this is surmountable. (Though then you have backwards compat issues) 2. poll takes a Pin'd mutable reference to self, whereas iterator does not 3. Future takes an extra argument These are real, practical problems that would need to be sorted, and it's not clear how, or even if it's possible to, sort them. Yes, if you handwave "they're sort of the same thing at a high enough level of abstraction!", sure, in theory, this could be done. But it is very unclear how, or if it is even possible to, get there.
- lmm 6y ago> That is, even if Rust had a monad trait, that does not mean that Try and Future could both implement it. This is because, in Haskell, these things have the same signatures. In Rust, they do not have the same signature. For reference: This is nonsense; Try and Future are not the same thing in Haskell, there are plenty of functions you can only call with one or the other. The point of the monad abstraction is to abstract over the part that is the same, mostly the function which Rust calls and_then: pub fn and_then<F, B>(self, f: F) -> AndThen<Self, B, F> where F: FnOnce(Self::Item) -> B, B: IntoFuture<Error = Self::Error>, Self: Sized, pub fn and_then<U, F>(self, op: F) -> Result<U, E> where F: FnOnce(T) -> Result<U, E>, Obviously these signatures aren't quite identical, but they're actually even more similar than I thought; AndThen<Self, B, F> is a subtype of `impl Future<B, Self::Error>`, and the fact that there's no IntoResult seems like plumbing rather than anything fundamental. So if we could write an interface like: trait Monad<M<*>> { pub fn and_then<A, B>(self: M<A>, f: F) -> impl M<B> where F: FnOnce(A) -> M<B> } then these both conform to that - in the first case with M=Future<Error=self::E> and A=Self::Item, in the second case with M=Result<Err=E> and A=T. Yes, there are low-level things you might want to do with Future or Result that you can't do via the monad interface - just as with any other high-level interface. But having the high-level interface available makes the simple, common cases a lot easier. I don't know what these "real, practical problems are", but they're certainly not at the syntactic/interface level.
- steveklabnik 6y agoSorry, you're right! Ironically, I remembered the high level problem, but filled in the wrong concrete details. I also made the mistake of talking about Try/Future, and then switched to Iterator/Future. > is a subtype of Rust does not have subtyping. (except for lifetimes) The point is, we don't actually have an existence proof that this is possible, and a lot of evidence to the contrary. It may be possible, but it's not a simple "just do x." There is a lot of handwaving in this post that would need to be hammered out, and details that would need to be fixed. (Another point here is that "impl Trait in traits" isn't currently implemented in Rust either; this one is more feasible, though I am less sure about it when parameterized.)