I've specified a custom type which takes two floats and makes them a pair (a complex number):
type complex = (float * float);;
let makeComplex x y = complex(x,y);;
The makeComplex
function is of type float -> float -> complex
, which is entirely correct. However, I want to create a function that takes a complex type and makes it a normal pair as well.
This is the function I've tried:
let complexToPair ((x,y):complex) = (x,y);;
But the resulting type is float * float -> float * float
, when it should really be complex -> float * float
. Am I using the wrong syntax for the ((x,y):complex)
part?
Type abbreviations do not hide or create a type distinct from their definitions. complex
and float * float
are still fully exchangeable, You've just given it another name.
The idiomatic way to make a distinct type in F# is to use a single-case discriminated union:
type complex = Complex of float * float
You can then write your functions using this constructor to create a value and pattern matching to deconstruct it:
let makeComplex x y = Complex (x,y)
let complexToPair (Complex (x, y)) = (x, y)
You can also hide the implementation completely from outside the module using private
:
type complex = private Complex of float * float
Consumers would then have to use the functions you expose to create and consume values of the complex
type.
Creating DUs with only a single case is bad practice (unless you intend to add other cases later), since DUs exist to contain and match discriminated data.
@CharlesRoddie Can you back that up with anything other than opinion? There's plenty of evidence to the contrary.
the first link isn't evidence but an opinion. My reasons in more detail are: 1. Naming. DUs are called "discriminated unions" and so they are for discriminated data, and one type of data does not need discrimination. 2. Complexity. DUs contain scaffolding for discrimination, unlike record types and record types, which are therefore more appropriate ways to implement this idea. You can use sharplab.io to confirm this.
Your "contrary" link is fine because it is perfectly good use of a DU to have a single case which is open to future expansion with more cases, as discussed there and mentioned in my original comment here.
@CharlesRoddie So the technical merit of your argument is essentially just that there's an extra
Tag
method? That seems pretty insignificant considering the 16 or so byte overhead you add by wrapping the value in an object, which both a record and a single-case discriminated union does.