3

I am trying to find if an element is a part of a set. Here is my function:

fun elementOf(x:int, (nil:int list, nil:bool list)) = nil
  | elementOf(x, (y::ys, z::zs)) = if x = y then z else elementOf(x, (ys, zs));

So if I were to call elementOf(2, ([2,7,4], [false,true,false])) it would return false.

However, I get the error message:

Error: types of if branches do not agree [tycon mismatch]
then branch: bool
else branch: 'Z list
in expression: if x = y then z else elementOf(x,ys,zs))

What is a 'Z list and how do I fix this error?

sshine
  • 15,635
  • 1
  • 41
  • 66
TGuedes
  • 69
  • 1
  • 6

3 Answers3

4

What is a 'Z list?

A 'Z list is Standard ML's way of inferring the return type of nil in your base case. nil could be any type of list, since it's empty. 'Z is a type variable.

How do I fix this error?

The problem is that elementOf cannot sometimes return nil and other times true / false. By changing the base-case to false, the function will work such that elements not in the list are also not in the set:

fun elementOf(x:int, (nil:int list, nil:bool list)) = false
  | elementOf(x, (y::ys, z::zs)) = if x = y then z else elementOf(x, (ys, zs));

But there is probably not a good reason to store elements that are not in the set, as opposed to throwing them away. A slightly more efficient representation would simply be a list of members:

fun elementOf (x, nil : int list) = false
  | elementOf (x, y::ys) = x = y orelse elementOf (x, ys)

Or made with the built-in list combinator List.exists:

fun elementOf x xs = List.exists (y => x = y) xs

Or written in point-free style:

fun curry f x y = f (x, y)
val elementOf = List.exists o curry op=

A better one yet relies on binary trees:

data binTree = Empty
             | Node of binTree * int * binTree

fun elementOf (x, Empty) false
  | elementOf (x, Node (left, y, right)) =
    (case Int.compare (x, y) of
          LESS => elementOf (x, left)
        | EQUAL => true
        | GREATER => elementOf (x, right))
sshine
  • 15,635
  • 1
  • 41
  • 66
3

The compiler has determined that the “then” branch must return the type bool, but the “else” branch must return the type 'Z list. The type 'Z list means “a list whose elements could be anything”. Types beginning with ' are type variables, which may later be replaced by another type if the compiler determines that not all types are possible, or may end up remaining if the type can be polymorphic.

The “then” branch must be a boolean because it's z, which given the pattern in the match construct is the first element of a list that the pattern above specifies is of type bool list. The “else” branch must be a list because it's a call to elementOf, and the first case in the pattern matching has elementOf return nil which is a list (with no constraint on the type of elements: since the empty list has no elements, its elements can be said to be of any type you care to choose).

Given the purpose of the function, it should return a boolean, so returning nil in the first case doesn't make sense. Empty lists encode a set with no element, so you should return false there.

Gilles 'SO- stop being evil'
  • 104,111
  • 38
  • 209
  • 254
1

You seem to be representing sets as (U,V) where U is a universal set and V is a Boolean vector (both represented as lists of the same length). This is certainly reasonable though as @SimonShine suggests in his excellent answer it isn't the most efficient.

Using your representation, you can define your function using pattern matching, letting SML's type-inference infer the most general type for it:

fun elementOf (x,([],[])) = false
|   elementOf (x,(y::ys,z::zs)) = if x = y then z else elementOf (x,(ys,zs));

The inferred type is

''a * (''a list * bool list) -> bool

''a refers to any equality type -- so your function can be used to represent sets of any type (as long as that type has a notion of equality) and not just ints. For example, elementOf ("cat",(["the","cat","in","hat"],[true,true,false,true])) evaluates to true.

In SML/NJ the definition gives a warning: Warning: match nonexhaustive. This is because the definition assumes that the lists are the same length. You could keep the code as is -- maybe the function should crash if your Boolean list isn't of the same length as the universal set, or you could decide that false is a reasonable default value and modify the definition to:

fun elementOf (x,([],_)) = false
|   elementOf (x, (_,[])) = false
|   elementOf (x,(y::ys,z::zs)) = if x = y then z else elementOf (x,(ys,zs));
John Coleman
  • 51,337
  • 7
  • 54
  • 119
  • You could join the two first cases and put them as a last case `elementOf (x, _) = false`. – sshine Jan 18 '16 at 15:19
  • 1
    @SimonShine Good observation, though there is something to be said about the more explicit patterns which make the basis cases more explicit. – John Coleman Jan 18 '16 at 15:22