Disclose the content of the discriminatory union case
I am wondering if there is a good way to expose the content of DU code to the DU type itself.
For example:
[<Measure>] type inch
module StructuralShape =
type Plate =
{
Length : float<inch>
Thickness : float<inch>
}
type SingleAngle =
{
VerticalLeg : float<inch>
HorizontalLeg : float<inch>
Thickness : float<inch>
}
type StructuralShape =
| Plate of Plate
| SingleAngle of SingleAngle
let area (ss : StructuralShape) =
match ss with
| Plate pl -> pl.Length * pl.Thickness
| SingleAngle sa ->
(sa.HorizontalLeg + sa.VerticalLeg - sa.Thickness) * sa.Thickness
type StructuralShape with
member ss.Area = area ss
module Test =
open StructuralShape
let myShape = Plate {Length = 2.0<inch>; Thickness = 0.25<inch>}
let area = myShape.Area
// Looking to allow the user of the library
// to access the internal shape properties
// without having to use some type of 'get shape' function
let length = myShape.Length // fails
After revisiting my design, which was inconsistent as Fedor pointed out, I decided to create an interface for IShape.
[<Measure>] type inch
type IShape = interface end
module StructuralShapes =
type Plate =
{
Length : float<inch>
Thickness : float<inch>
}
interface IShape
type SingleAngle =
{
VerticalLeg : float<inch>
HorizontalLeg : float<inch>
Thickness : float<inch>
}
interface IShape
let area (shape: IShape) =
match shape with
| :? Plate as pl -> pl.Length * pl.Thickness
| :? SingleAngle as sa ->
(sa.HorizontalLeg + sa.VerticalLeg - sa.Thickness)
* sa.Thickness
| _ -> failwith "Shape not suppported."
type IShape with
member this.Area = area this
Note. For simplicity, this is a simplified version of the library.
source to share
It is not clear what you want to achieve here. I suspect you may have an inconsistent design in your mind and not be aware of it.
If your type StructuralShape
has only one case, then why do you need it at all? Just use Plate
instead!
If your type StructuralShape
has more cases, then what happens when the actual value has no field Thickness
?
If your type StructuralShape
will have more cases, but they all need to have a field Thickness
, and that field has the same type and the same meaning for all of them, then a better design would be to "lift" common fields to a common type and leave only the differences in the DU:
type Plate = { Length: float }
type Foo = { Bar: string }
type StructuralShapeInfo = Plate of Plate | Foo of Foo
type StructuralShape = { Info: StructuralShapeInfo; Thickness: float }
(sorry for my choice of suffix Info
, I understand that this is not the best choice, but I cannot think of a better one without knowing your domain and not thinking about it for a while, there are three difficult problems in programming: material naming and mistakes "one by one others ")
source to share
You can expand a discriminatory union with members, which is useful for easy access in such cases:
type StructuralShape =
| Plate of Plate
member this.Thickness = match this with Plate p -> p.Thickness
If you add new cases to StructuralShape
, you need to handle the case in your code for this property.
This can be a useful thing, but Fyodor talks very well about refactoring your data model, so it's not necessary.
source to share