Search code examples
f#pattern-matchingtail-recursionguard-clause

Incomplete pattern matching a tuple in F#


I define a point

type TimeSeriesPoint<'T> = 
    { Time : DateTimeOffset
      Value : 'T }

and a series

type TimeSeries<'T> = TimeSeriesPoint<'T> list

where I assume the points in this list are ordered by time.

I am trying to zip two time series, where, in general, they will have points with the same time, but there might be some points missing in either of them.

Any idea why I get a warning for incomplete pattern matches in the code below?

let zip (series1 : TimeSeries<float>) (series2 : TimeSeries<float>) =
    let rec loop revAcc ser1 ser2 =
       match ser1, ser2 with
       | [], _ | _, [] -> List.rev revAcc
       | hd1::tl1, hd2::tl2 when hd1.Time = hd2.Time ->
           loop ({ Time = hd1.Time; Value = (hd1.Value, hd2.Value) }::revAcc) tl1 tl2
       | hd1::tl1, hd2::tl2 when hd1.Time < hd2.Time ->
           loop revAcc tl1 ser2
       | hd1::tl1, hd2::tl2 when hd1.Time > hd2.Time ->
           loop revAcc ser1 tl2
    loop [] series1 series2

If I write it this way, I get no warning, but is it tail recursive?

let zip' (series1 : TimeSeries<float>) (series2 : TimeSeries<float>) =
    let rec loop revAcc ser1 ser2 =
       match ser1, ser2 with
       | [], _ | _, [] -> List.rev revAcc
       | hd1::tl1, hd2::tl2 ->
           if hd1.Time = hd2.Time then
               loop ({ Time = hd1.Time; Value = (hd1.Value, hd2.Value) }::revAcc) tl1 tl2
           elif hd1.Time < hd2.Time then
               loop revAcc tl1 ser2
           else 
               loop revAcc ser1 tl2
    loop [] series1 series2

Solution

  • In general, it is an anti-pattern to have a when guard in the last pattern.

    In zip, you can achieve the same effect as zip' does by removing the redundant guard:

    let zip (series1: TimeSeries<float>) (series2: TimeSeries<float>) =
        let rec loop revAcc ser1 ser2 =
           match ser1, ser2 with
           | [], _ | _, [] -> List.rev revAcc
           | hd1::tl1, hd2::tl2 when hd1.Time = hd2.Time ->
               loop ({ Time = hd1.Time; Value = (hd1.Value, hd2.Value) }::revAcc) tl1 tl2
           | hd1::tl1, hd2::tl2 when hd1.Time < hd2.Time ->
               loop revAcc tl1 ser2
           | hd1::tl1, hd2::tl2 ->
               loop revAcc ser1 tl2
        loop [] series1 series2
    

    Both two functions are tail recursive since there is no extra work after a recursive call to loop.