Search code examples
smltypechecking

number_in_month exercise (SML error: operator and operand don't agree when comparing integer in list to an integer)


I am new to Standard ML, and can't figure out why I am getting this type mismatch error:

fun number_in_month (month : int, dates : int list) =                                            
    if null dates                                                                                
    then 0                                                                                       
    else if (month = (hd (tl (hd dates))))            
    then number_in_month(month, (tl dates)) + 1
    else number_in_month(month, (tl dates))

Evaluating this function results in the following error:

Error: operator and operand don't agree [tycon mismatch]     
 5  operator domain: 'Z list                                                                       
 6  operand:         int                                                                           
 7  in expression:                                                                                 
 8    tl (hd dates)     

However, at the REPL, if i do the following:

val x = [[84, 12, 23], [83, 01, 18]]
12 = (hd (tl (hd x)))                    (*  -> val it = true : bool *)

I am not sure what the type-checking rules are in this case, and I don't see why the same expression would work on the REPL but not when I try to evaluate the subexpression in the function.


Solution

  • You're getting the head of the tail of the head of a list. Your x (in the REPL) is a int list list (a list of a list of ints). But your function definition declares it as an int list. Re-declaring number_in_month with dates: int list list should solve your problem:

    fun number_in_month (month : int, dates : int list list) =  
       ...
    

    It works as you expect in the REPL because you define x without explicitly declaring it's type. SML infers that the type of x is int list list which is why (hd (tl (hd x))) passes the type-checker.

    UPDATE

    (was trying to add this right when stackoverflow went down)

    If you're interested, here's some ideas on how you could re-write your code to make it more ML-ish:

    First, you could use pattern matching:

    fun number_in_month (month: int, []) = 0
      | number_in_month (month: int, ([y,m,d]::rest)) = 
          if month = m then number_in_month(month, rest) + 1
          else number_in_month(month, rest)
    

    So number_in_month takes a tuple of a month and a list of dates, which is logically either [] or ([y,m,d]::rest). This is compatible with how you've chosen to represent dates (as a list of ints), but this will compile with a match nonexhaustive warning. That makes sense, because what happens if you pass in dates as [[84], [83]]? The pattern match approach at least warns you about this, but with code like (hd (tl (hd dates))) you'll get a runtime error though your program has type-checked successfully. You could add another pattern match for lists of dates where the date has less/more than 3 elements, but if possible, it might be cleaner to represent dates as tuples of 3 ints.

     type date = (int * int * int)
    

    Then you could have:

    fun number_in_month (month: int, []: date list) = 0
      | number_in_month (month: int, ((y,m,d)::rest)) = 
          if month = m then number_in_month(month, rest) + 1
          else number_in_month(month, rest)
    

    Also, if you'd rather reuse code, you could try higher-order functions (such as foldr):

    fun number_in_month (month: int, dates: date list) =
      foldl (fn ((_,m,_), c) => if m = month then c+1 else c) 0 dates
    

    Or

    fun number_in_month (month: int, dates: date list) =
      length (List.filter (fn (_,m,_) => m = month) dates)
    

    More than you asked for, but I hope it helps.