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.

+3


source to share


2 answers


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 ")

+4


source


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.

+2


source







All Articles