-
Notifications
You must be signed in to change notification settings - Fork 40
Tuple projections #57
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
base: master
Are you sure you want to change the base?
Conversation
rfcs/tuple_projections.md
Outdated
| let y = x.tuple_label_a in | ||
| ignore (x : (tuple_label_a:int * string * bool)); | ||
| (y, x.2) | ||
| Error: The value `x` has type `foo` but an expression was expected of type |
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.
Did you mean discombulating_record instead of foo ?
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.
Fixed in latest version
| Unlabeled tuple projections can naturally (and efficiently) be typed using row polymorphism, in | ||
| the same way object fields are typed today: | ||
| ```ocaml | ||
| # let snd x = x.2;; |
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.
Here we might want to have the same semantic as with
let get_y t =
let ~y, .. = t in
ywhich is rejected if I remember correctly.
We would also need to check that the back-end supports such polymorphism (I remember talking about a usefull feature similar to this one for modules with @lthls)
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.
Yes, this is rejected currently since the expected type of the pattern (i.e. the type of t) is not known
| Check to see whether the expected type is known (principally known, if in `-principal` mode). | ||
| Then: | ||
| * If the type is not known: raise an error stating that the projection is ambiguous. | ||
| * If the type is known to be `(?l0:ty0 * ... * tyj * ... * ?ln:tyn)`: type the projection as `tyj` |
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.
What happens in the case :
let x = ~a:1, ~b:2 in x.1Is the example rejected because only x.a and x.b are accepted and not x.0/x.1 ?
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.
This would be rejected, as you correctly surmise, the only well-typed projections for x would be x.a, x.b.
This is consistent with the current pattern matching semantics i.e. the following is ill-typed:
# let x = ~a:1, ~b:2 in
let _, x1 = x in
x1;;
Error: The value x has type a:int * b:int
but an expression was expected of type 'a * 'b
The first tuple element is labeled a,
but an unlabeled element was expected|
As a whole I'm strongly in favor of this feature because I consider that it would increase the quality of life when handling tuples. I also find the part about row-polymorphic tuples to be extremely interesting as it actually increases the expressiveness of OCaml's polymorphism. However I suspect this would require tweaking a few things in the back-end but hopefully not much. |
68585b9 to
0443ca7
Compare
|
My personal impression is that row-polymorphic tuples are the most interesting part of the proposal, which is still useful independently of the rest of proposal and interact quite strongly with the design of projections. Thus I have the impression that it could make sense to focus on that part first, possibly in a separate RFC?. |
|
The discussion of row-polymorphic tuples in this RFC is meant only as a consideration of an alternative typing approach for tuple projections, originally suggested in ocaml/ocaml#14257 (comment). This RFC does not propose introducing row-polymorphic tuples for unlabelled tuple projections; it discusses them solely to ensure that this RFC's proposed type-based disambiguation approach to typing projections remains compatible with that possible future direction. If there is a consensus among maintainers that row-polymorphic tuples are desirable, I agree that they should be explored in a separate RFC. However, they are not part of the present proposal and remain outside the scope of this RFC. |
|
Hey 👋 Following up to see if there's anything I need to do on my side. If not, what are the next steps to move this RFC forward? |
|
We had a meeting of OCaml maintainers today, and we discussed your RFC. The discussion was lively and rather subtle, but we reached the following consensus (not everyone agrees with everything below, but we felt that there was a general agreement to follow this proposal):
(I tried to call tuples like This consensus is in fact remarkably similar to the opinion expressed by @garrigue on the PR, but it has the benefit of coming with a set of design parameters for which we collectively agree to add the feature to the language, if you are willing to implement it. |
|
Thanks for the update! 🙏 A few thoughts from my side:
If nothing in my response is able to sway the maintainers, then I will accept that we have consensus around labelled tuple projections and adjust my implementation to support that feature only. Footnotes |
|
My understanding of the majority opinion that (numeric) tuple projections should not require type information is as follows: in everyone's mental model of OCaml untyped semantics, tuple access In contrast, mental models of untyped semantics for labelled tuples differ. If you view them as associative lists, then the same argument would apply (but in that model tuple access has a linear access time...). But many people see labelled tuples as represented by non-labelled tuples at runtime (in their mental untyped semantics), with an ordering between labels decided at type-checking time (in other words, The fact that a significant portion of maintainers have this view (that unlabelled tuple access should be treated polymorphically, and that failing in type-undetermined setting is surprising and counter-intuitive) suggests that it would also be widespread among users. I think it would be reasonable for you to work on labelled tuple projections only. Note that while people were, in general, enthusiastic about the idea of having flexible tuples and numeric tuple indexing, there was less of an enthusiasm about labelled projections only (I asked during the meeting whether people are convinced that it will make a difference in practice in how we write programs, and in general people were not convinced that it would). My sense is that people are okay with that feature in isolation, but they don't consider it very important. |
I'm not convinced that the untyped-semantics intuition is the right basis for deciding what the type checker should accept. If it were, then When we consider a typed language, the complexity of inference (both in terms of time/space and engineering effort, and in some cases decidability) becomes part of the design space. As such the "it works in the untyped mental model" doesn't, by itself, carry much weight as an argument for typability.
I agree with you here: labelled tuples are nothing but a type-directed compilation strategy for unlabelled tuples.
I think that the inductive step here is ill-founded. To my knowledge (and correct me if I'm wrong), no mainstream functional programming language implements tuple polymorphism. SML has structural records, but failed to typecheck Ultimately, my hesitancy around tuple polymorphism stems from complexity: implementing tuple polymorphism looks like a fairly large and relatively invasive to an already complex type checker. And for what is, in practice, a modest quality-of-life improvement; I'm not convinced that this trade-off is worth it. By contrast, the current change to the type checker is self-contained and relatively maintainable. The main practical downside (it's reliance on known type information) is something we could address more directly (with benefit to other more well-used features). |
|
You of course should not feel pressured to implement flexible tuples; but the consensus decision is that we will not add unlabeled/numeric tuple projections without them. |
|
I don't have a strong opinion about tuple projections, but I thought it might be useful to explain why we didn't do something like tuple polymorphism in the initial labeled tuples implementation. Mainly, the reason is that I don't see how to implement it well. I'll expand: Let's consider What does this function do at runtime? In particular, how does it locate the field it wants to project in the block? I think there are three possible answers. The first is that there is some kind of dynamic lookup to map labels and unlabeled indexes to dynamic positions in the block. At that point tuples are basically objects, and I think I would be sad to lose the cheaper anonymous product representation. The second is that there must be a canonical way to locate the second unlabeled field. Maybe the labeled fields come after all the unlabeled fields? But how do we then compile projection of a labeled field - how do we know how many unlabeled fields come before it? The answer in SML is to reject such functions. That is, The third is that the ... can't be filled in with labeled fields and we go with some canonical ordering. I think this limitation on the polymorphism is not ideal, and would also be a bit sad about a canonical ordering of labeled fields - users might want different orderings for cache reasons (and there are additional reasons to care about the order in OxCaml, where we have fields that are less than a word wide we may be able to pack tightly). Possibly I'm missing some nice compilation strategy your discussion covered! I'd love to hear more about it. |
|
Good question! One possible answer is that The downside of this is that it implies that It would also be possible to always interpret |
|
We could introduce some row-like syntax for the head of the tuple e.g. infer You could infer something similar for labelled tuples e.g. |
Rendered version
TL;DR: Adds labeled and (0-indexed) unlabeled tuple projections: