问题
I'm trying to convert a discriminated union to string but I don't understand why this code is not working.
type 'a sampleType =
| A of 'a
| B of 'a
let sampleTypeToString x =
match x with
| A (value) -> string value
| B (value) -> string value
This is the fsharp interactive output
sampleTypeToString A(2);;
Stopped due to error
System.Exception: Operation could not be completed due to earlier error
Successive arguments should be separated by spaces or tupled, and arguments involving function or method applications should be parenthesized at 3,19
This expression was expected to have type
'obj'
but here has type
'int' at 3,21
回答1:
There are two errors here: function application syntax and lost genericity.
Function application syntax
This is the error "function arguments should be separated by spaces or tupled..."
In the expression sampleTypeToString A(2)
, you actually have three terms, not two:
sampleTypeToString
A
(2)
Don't let the lack of a space between A
and (2)
fool you. These are not considered, somehow, as "one expression". A
and (2)
are separate terms.
Therefore, the whole expression sampleTypeToString A(2)
is being interpreted as function sampleTypeToString
applied to two arguments - A
and (2)
. This, of course, doesn't work, because sampleTypeToString
takes only one argument, and term A
doesn't fit, because it's of the wrong type.
The simplest way to fix it is to just put parentheses around what should be evaluated first:
sampleTypeToString (A(2))
And of course, since function (or constructor) application in F# doesn't actually require parentheses by itself, you can drop the first set:
sampleTypeToString (A 2)
Alternatively, you can use a pipe:
sampleTypeToString <| A(2)
This works, because the pipe operator has lower precedence than function application (which is the highest of all), so that A(2)
gets evaluated first, and only then piped into sampleTypeToString
.
Lost genericity
This has to do with the error "expected obj
, but here has type int
"
This one is a bit trickier. See how you're using the string
function within sampleTypeToString
? That function is technically generic, but not in the regular way. It uses statically resolved type constraints. Without going into too much detail, this basically means that the concrete type of the argument has to be known at compile time.
But your function sampleTypeToString
takes a parameter of generic type sampleType<'a>
, and thus when it calls string
, it passes the argument of type 'a
. But string
can't work like that: it needs to know the concrete type, can't be generic 'a
. So the compiler tries its best to substitute a concrete type. Because it knows literally nothing of what 'a
could be, it goes with the most general assumption obj
.
As a result, your function sampleTypeToString
actually ends up taking parameter of type sampleType<obj>
, not sampleType<'a>
as you might expect.
The solution? Declare you function as inline
. This will tell the compiler not to actually compile it as a .NET method, but rather expand its definition wherever it's called (somewhat similar to a DEFINE
in C or a template in C++). This way, the type 'a
will always be known at compile time, and the picky function string
would be satisfied.
let inline sampleTypeToString x =
match x with
| A (value) -> string value
| B (value) -> string value
sampleTypeToString (A 2)
sampleTypeToString (B "abc")
sampleTypeToString (A true)
Alternatively, you could box the argument of string
, turning it into obj
:
let sampleTypeToString x =
match x with
| A (value) -> string (box value)
| B (value) -> string (box value)
But then you'd slightly change the semantics of string
, for it has special processing for certain types, converting to string in culture-invariant way. If you box the argument, it will basically always fall back to obj.ToString()
. Plus, you'd suffer an extra heap allocation for the boxed value.
Even more alternatively, you could do away with string
and just call .ToString()
yourself:
let sampleTypeToString x =
match x with
| A (value) -> value.ToString()
| B (value) -> value.ToString()
Keep in mind that this is not exactly the same as calling string
.
来源:https://stackoverflow.com/questions/43232977/f-convert-a-discriminated-union-to-string