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

StackOverflow https://stackoverflow.com/questions/14431526

  •  16-01-2022
  •  | 
  •  

Question

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.

Was it helpful?

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top