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.
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.