Search code examples
smlsmlnj

SMLNJ: Operator & operand don't agree [tycon mismtach] - for list assignment


I have written the following function in SMLNJ:

fun f(id : int, l : int list list) =
  let
    val i : int = length(l) - 1
  in
    while i > 0 do
    (
      if (exists(id, List.nth(l, i))) then
        List.hd(List.nth(l, i)) := 1
      else();

      i = i - 1
    )
end;

The error received is as follows:

Error operator and operand don't agree [tycon mismatch]
  operator domain: 'Z ref * 'Z
  operand:         int * [int ty]
  in expression:
    List.hd (List.nth (l,i)) := 1

I am aware that the operator domain is what the function expects while the operand is what is provided. I presume this occurs as an int cannot be assigned to a list type. However, I am unclear as to how List.hdList.nth(l, i)) would result in anything other than an int.

Please advise on how I may fix this error and the supporting logic.


Solution

  • SML/NJ's error message does not make this terribly clear. If you were to put this code inside the Moscow ML REPL, and include the function contains which you've used but not defined, you'd get the following error:

    ! Toplevel input:
    !         List.hd(List.nth(l, i)) := 1
    !                          ^
    ! Type clash: expression of type
    !   int list list
    ! cannot have type
    !   'a ref list list
    

    Your program fails because you're treating int values as if they were int ref values.

    In functional programming you generally try to avoid mutable variables (ref values).

    To elaborate on the problem you're having:

    • List.hd(List.nth(l, i)) := 1 means "Set the reference returned by List.hd(List.nth(l, i)) to 1. Since l is an int list list, then List.nth(l, i) returns the ith element of that (or crashes), which is an int list. Then List.hd(...) takes the 1st element of that (or crashes), which is an int. Not an int ref.

      For this line to work, you'd need for l : int ref list list.

      But you don't want that.

    • i = i - 1 is a boolean expression that returns true if i is equivalent to itself minus 1. This is not true for any int. You probably intend to subtract 1 from i and place the result in i, but you can't, because i is not a mutable variable, and the operator for updating a ref is called :=.


    If your problem were to convert the list

    val m = [ [ 1, 2, 3 ],
              [ 4, 5, 6 ],
              [ 7, 8, 9 ] ]
    

    into the list

    val n = [ [ 1, 2, 3 ],
              [ 1, 5, 6 ],
              [ 1, 8, 9 ] ]
    

    then a short way to do this would be to use List.map:

    val n = List.map (fn row => 1 :: List.drop (row, 1)) m
    

    A more manual way that practices recursion and pattern matching over iteration like while ... do (that only works if you have mutable variables) and partial functions like List.hd and List.nth (that might crash) could be:

    fun f [] = []
      | f (row::m) = (1 :: List.drop (row, 1)) :: f m
    
    val n = f m
    

    If you want a mutable version, consider the Array2 module instead of an int list list.

    Here is one Array2 solution where the cursor is increased using recursion:

    fun appulate f from to =
        if from > to then ()
        else (f from; appulate f (from+1) to)
    
    fun f col m =
        appulate (fn row => Array2.update (m, row, col, 1))
                 0 (Array2.nRows m - 1)
    
    fun show m =
        Array2.appi Array2.RowMajor (fn (_, col, c) =>
          print (Int.toString c ^ (if col + 1 = Array2.nCols arr then "\n" else "" )))
          {base=arr,row=0,col=0,nrows=NONE,ncols=NONE};
    
    
    val m' = Array2.fromList m
    val n' = f 0 m'
    val _  = show n'
    

    I realize that I haven't given any examples of a while ... do, or any collection of int refs on which one operates with ref, ! and :=.