'is there a way to handle standard fields in this DU, in F#?

I have some quite ugly code here:

type AnalysisEvent =
    | ZoneStart             of DateTime * ConsolidationZone
    | ZoneEndExitHigh       of DateTime * ConsolidationZone
    | ZoneEndExitLow        of DateTime * ConsolidationZone
    | ZoneContraction       of DateTime * ConsolidationZone
    | ZoneExpansion         of DateTime * ConsolidationZone
    | FairPriceCrossHigh    of DateTime * FairPriceLine
    | FairPriceCrossLow     of DateTime * FairPriceLine
    | LiquidityLineCreated  of DateTime * LiquidityLine
    | LiquidityLineReached  of DateTime * LiquidityLine

    member this.GetTimestamp () =
        match this with
        | ZoneStart             (ts, _) -> ts
        | ZoneEndExitHigh       (ts, _) -> ts
        | ZoneEndExitLow        (ts, _) -> ts
        | ZoneContraction       (ts, _) -> ts
        | ZoneExpansion         (ts, _) -> ts
        | FairPriceCrossHigh    (ts, _) -> ts
        | FairPriceCrossLow     (ts, _) -> ts
        | LiquidityLineCreated  (ts, _) -> ts
        | LiquidityLineReached  (ts, _) -> ts

    member this.GetInterval () =
        match this with
        | ZoneStart             (_, x) -> x.Interval
        | ZoneEndExitHigh       (_, x) -> x.Interval
        | ZoneEndExitLow        (_, x) -> x.Interval
        | ZoneContraction       (_, x) -> x.Interval
        | ZoneExpansion         (_, x) -> x.Interval
        | FairPriceCrossHigh    (_, x) -> x.Interval
        | FairPriceCrossLow     (_, x) -> x.Interval
        | LiquidityLineCreated  (_, x) -> x.Interval
        | LiquidityLineReached  (_, x) -> x.Interval

And there are many common fields across all DU types that I need to extract like this; and they have, of course, a lot of different fields as well.

Is there a nicer way to extract the common fields? The DateTime is separated from the underlying type, but there are also common fields across all the types.

How could this be rewritten in a nicer way?

f#


Solution 1:[1]

As Fyodor suggests, this can be refactored as:

type AnalysisEventType =
    | ZoneStart             of ConsolidationZone
    | ZoneEndExitHigh       of ConsolidationZone
    | ZoneEndExitLow        of ConsolidationZone
    | ZoneContraction       of ConsolidationZone
    | ZoneExpansion         of ConsolidationZone
    | FairPriceCrossHigh    of FairPriceLine
    | FairPriceCrossLow     of FairPriceLine
    | LiquidityLineCreated  of LiquidityLine
    | LiquidityLineReached  of LiquidityLine

type AnalysisEvent =
    {
        EventType : AnalysisEventType
        Timestamp : DateTime
    }

    member this.GetTimestamp () =   // you probably don't even need this any more
        this.Timestamp

    member this.GetInterval () =
        match this.EventType with
        | ZoneStart             x
        | ZoneEndExitHigh       x
        | ZoneEndExitLow        x
        | ZoneContraction       x
        | ZoneExpansion         x -> x.Interval
        | FairPriceCrossHigh    x
        | FairPriceCrossLow     x -> x.Interval
        | LiquidityLineCreated  x
        | LiquidityLineReached  x -> x.Interval

And if you want to refactor even further, you could do something like this:

type ConsolidationZoneEventType =
    | Start
    | EndExitHigh
    | EndExitLow
    | Contraction
    | Expansion

type FairPriceEventType =
    | CrossHigh
    | CrossLow

type LiquidityLineEventType =
    | Created
    | Reached

type AnalysisEventType =
    | ConsolidationZoneEventType of ConsolidationZoneEventType * ConsolidationZone
    | FairPriceEventType of FairPriceEventType * FairPriceLine
    | LiquidityLineEventType of LiquidityLineEventType * LiquidityLine

    member this.GetInterval () =
        match this with
        | ConsolidationZoneEventType (_, x) -> x.Interval
        | FairPriceEventType (_, x) -> x.Interval
        | LiquidityLineEventType (_, x) -> x.Interval

type AnalysisEvent =
    {
        EventType : AnalysisEventType
        Timestamp : DateTime
    }

    member this.GetTimestamp () =
        this.Timestamp

    member this.GetInterval () =
        this.EventType.GetInterval ()

Solution 2:[2]

I would recommend to do what Brian suggests and refactor the code so that you have a record with Timestamp and separate discriminated union with event details.

However, if you really want to keep your representation as a union with duplicate items, you can simplify the logic somewhat by using an active pattern that recognizes different types of events based on what other information they contain - so all Zone events would be recognized as one thing. For simplicity, here is an example using just the two latter types of events:

let (|FairPriceEvent|LiquidityEvent|) e =
  match e with 
  | FairPriceCrossHigh(ts, l) | FairPriceCrossLow(ts, l) -> 
      FairPriceEvent(ts, l)
  | LiquidityLineCreated(ts, l) | LiquidityLineReached (ts, l) -> 
      LiquidityEvent(ts, l)

Now you can write logic only by considering three (two in my example) cases:

let getTimeStamp e =
  match e with 
  | FairPriceEvent(ts, _)
  | LiquidityEvent(ts, _) -> ts

let getInterval e =
  match e with 
  | FairPriceEvent(_, l)
  | LiquidityEvent(_, l) -> l.Interval

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Tomas Petricek