-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Finalize defaulted type parameters #213
Conversation
} | ||
|
||
Using this definition, a call like `range(0, 10)` is perfectly legal. | ||
If it turns out that the type argument is not other constraint, `uint` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/is not other constraint/is not otherwise constrained/
6357402
to
e0acdf4
Compare
My only concern here is about whether this feature carries its weight. Having defaults drive inference is a nice touch! But overall, it's a not insignificant amount of complexity, likely to be in conflict (or at the very least tension) with bigger and more important type system features in the future, and the benefit is not that large. The goal of backwards compatibly extending types can be accomplished with just modules and typedefs, if I'm not missing something. As merely a strawman example: Before:
After:
Existing code should keep working. Code which wants to specify a different hasher has to be modified either way, and in the course of that modification, the only additional change that has to be made would be to import The conflict with HKT we've discussed before, but there was never any satisfactory resolution proposed that I can remember. To be able to implement most of the useful HKT traits for a generic collection type, the type parameter representing the contained type needs to be the last one. For example, one would write:
If we were to add HKT later on, and then discover that we can implement (As a side note, for forwards compatibility with HKT, we should also change the order of the type parameters of |
@glaebhoerl We can implement HKT without relying on currying, there is no need to give anyone the wrong impression about this. Defaulted type params/VG and HKT are orthogonal (and I would like to see Haskell handling |
On Sat, Sep 13, 2014 at 03:54:22PM -0700, Gábor Lehel wrote:
I don't see a conflict with HKT, though clearly there is a conflict |
On Sat, Sep 13, 2014 at 03:54:22PM -0700, Gábor Lehel wrote:
Sorry, I didn't address this point specifically. First, let me point |
👍 I've been wanting defaults to work with inference for a while. A number of APIs that I have been working on are currently required to expose generics to the user that they often shouldn't care about. Being able to set defaults and having the inferencer use that would make these APIs much nicer. |
Just discovered this RFC from a hint on IRC. I have an API with a function like this: fn execute<T: FromSomething>(&self) -> Result<FromSomething, Error> {
FromSomething::value_from_something(...)
} The problem comes up in cases where let _: () = execute(...).unwrap() Would it be feasible to default |
Actually i presume I could just define the function like this to achieve what I want: fn execute<T: FromSomething=()>(&self) -> Result<FromSomething, Error> {
FromSomething::value_from_something(...)
} |
On Sat, Sep 27, 2014 at 06:34:24AM -0700, Armin Ronacher wrote:
Yes, this. Though I think the result type would be |
@nikomatsakis Sorry for the late response.
The direct conflict is indeed only syntactical as far as I can tell. As I've written before I wouldn't mind requiring an explicit syntax such as (As an aside, I don't really have any conception of what is "Rust-y" in this space, other than whatever works the best. We're not constrained the way we sometimes are at the value level.)
Newtype wrappers would of course work, but then we'd be paying back the convenience we got from default type arguments, most likely with interest. The problem with type lambdas is that they mess up type inference: see this comment by rwbarton. They also figure prominently in Edward Kmett's criticism of Scala.1 In short, they don't play well with other features and do not appear to have much of a countervailing benefit. I would rather have fewer features which work well together, than more features which are always stepping on each others' toes and negating each others' benefits. In particular, if we are to have HKTs, we should have HKTs with useful inference which makes them a practical tool that's pleasant to use, rather than HKTs as window dressing. (And even if we were to decide that we want type lambdas, that's a big decision which should be made on its own merits; getting backed into it by default type arguments would be the tail wagging the elephant.) (@eddyb: Sorry. I don't like being in the position of pushing back on someone else's pet feature, I've been on the other end and know that it sucks. But that's life. People have opinions, and they're often different ones.) 1 In case anyone's not familiar with Edward Kmett: Among many other things, he's the mastermind behind Haskell's lens library as well one of the principal contributors to scalaz, co-wrote a state-of-the-art compiler for an advanced Haskell-like language first in Scala and then in Haskell, and is quite possibly the most brilliant and prolific Haskeller on the planet. (Arguably for either taken alone; without a doubt for the combination.) I am inclined to take his opinions very seriously. |
@glaebhoerl I have no opinion on type lambdas as a Rust feature, but those arguments leave me unconvinced. Rust bans overlapping impls of a trait in the first place, so the same problem doesn't come up. The inability to define overlapping impls in Rust might make type lambdas less useful; I'm not sure. And even with overlapping impls, there is a good reply from winterkoininkje that went unanswered. |
Haskell doesn't allow overlap either, without some GHC extensions which are highly discouraged. I don't believe rwbarton was assuming them in his comment. As far as the basic issue is concerned, type classes don't even enter into it. You have a type (going with more rustic notation) And in any case... (from winterkoninkje's comment)
"Not known to be impossible" is not exactly a vote of confidence as far as adding things to Rust goes. |
@glaebhoerl The concerns raised by rwbarton are specifically framed in the context of choosing between multiple type class instances for a single type, which requires OverlappingInstances to be an issue in the first place. The related issue for Rust would be that impls are only allowed in modules that define either the type or the trait, so as it stands you would only be able to make impls for type lambdas in modules that define the trait, rather than those that define the type lambda. As for the more basic issue, what would be lost by just never inferring a (type-)polymorphic type? Rust already doesn't infer polymorphism when it could, e.g. this program fails to type check: fn main() {
let f = |a| a;
let _x = f(0u);
let _y = f(0i);
} Why would we expect polymorphism to be introduced at the type level when we don't introduce it at the value level? Languages that use Hindley-Milner inference and perform polymorphic generalization have this problem, but Rust doesn't. |
No, I don't believe that to be the case. The issue is confused by the fact that the prospect of type class instances for type lambdas was the suggestion being responded to, and so that's what the response also concerned itself with. But the two are logically separate. (Type inference in Haskell also precedes instance resolution.) Let's consider another example:
What's
I don't see why it makes any sense to tangle these things together. You can have a language with higher-kinded type variables but no first-class polymorphism, and you could also have the reverse. First-class polymorphism is difficult to reconcile with Rust's compilation model, but even C++ has higher-kinded types! (Haskell 98 is also closer to that extreme, apart from a couple of things like let generalization and polymorphic recursion.) |
I was suggesting that the type checker never introduce polymorphism on the user's behalf. What's wrong with that? It would exclude all but the first choice.
The example I gave isn't first-class polymorphism; it is plain old boring prenex polymorphism. First-class polymorphism (albeit only rank-2) would be an example like this: fn main() {
let f = |g| {
g(0u);
g(0i);
};
let g = |a| a;
f(g);
} which also doesn't work in Rust. |
Ah, okay. I've thought of that as well. It seems like a reasonable idea, I don't know if anyone's done it before. But as far as using type lambdas to bridge the gap between default type parameters and HKTs is concerned, it doesn't help at all. If the presence of default type parameters on common types forces impls of HKT traits for those types to be written for type lambdas, and type lamdbas are never inferred, then you're back in the same place, which is that inference doesn't work when you want it to.
Yes, I was imprecise, sorry. They are connected in that whether, when, and how to infer polymorphism is also the big issue in systems with first-class polymorphism, and let generalization is an instance of inferring polymorphism (even if first-rank), so I feel that these things are along a spectrum. |
From what I read on the Internet, it appears to be what Scala does.
The problem you posed above with struct Pair<A>(A, A)
struct WrapInt<W> { wrapped: W<int> }
trait Confused { fn bar(&self) }
impl<type<type> W> Confused for WrapInt<W<Pair>> { ... }
let foo = WrapInt { wrapped: Pair(Pair(0i, 0i), Pair(0i, 0i)) };
foo.bar(); What should This combined with the fact that checking for instance overlap seemingly requires higher-order unification is probably a sign that type lambdas are not compatible with language features that attempt to infer terms from types with any sort of coherence. |
@aturon and I had a pretty detailed discussion about default type On inferenceThe monad exampleLet me begin by translating the example that @glaebhoerl pointed out into theoretical Rust syntax:
Here we wind up with a (higher-kinded) type variable There is also a trait obligation that This implies that to write an example like that in Rust, presuming we don't have curried partial application, would require a type annotation. For example, using UFCS, one could write A struct exampleHowever, let's poke a bit further into other examples. Here the idea is to drill into things we actually expect to use HKT for in Rust. One such thing would be the ability to reason independently about pointer types. For example, I might want to write:
Now I would like to write:
and I would like Rust to infer that the type of There is however an interesting compromise. We might restrict type lambadas to always be a partially applied type, though not necessarily a curried one. In other words, a value of kind Note that we still cannot infer the In a more complex case, we might therefore require annotations:
Problems with curried partial type applicationAt some point I made a statement that curried partial type application a la Haskell is not very "Rusty". What I meant mostly is that we don't do currying in general (i.e., not for ordinary functions), so it seems Based on the syntax alone, Other anticipated uses for HKTThe other major use for HKT that we anticipate is on associated types. For example, a trait Iterable {
type Elem;
type Iterator<'a>;
fn iter<'a>(&'a self) -> Iterator<'a>;
} In cases like these, the limitations on inference don't apply at all, because we are propagating forward rather than backward (that is, we don't have to deduce the function from its output, as in the other examples). ConclusionIn conclusion, it seems like default type parameters have a lot to offer in terms of convenience and have proven very useful. They do interact poorly with curried partial type application a la Haskell. However, curried partial type application is a poor fit for the Rust due to the kind of |
Is this something that will make it into 1.0? |
@mitsuhiko I would expect this to happen before 1.0 final, but not necessarily for the alpha in two weeks. |
…ddyb Currently, we only infer the kind of a closure based on the expected type or explicit annotation. If neither applies, we currently report an error. This pull request changes that case to defer the decision until we are able to analyze the actions of the closure: closures which mutate their environment require `FnMut`, closures which move out of their environment require `FnOnce`. This PR is not the end of the story: - It does not remove the explicit annotations nor disregard them. The latter is the logical next step to removing them (we'll need a snapshot before we can do anything anyhow). Disregarding explicit annotations might expose more bugs since right now all closures in libstd/rustc use explicit annotations or the expected type, so this inference never kicks in. - The interaction with instantiating type parameter fallbacks leaves something to be desired. This is mostly just saying that the algorithm from rust-lang/rfcs#213 needs to be implemented, which is a separate bug. There are some semi-subtle interactions though because not knowing whether a closure is `Fn` vs `FnMut` prevents us from resolving obligations like `F : FnMut(...)`, which can in turn prevent unification of some type parameters, which might (in turn) lead to undesired fallback. We can improve this situation however -- even if we don't know whether (or just how) `F : FnMut(..)` holds or not for some closure type `F`, we can still perform unification since we *do* know the argument and return types. Once kind inference is done, we can complete the `F : FnMut(..)` analysis -- which might yield an error if (e.g.) the `F` moves out of its environment. r? @nick29581
After some fairly extensive discussion on this RFC, the core team is convinced that this feature will pose no serious problems for type inference around a future HKT extension. See this comment for details. Other than those concerns, this feature is a frequently-requested one, and a natural extension given integer fallback. I have merged the RFC; the tracking issue is here. |
Custom Components API
This RFC proposes finalizing the design of defaulted type parameters with two changes:
_
to explicitly use a defaultRendered view.