Search code examples
f#recordsdiscriminated-union

How to update values in list of type <T> in F#?


I am currently studying F# and at the same time, a bit struggling to get the hang of how discriminated unions and records exactly work.

I'd like to know how I can update some values from a list of type <'T>?

My code

type Position =
| Location of (int * int)

type Ship = 
{
    Position : Position;
    Projectiles : List<Position>; 
}

I create an instance of a ship:

let aShip = 
{
    Position: Location(1,5);
    Projectiles: [Location(1,1);Location(2,5)] 
}

Now, I tried to loop over the projectiles, but I get this:

for elem in aShip.Projectiles do
    printfn "%A" elem

// prints out
Location(1,1)
Location(2,5)

But I only like to get the values (1,1) and (2,5) back, how would I achieve this?


Solution

  • Discriminated unions can be destructured by providing a pattern with some places in it occupied by identifiers. The compiler will then generate code that tries to match this pattern to the data, and bind data points to appropriate identifiers. For example:

    let loc = Location (1,2)
    let (Location (x,y)) = loc
    

    For the second line, the compiler will generate code to the effect of "make sure this is a Location, then bind the first int to name x, and the second int to name y"

    Alternatively, you can use more verbose match syntax:

    let x = match loc with Location(x,y) -> x
    

    For your specific case, this is overkill, but for discriminated unions with more than one case, match is the only way to handle them all, for example:

    type Position = 
       | Location of int*int
       | Unknown
    
    let xOrZero = 
       match loc with
       | Location(x,y) -> x
       | Unknown -> 0
    

    Above examples demonstrate how patterns can appear in let-bindings and within match expressions, but this is not all. In F# pretty much anything that you'd think of as "variable declaration" is actually a pattern. It's just most of the time patterns are trivial, as in let x = 5, but they don't have to be - e.g. let x,y = 5,6

    The corollary from the above is that the elem in for elem in ... is also a pattern. Which means that you can destructure the element in place:

    for Location(x,y) in aShip.Projectiles do
        printfn "%d,%d" x y
    

    Or, if you'd like to extract the pair as a whole, rather than x and y individually, that is also possible:

    for Location xy in aShip.Projectiles do
        printfn "%A" xy