I'm completely baffled by observing this behaviour in my F# code, here taken from an interactive session:
Microsoft (R) F# 2.0 Interactive build 4.0.40219.1
Copyright (c) Microsoft Corporation. All Rights Reserved.
For help type #help;;
> type foo = Foo of (string * int);;
type foo = | Foo of (string * int)
> let f = Foo ("bar",42);;
val f : foo = Foo ("bar", 42)
> match f with Foo x -> x;;
val it : string * int = ("bar", 42)
> type bar = Bar of string * int;;
type bar = | Bar of string * int
> let b = Bar ("baz",21);;
val b : bar = Bar ("baz",21)
> match b with Bar x -> x;;
match b with Bar x -> x;;
-------------^^^^^
stdin(7,14): error FS0019: This constructor is applied to 1 argument(s) but expects 2
>
It seems obvious to me that pattern matching on both Foo and Bar with a single variable should be valid - so I was wondering if anyone knew the reason for this wierd behaviour, or if you like me consider it a bug.
Update:
Just to clarify, the reported types of the constructors Foo
and Bar
are:
> Foo;;
val it : string * int -> foo = <fun:clo@14-1>
> Bar;;
val it : string * int -> bar = <fun:clo@13>
So certainly, they should accept the same set of valid patterns
I agree this looks quite confusing. As pad explained, the difference between the two declarations is not just syntactical - you are actually defining discriminated union cases that consist of different types.
Foo
, the case contains one element of type int * string
Bar
, the case contains two elements of type int
and string
The two options are very similar, but they are actually different. You can see that if you look at the type definitions in the F# specification. Here are the bits that describe type definitions of discriminated union:
union-type-defn :=
type-name '=' union-type-cases type-extension-elementsoptunion-type-cases :=
'|'opt union-type-case '|' ... '|' union-type-caseunion-type-case :=
attributesopt union-type-case-dataunion-type-case-data :=
ident -- null union case
ident of type * ... * type -- n-ary union case
Note that the "n-ary union case" consists of multiple elements (type * ... * type
). A type is defined as follows (not surprisingly, it can be a tuple):
type :=
( type )
type -> type -- function type
type * ... * type -- tuple type
... -- lots of other types
I do not know why union-type-case-data does not use just unary union case (instead of n-ary) and always treat the elements as tuple. I think that would make perfect sense, but it might be something that F# inherited from OCaml or ML. However, at least the specification explains this!
In fact, I suppose the specification is a bit ambiguous, because you could treat Foo of int * int
as both n-ary union case and unary case with a tuple (but without the bracketted type ( type )
).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With