Search code examples
listsmldecomposition

SML list decomposition issue


I found the old U of Washington course CSE341: Programming Languages and am trying to follow along just so I can one day tackle Paulson's ML for the Working Programmer. So I have this:

fun number_in_month5 (m : int, ms : (int*int*int) list) =
    List.length (List.filter (fn (_,x2,_) => x2 = m) ms)

which gives me back a list of matching 3-tuples. I can throw a List.length on the front and get the count. But what about this

fun number_in_month6 (m, (_,d2,_) :: ms) =
    List.length (List.filter (fn (_,x2,_) => x2 = m) ms)

: stdIn:279.36 Warning: calling polyEqual
: stdIn:278.5-279.43 Warning: match nonexhaustive
:           (m,(_,d2,_) :: ms) => ...
:   
: val number_in_month6 = fn : ''a * ('b * ''a * 'c) list -> ('b * ''a * 'c) list

Testing gives bad answers sometimes

- number_in_month6  (2,[(2018,2,1),(2018,2,2),(2018,1,3),(2018,2,4)])
val it = 2 : int

Why is this not working? But then I have no skill in reading an ML error message. Somebody advised not declaring types at the start of the function -- and I tried and failed to do this. Then I found this

fun sumlists [] [] : int list = []
    |   sumlists (n :: ns) (m :: ms) = (n + m) :: sumlists ns ms;

    ;;; ML WARNING - Clauses of function declaration are non-exhaustive
    ;;; CONTEXT  :  fun sumlists
    val sumlists = fn : int list -> int list -> int list

The definition leaves an expression such as sumlists [1] []; undefined.

So I understand the concept of non-exhaustive, but I can't fathom why my function is non-exhaustive and giving bad answers.


Solution

  • fun number_in_month6 (m, (_,d2,_) :: ms) =
    List.length (List.filter (fn (_,x2,_) => x2 = m) ms)
    
    : stdIn:279.36 Warning: calling polyEqual
    : stdIn:278.5-279.43 Warning: match nonexhaustive
    :           (m,(_,d2,_) :: ms) => ...
    :   
    : val number_in_month6 = fn : ''a * ('b * ''a * 'c) list -> ('b * ''a * 'c) list
    

    I can't fathom why my function is non-exhaustive and giving bad answers.

    • Non-exhaustive: there exists a value which the pattern (_,d2,_) :: ms will not match, which is []. The problem seems to be that you pattern match on the input of number_in_month6 at all when you actually only need to do this inside the predicate function to List.filter.

      Maybe the misconception here is that "the input won't be a list of 3-tuples if I don't pattern match in the argument, since I removed type annotations". It may be interesting to know that SML's type inference is strong enough to infer that ms is a list of 3-tuples simply by using List.filter and fn (_,month2,_) => ....

    • Bad answers: The result is off by one sometimes because you throw away the first element, (_,d2,_), and see how long the filtered remainder of the list, ms, is.

      In your example the first date is supposed to be counted as part of the correct result, 3, but instead it is discarded, and the remaining 2 out of 3 dates are counted correctly.

    Fixing this:

    fun number_in_month (month1, dates) =
        List.length (List.filter (fn (_, month2, _) => month1 = month2) dates)
    

    And testing this:

    - number_in_month6 (2,[(2018,2,1),(2018,2,2),(2018,1,3),(2018,2,4)]);
    > val it = 3 : int
    

    Some points: Giving your variables better names makes it easier to understand what's going on. You may want to consider using a record instead of a tuple. They're basically the same, except tuples have numbered entries and records have named entries, and record syntax is a bit more complicated. But the upside is that you don't accidentally forget if this is American, European or ISO dates:

    type date = { year : int, month : int, day : int }
    
    fun number_in_month (month1, dates : date list) =
        List.length (List.filter (fn ({ month = month2, ... }) => month1 = month2) dates)
    
    val test_1 = number_in_month (2, [ { year = 2018, month = 2, day = 1 }
                                     , { year = 2018, month = 2, day = 2 }
                                     , { year = 2018, month = 1, day = 3 }
                                     , { year = 2018, month = 2, day = 4 }
                                     ])
    

    Record syntax can be a bit tricky, though, so feel free to continue with tuples for as long as you find them practical.