Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why are parentheses significant in F# type declarations

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

like image 586
plc Avatar asked Aug 23 '12 09:08

plc


1 Answers

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.

  • In case of Foo, the case contains one element of type int * string
  • In case of 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-elementsopt

union-type-cases :=
   '|'opt union-type-case '|' ... '|' union-type-case

union-type-case :=
   attributesopt   union-type-case-data

union-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 )).

like image 171
Tomas Petricek Avatar answered Oct 05 '22 12:10

Tomas Petricek