[RFC FS-1093 Discussion] - Additional type directed conversions #525
Replies: 12 comments 30 replies
-
Wonder if this can also be extended to implicitly convert when accessing member interface properties/functions of a concrete type instance? type IEvent =
abstract Name: string
type Leave =
| Leave
interface IEvent with
member x.Name = "Leave"
let evt = Leave
evt.Name // Error: The type 'Leave' does not define the field, constructor or member 'Name'
(evt :> IEvent).Name // fine Extending it to anonymous type-tagged unions. type Join =
| Join of address: string
interface IEvent with
member x.Name = "Join"
let event2: (Join|Leave) = Join("foo@nsa.gov")
event2.Name |
Beta Was this translation helpful? Give feedback.
-
I wonder if this feature would cause breaking changes in complex overload resolution, where automatically upcasts are a problem, and I'm using some tricks (wrappings) to avoid it. Maybe now those tricks would stop working. Still, I think we need to have something like the opposite of the flexible type operator |
Beta Was this translation helpful? Give feedback.
-
Yes, it's possible, the conversions should be a tie-breaker of last-resort. I'll start to develop test cases along those lines. The rules are not active in SRTP resolution. |
Beta Was this translation helpful? Give feedback.
-
From the RFC:
I would very much like Some popular 3rd party libraries also use One thing to watch out for is |
Beta Was this translation helpful? Give feedback.
-
This RFC/PR does two things
The choices in (2) and (3) are separate to the mechanism in (1). However the question of what conversions are in (2) at least initially is best discussed and trialled in the context of this RFC. So for now we should discuss both numeric widening and Practically speaking I think it's unlikely we will address numeric widening or
To be honest, even this is somewhat controversial for me, or would have been done before - see for example some of the examples in the RFC where extracting tuple expressions to a That said, it's less controversial than the others. So in order of controversy we have
On integer widening
I understand this position, and we considered it for F# 1.0, though as I think we've discussed elsewhere I do not believe it's feasible to implement generic literals without making a significant breaking change - so I don't actually think it's a starter. The fact that
I'm fairly certain any discussion about There are however things we can tune in this RFC, including
The intent of this RFC is to give a user experience where:
That is, when you hit a library whose design activates it then "it's just nice". That is it's not really part of the F# programmer's actively used features of the language c.f. like the way the "you can use a lambda for a delegate" is not really an active part of the F# programmer's active feature set, it's just something that makes using particular libraries nice. |
Beta Was this translation helpful? Give feedback.
-
@gusty @charlesroddie @cmeeren I've added sections to the RFC on motivation, design principles and so on capturing much of this discussion. I'm particularly interested in examples where genuine cognitive inconsistency (i.e. confusion) or inadvertent bugs would arise in practive if this mechanism is activated, particularly with regard to:
Ideally these examples would use the RFC trial implementation to write example code afresh in an IDE where type hints are available. That is not exercises in potential confusion, but demonstrations of actual confusion. I actually think genuine examples will be rare. But I'd like to think of what methodology we can use to assess this (beyond enabling it in preview bits). |
Beta Was this translation helpful? Give feedback.
-
In regard to upcasts, copying my comment from the suggestion issue:
|
Beta Was this translation helpful? Give feedback.
-
I've revised the RFC to match the implementation in clarifying some technical details. There are some remaining issues. Specifically the presence of > let x : System.Nullable<int> = 3;;
val x : System.Nullable<int> = 3
> let x : option<int> = 3;;
val x : int option = Some 3
> let f (x: option<int>) = x;;
val f : x:int option -> int option
> f 3;;
val it : int option = Some 3 This is, frankly, "surprising" to the F# programmer as things stand today, though there is nothing particularly unsound about it. However I need to consider the full ramifications of this - likely we will disallow it for type |
Beta Was this translation helpful? Give feedback.
-
Some topics to consider
These could be done separately to this RFC, though perhaps it's worth folding them in now, unless they are approached some other way. |
Beta Was this translation helpful? Give feedback.
-
I've been playing with this RFC and have mixed feelings, especially with regard to The problem is really that For example, consider JsonValue from FSharp.Data. It becomes tempting to try to implicit-conversion the heck out of it in the name of getting F# code close and closer to JSON: /// Represents a JSON value. Large numbers that do not fit in the
/// Decimal type are represented using the Float case, while
/// smaller numbers are represented as decimals to avoid precision loss.
[<RequireQualifiedAccess>]
[<StructuredFormatDisplay("{_Print}")>]
type JsonValue =
| String of string
| Number of decimal
| Float of float
| Record of properties:(string * JsonValue)[]
| Array of elements:JsonValue[]
| Boolean of bool
| Null
static member op_Implicit(x: string) = String x
static member op_Implicit(x: int) = Number (decimal x)
static member op_Implicit(x: decimal) = Number x
static member op_Implicit(x: float) = Float x
static member op_Implicit(x: (string * JsonValue)[]) = Record x
static member op_Implicit(x: (string * JsonValue) list) = Record (Array.ofList x)
static member op_Implicit(x: JsonValue[]) = Array x
static member op_Implicit(x: JsonValue list) = Array (Array.ofList x)
static member op_Implicit(x: bool) = Boolean x
let x1 : JsonValue = "aaa"
let x2 : JsonValue = 3.0M
let x3 : JsonValue = 3.0
let x3b : JsonValue = true However this is just yuck, because it really doesn't get you where you want to go, for example this fails: let x4 : JsonValue = [| "a", 1 |] // no implicit conversion because type is not fully known at point of type failure and so this RFC doesn't apply But this works! let x5 : JsonValue = ([| "a", 1 |] : (string * JsonValue) array) Ugh. I mean what have we gained here? Traded one kind of annotation for a worse kind. The problem is that F# programmers will "fiddle" with this too much. They may also scatter It's not so much about mechanism as policy. My belief is that we should do this change to make more warnings on by default:
The logic here is that |
Beta Was this translation helpful? Give feedback.
-
BTW here are some of the
|
Beta Was this translation helpful? Give feedback.
-
Putting this here for now @dsyme @KathleenDollard I wonder what to do with the existing docs on flexible types: https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/flexible-types Every code sample in there no longer requires the use of the constraint, rendering the doc as just noise. |
Beta Was this translation helpful? Give feedback.
-
Discussion thread for RFC FS-1093 Additional type directed conversions
Beta Was this translation helpful? Give feedback.
All reactions