1

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 makeComplexfunction 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?

3 Answers3

2

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.

glennsl
  • 28,186
  • 12
  • 57
  • 75
  • 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. – Charles Roddie Feb 04 '20 at 10:58
  • 1
    @CharlesRoddie Can you back that up with anything other than opinion? There's plenty of [evidence](https://fsharpforfunandprofit.com/posts/designing-with-types-single-case-dus/) to the [contrary](https://stackoverflow.com/questions/10150767/purpose-of-a-single-case-discriminated-union). – glennsl Feb 04 '20 at 13:01
  • 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 https://sharplab.io/ to confirm this. – Charles Roddie Feb 04 '20 at 15:25
  • 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. – Charles Roddie Feb 04 '20 at 15:27
  • @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. – glennsl Feb 04 '20 at 19:33
  • Of argument 2 only. The Tag is the main conceptual addition compared with a record, and it's a useless one. All these methods wrap in an object so there is no difference there. The difference in lines of C# code from sharplab.io is 44 so there may be other inefficiencies of the DU. There is also an additional naming argument that it is not possible to find good names for both the DU type and the DU union case. – Charles Roddie Feb 04 '20 at 22:13
  • @CharlesRoddie The difference is lines of pretty-printed code is irrelevant. It adds a few bytes **per type** but not **per value**, which is completely insignificant compared to the per type overhead of a class and per value overhead of an object, which is the same with a record. – glennsl Feb 05 '20 at 05:56
1

If you still really want to use type abbreviation, then use type annotation, like this:

type complex = float * float
let complexToPair: complex -> float * float = id
Nghia Bui
  • 3,694
  • 14
  • 21
-2

You can create a class:

type Complex(x:float, y:float) =
    member t.Real = x
    member t.Imaginary = y

Or a record:

type Complex = { Real:float; Imaginary:float }

These will be distinct types and also allow you to create and organize useful methods (such as arithmetic operators).

Charles Roddie
  • 952
  • 5
  • 16