问题
In F# you can define a first
function as follows:
let first (x, y) = x
You can call it like this:
first (1, 2)
You can also define the same function in terms of the BCL Tuple
type:
let first (t:Tuple<_, _ >) = t.Item1
However, you cannot call it using the prior syntax, or you will get the following error:
error FS0001: The type ''c * 'd' is not compatible with the type 'Tuple<'a,'b>'
Instead, you have to do the following:
first (Tuple<_,_>(1, 2))
This is strange, since compiled F# code does seem to use Tuple
to represent its parameters in either case. So why is the F# compiler telling me that the types are not compatible?
Why does any of this matter? Well, basically I want to write a method with overloads supporting a tuple of arbitrary length. This is impossible with F#'s syntactic tuples, since the exact number of arguments must be known in advance. However, it does seem possible by using the BCL Tuple
types, because those use the TRest
trick to allow tuples of arbitrary length. Unfortunately, if I write my overloads this way, then they won't work with F# syntactic tuples, which is the ultimate goal.
So my question is: why aren't syntactic tuples and BCL tuples compatible? And also, are there any examples of writing functions and/or methods that operate on arbitrary-length tuples in F#?
The specific application deals with a type inference-based binary parsing library I'm writing. You can view the code here. You can see the many overloads that I have for tuples, but I don't want to extend them out to some magic number.
回答1:
as usual F# spec to the rescue:
6.3.2 Tuple Expressions
An expression of the form expr1, ..., exprn is a tuple expression. For example:
let three = (1,2,"3")
let blastoff = (10,9,8,7,6,5,4,3,2,1,0)
The expression has the type (ty1 * ... * tyn) for fresh types ty1 … tyn, and each individual expression ei is checked using initial type tyi.
Tuple types and expressions are translated into applications of a family of F# library types named System.Tuple. Tuple types ty1 * ... * tyn are translated as follows:
- For n <= 7 the elaborated form is
Tuple<ty1,...,tyn>
. - For larger n, tuple types are shorthand for applications of the additional F# library type
System.Tuple<_>
as follows: - For n = 8 the elaborated form is
Tuple<ty1,...,ty7,Tuple<ty8>>
. - For 9 <= n the elaborated form is
Tuple<ty1,...,ty7,tyB>
where tyB is the converted form of the type (ty8 ... tyn).
Tuple expressions (expr1,...,exprn) are translated as follows:
- For n <= 7 the elaborated form
new Tuple<ty1,…,tyn>(expr1,...,exprn)
. - For n = 8 the elaborated form
new Tuple<ty1,…,ty7,Tuple<ty8>>(expr1,...,expr7, new Tuple<ty8>(expr8)
. - For 9 <= n the elaborated form
new Tuple<ty1,...ty7,ty8n>(expr1,..., expr7, new ty8n(e8n)
where ty8n is the type (ty8*...* tyn) and expr8n is the elaborated form of the expression expr8,..., exprn.
When considered as static types, tuple types are distinct from their encoded form. However, the encoded form of tuple values and types is visible in the F# type system through runtime types. For example, typeof is equivalent to typeof<System.Tuple<int,int>>
, and (1,2) has the runtime type System.Tuple<int,int>
. Likewise, (1,2,3,4,5,6,7,8,9) has the runtime type Tuple<int,int,int,int,int,int,int,Tuple<int,int>>
.
NOTE: prior to adding tuples to BCL in .NET 4.0 F# used System.Tuple type defined in FSharp.Core dll
I guess the only way for you to deal with tuples having arbitrary size is to resort to constructing and deconstructing then with functions from Microsoft.FSharp.Reflection.FSharpType\FSharpValue
回答2:
I think your observation about very long tuples partly answers your question - in F#, you are allowed to have tuples of arbitrary length, so it is perfectly fine to create a tuple with 9 elements:
let t = (1,1,1,1,1,1,1,1,1)
If you look at the runtime type using t.GetType()
then this is actually compiled to a nested .NET tuple Tuple<int, int, int, int, int, int, int, Tuple<int, int>>
.
I'm not sure if this is the definite answer, but I think it shows part of the problem - if F# tuples matched .NET tuples, then they would either have to be limited to 8 elements (to match the .NET tuple types) or they would be a "leaky" abstraction and large tuples would (silently) match some nested tuple types.
If you need a function that works on arbitrary number of elements, then it would perhaps make more sense to accept the arguments as a list, rather than a tuple? Or you can write a function that works on arbitrarily sized tuples using F# reflection (in Microsoft.FSharp.Reflection
)... But I see that this would be useful for parsers and the other approaches might not be as nice.
来源:https://stackoverflow.com/questions/21870026/f-structural-tuples-versus-bcl-tuple-types