-
Notifications
You must be signed in to change notification settings - Fork 20
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
Auto-upcast values when type information is available #849
Comments
I'd personally love to see this for the charting scenario with XPlot as well. You need to add the let trace1 =
Scatter(
x = [0; 1; 2; 3; 4; 5],
y = [1.5; 1.; 1.3; 0.7; 0.8; 0.9]
) :> Trace
let trace2 =
Bar(
x = [0; 1; 2; 3; 4; 5],
y = [1.; 0.5; 0.7; -1.2; 0.3; 0.4]
) :> Trace
[trace1; trace2]
|> Chart.Plot I always forget to do that and I'm then met with this:
|
Will this suggestion fix your issue @cartermp ? I thought lists were one of the examples where auto-upcasting did happen. type Trace() = class end
type Scatter() = inherit Trace()
type Bar() = inherit Trace()
let s = Scatter()
let b = Bar()
[s; b] |> Chart.Plot // Error
([s; b]:Trace list) |> Chart.Plot // Ok
Chart.Plot [s; b] // Ok Isn't this a case of type information flowing from left to right in F#, but not (necessarily) backwards? It's valid that |
Upvote, this reduces clutter with casting to base IActionResult asp .net where in C#, this is sorta free |
@charlesroddie the explicit type annotation at the call site works, but using non-piping style does not (same error). Supporting this particular use case would probably be complicated. |
I would like to make progress on this, and will be writing notes about it in the context of widening for numeric literals and numeric types.
The proposed change will not address this example. Specifically the change being proposed is presumably to embrace more algorithmic type inference and allow upcasts to the "known type" used in type inference. However in this case:
then at the point we check For the proposed rule to be relevant to the example here, the user would need to do this:
But this is exactly the problem of introducing this kind of type-directed rule - suddenly type annotations become much more relevant to basic type checking. Now, F# doesn have a rule that says "if the inferred element type of the first element of a list or array is a nominal type then all the rest of the elements are implicitly checked with subtyping flexibility against that type", e.g. this is allowed:
But this is a classic "type directed" rule that still requires an annotation and, while being useful now and then, rightly feels clumsy to use in practice when combined with piping. It can however be applicable in this form:
where the known desired type for the list is derived from There is however no proposal on the plate to allow
without some kind of annotation, and in general for APIs sensitive to this (particularly view DOM descriptions like the above) I'd discourage the use of piping |
Putting aside the question of homogeneity of lists, I'd like to identify the most common cases where upcasts are commonly required in F# code but where it is entirely reasonable to remove them. The most common I notice are "strongly annotated" expressions like this:
and similarly when implementing an override:
where in the second case override-slot inference means the compiler already knows that the return type of Often these upcasts happen on each branch of a discriminated union match or if/then/else expression, e.g. there are examples in Linq.fs |
I played around with an approach to introduce more flexible coercions and widenings, it's quite promising and a relatively conservative change. I'll keep playing and send a link later. One initial note: this has the potential to inadvertently change programming around the "obj" type, for example consider:
Currently introducing |
I am using BabylonJS from Fable. It has a type hierarchy of Node -> TransformNode -> AbstractMesh -> Mesh. In many cases, properties take a value of Node, for example for parenting nodes I often have to use this code: // Node.parent has type 'Node option'
mesh.parent <- Some (parentMesh :> _) I imagine this is similar for other UI frameworks which have a base |
Experimenting with erased union types, this feature to upcast when type information is available would certainly have helped: For example, when creating the cases for union type, it works out the common ancestor type which is the type all cases would be up-casted to. I'll stick to explicit style, since that's the least way I can possibly break things, and wait for your changes |
I've noticed that in many cases F# function types already automatically handle argument type contravariance but not return type covariance, presumably because inserting an upcast on a return value could spoil a proper tail call. Should tail calls be addressed in the RFC? |
Fixed as part of RFC FS-1093 https://github.com/fsharp/fslang-design/blob/main/FSharp-6.0/FS-1093-additional-conversions.md |
I propose we auto-upcast values when type information is available.
The existing way of approaching this problem in F# is adding clucky
upcast
keywords.Pros and Cons
The advantages of making this adjustment to F# are
The disadvantage of making this adjustment to F# is the possibility of more inheritance-based code that are against F# philosophy.
Extra information
Estimated cost (XS, S, M, L, XL, XXL): S
Related suggestions:
#3
#91
#536
#792
Affidavit (please submit!)
Please tick this by placing a cross in the box:
Please tick all that apply:
The text was updated successfully, but these errors were encountered: