Search code examples
f#with-statementdiscriminated-union

How to deconstruct union with "with"?


I have some problems to use "with" with a discriminated union:

type NaturalPerson = {
    FirstName: string
    LastName: string
}

type CorporateEntity = {
    Name1: string
    Name2: string option
}

type Person = 
    | Natural of NaturalPerson
    | Company of CorporateEntity

let company = Company { Name1 = "Foo Bar AG"; Name2 = Some "Baz" }

Now I want to change Name2 to None, but I could not figure out how. Something like:

let company2 = Company { company with Name2 = None }

In my "real world example" of course this is nested, otherwise I could use the correct type.

Maybe this isn't possible, because I have to pattern match for an edge case, that can not exist (but the compiler is not smart enough to know).


Solution

  • If you break it out a bit more it is easier to see the problem. In fact what is probably making this difficult is the naming.

    let company = Company { Name1 = "Foo Bar AG"; Name2 = Some "Baz" } // Person
    let company2 = Company { company with Name2 = None } // Person, but broken because expecting company to be type of CorporateEntity
    

    So you are trying to create a CorporateEntity with a Person type, which are not the same.

    This works because the correct type is used.

    let c1 : CorporateEntity = { Name1 = "Foo Bar AG"; Name2 = Some "Baz" }
    let p1 : Person = Company c1
    let c2 : CorporateEntity = { c1 with Name2 = None }
    let p2 : Person = Company c2
    

    I have added the types and changed the name to make the type more apparent. You could match on this...

    match company with // <- rename company to person so it is clearer
    | Natural _ -> company
    | Company c -> Company { c with Name2 = None }
    

    If you wanted to match in a function you could do it like this:

    let noCompanyName2 (c:CorporateEntity) = // c:CorporateEntity -> Person
        let { Name1 = n1; Name2 = _ } = c
        let company3 = Company { Name1 = n1; Name2 = None }
        company3
    

    Or more concisely:

    let noCompanyName2 ({ Name1 = n1; Name2 = _ }) = Company { Name1 = n1; Name2 = None }
    

    Hope this helps.