Alexander Zeitler
Alexander Zeitler

Reputation: 13089

Understanding union cases

I have some types like this:

type Workshop = { Start: DateTime; End: DateTime }
type Mounting = { Start: DateTime; End: DateTime }
type FixedWorkTime = { Start: DateTime; End: DateTime }

type WorkTime =
    | Workshop of Workshop
    | Mounting of Mounting

type ApprovedWorkTime =
    | ExistingWorkTime of WorkTime
    | FixedWorkTime of FixedWorkTime

Now I want to create a function with this signature WorkTime -> ApprovedWorkTime

let FixWorkTime (workTime: WorkTime) : ApprovedWorkTime =
    match workTime with
    | WorkTime.Workshop workshop -> ApprovedWorkTime.ExistingWorkTime { Start = workshop.Start, End = workshop.End }
    | WorkTime.Mounting mounting -> ApprovedWorkTime.FixedWorkTime { Start = mounting.Start, End = mounting.End }

The match fails to compile with this error:

This expression was expected to have type 'WorkTime' but here has type 'FixedWorkTime'

How can I fix this?

Upvotes: 1

Views: 70

Answers (3)

CaringDev
CaringDev

Reputation: 8551

The easy fix is to make the types match, as other answers already explained:

let FixWorkTime (workTime: WorkTime) : ApprovedWorkTime =
    match workTime with
    | WorkTime.Workshop _ -> ExistingWorkTime workTime
    | WorkTime.Mounting mounting -> FixedWorkTime { Start = mounting.Start; End = mounting.End }

However, I'd probably change your types a bit to make them more composable:

type Interval = { Start: DateTime; End: DateTime }

type WorkTime =
    | Workshop of Interval
    | Mounting of Interval

type ApprovedWorkTime =
    | ExistingWorkTime of WorkTime
    | FixedWorkTime of Interval

let FixWorkTime (workTime: WorkTime) : ApprovedWorkTime =
    match workTime with
    | Workshop _ -> ExistingWorkTime workTime
    | Mounting interval -> FixedWorkTime interval

Upvotes: 2

Fyodor Soikin
Fyodor Soikin

Reputation: 80734

Here:

ApprovedWorkTime.ExistingWorkTime { Start = workshop.Start, End = workshop.End }

You're trying to construct a value of ApprovedWorkTime with the ExistingWorkTime constructor, and you're passing it a record as a parameter.

But according to its definition:

| ExistingWorkTime of WorkTime

It does not expect a record, or expects a value of type WorkTime.

So the shortest way to fix it would be to it workTime as a parameter:

ApprovedWorkTime.ExistingWorkTime workTime

Although I severely doubt that this is what you meant to do.


Separately I'd like to point out that your types are needlessly complicated. Look: every single value has the same fields, and they only differ in their constructors, and you have to shovel those fields back and forth every time you convert between values.

A more efficient model would have the Start and End fields at the top level, and the kind of work as a third field:

type WorkTime<'kind>= { Start: DateTime; End: DateTime; Kind: 'kind }

type WorkTimeKind = Workshop | Mounting

type ApprovedWorkTimeKind = ExistingWorkTime of WorkTime | FixedWorkTime

Upvotes: 2

Bent Tranberg
Bent Tranberg

Reputation: 3470

Use semicolons, not commas, between the fields of the records. Whenever you see a comma, assume that means a tuple.

Second, ExistingWorkTime is of WorkTime, which is also a DU, so you have a hierarchy.

let fixWorkTime (workTime: WorkTime) : ApprovedWorkTime =
    match workTime with
    | Workshop workshop -> ExistingWorkTime (Workshop { Start = workshop.Start; End = workshop.End })
    | Mounting mounting -> FixedWorkTime { Start = mounting.Start; End = mounting.End }

Upvotes: 3

Related Questions