Implicit type conversions in F# 6
source link: https://www.compositional-it.com/news-blog/implicit-type-conversions-in-f-6/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
F# has always been quite strict with its types, and that's broadly very useful. Implicit conversions and upcasting are often not allowed, helping to prevent certain kinds of bugs. However, in F# 6 some of these rules have been relaxed.
Implicit conversion of int to "wider" types
Note:
1
has typeint
and1L
has typeint64
Before F# 6, if you provided any number type when a different number type was expected this would cause a compiler error. The motivation here was to avoid any loss of accuracy or integer overflow that could accidentally happen with an implicit conversion. F# wanted you to always explicitly convert that type to force you to think about the risk of doing so.
In F# 6, you are now allowed to provide an int
when an int64
is expected. Consider this example in a list, where every type must match or be convertible to the type of the first element:
[ 1L; 2L; 3 ]
// returns [ 1L; 2L; 3L ]
The first two elements are int64
but the third element is an int
. However, the code compiles and runs. The third element has been automatically converted to an int64
.
There are two more similar conversions from int
that are now allowed:
[ 1.1; 2 ] // int -> float/double
[ 1n; 1 ] // int -> nativeint
Note that these are only allowed because they are safe conversion that can't result in overflow or loss of accuracy. This is known as a "widening" of the type. Other type conversions such as float
-> int
are not done implicitly even though they might be more convenient in some situations, as they could accidentally result in bugs.
Implicit upcasts
Before F# 6, when multiple branches returned different types you would always get a compiler error, even if both types derived from a common type. Code like this would cause a compiler error:
let xs : int seq =
if true then
[ 1 ]
else
[| 1 |]
// xs is now seq { 1 }
The first branch returns a list
and the second an array
, which both derive from seq
. From F# 6, this code will compile successfully.
Note that in this particular case the code only compiles because the whole expression is known to be a
seq
, which is known because of the type annotation.
.NET implicit conversions on method calls
The JsonNode
type from System.Text.Json
has an implicit conversion from several types, which makes it convenient for building JSON, which is a flexible and dynamic data type.
open System.Text.Json.Nodes
// Define a function and a method that take a JsonNode
let takeNode (x:JsonNode) = x
type MyClass =
static member TakeNode (x:JsonNode) = x
// a) Compiles before F# 6
takeNode (JsonNode.op_Implicit 1) // compiles before F# 6
// b) Compiles from F# 6 only but with a warning
takeNode 1 // Warning: implicit conversion
// c) Compiles from F# 6
MyClass.TakeNode 1 // no warning or error
These three examples above demonstrate the following:
a) Before F# 6 we needed to call JsonNode.op_Implicit
to convert an int
into a JSON node. All of the following examples cause a compiler error before F# 6.
b) From F# 6, we can pass an int
directly to a function where JsonNode
is expected, but it produces a warning.
c) From F# 6, we can pass an int
directly to a method where JsonNode
is expected, without any warning.
Implicit convenience
Although we trade off a little bit of type safety by using these implicit conversions, they have been designed to apply to cases that are least likely to cause any bugs while giving the most improvement in user experiences, especially when using common .NET APIs.
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK