Conceptually creating a discriminated union for an appointment in F# from C#

Viewed 76

Be kind, I am a complete newbie to F# (coming from C#). In reviewing a user control for my appointment book, I see it is built upon the class concept of a "Visit" being used to fill a datagrid. I would like to use an F# descriminated union to replace the C# Visit class, but can't get it right. After much reading, I can't seem to break my thinking in C#.

Conceptually, a "Visit" will always have an "appointmentTime", regardless of it having a name or anything else. Now from this point a visit can be several different things (the reason for a using a discrimated union):

  1. If the visit only has a name and posting time, then it is an appointment only.
  2. if the visit only has a appointment time, time of service and a name, then the person was here without an appointment, and is a "walk-in",
  3. if the visit has a name, appointment time, posting time, and service time, then it is an appointment which the person was seen -- "a kept appointment",
  4. if the visit only has an appointment time without a name or anything else, then it is available for use --"an open time slot".

In C#, this is a simple class:

public class Visit
{
   DateTime? tservice {get; set;}
   DateTime? tappointment {get; set;}
   DateTime? tposted {get; set; }
   string lastname {get; set; }
   string firstname {get; set; }
   DateTime birthdate {get; set; }
}

How can this be conceptually written as a discriminated union in F#? And how would I use match...with with it?

Any help or guidance is most appreciated.

2 Answers

The solution is similar to ChesterHusk's one, with some differences:

  1. Since appointmentTime is always present, I believe it makes sense to move it outside DU. Therefore, Visit is a record consisting of visitType and appointmentTime.
  2. In this.Name we use a feature of named DU which allows us to capture only the required fields. This form is better since it is invariant of the order or the number of fields.
open System

type VisitType = 
    | AppointmentOnly of name: string * postedTime: DateTime
    | WalkIn of name: string * serviceTime: DateTime
    | Kept of name: string * postedTime: DateTime * serviceTime: DateTime
    | Open

type Visit =
    { appointmentTime: DateTime
      visitType: VisitType }
with
    member this.Name =
        match this.visitType with
        | AppointmentOnly(name=name) | WalkIn(name=name) | Kept(name=name) -> Some name
        | Open -> None

let visit = { appointmentTime = DateTime.Now
              visitType = WalkIn(name="cool_name", serviceTime=DateTime.Now) }

printfn "%A" visit.Name
printfn "%A" visit.appointmentTime

Here's an example of how this could be modeled as a union in F#.

The basic idea is that you've described each case, and I've just translated each case you dictated. Each case only had the data allowed to it, so there aren't nullables that may or may not be filled in. On top of that, I've defined a helper member that can be accessed from any form of the DU to retrieve commonly-used data (like I imagine the person's name would be).

Hope this helps!

Related