CS 381: Additional Explanations, Frequently Asked Questions

On this page I collect definitions and explanations for some terms and topics that might be difficult to understand or to remember and for which there is no explicit definition given on the slides or in the textbook. I also try to add explanations for frequently asked questions by students. If somebody would like to have something added, please let me know (it would be great if you tried a definition yourself!). Also if you find errors or have other suggestions for improving this reference list, please send me an email.

List Syntax and Pattern Matching
Lists are built using the two constructors [], which denotes the empty list, and (:), also called cons, which adds a single element at the beginning of a list. Lists with a specific number of elements can also be written by listing the elements, separated by commas and enclosed in square brackets. For example,
 
  [3,4,2]
is just an abbreviated form of denoting the same list as:
 
  3:4:2:[]
Note that the latter is the same as 3:(4:(2:[])) since the cons constructor associates to the right.

The same two notations can be used in patterns (i.e. on the LHS of equations used in function definitions). For example, the following function middle matches a list of exactly three elements and returns the middle one. For all other lists, middle returns the first list element. (The second equation is selected only when the first one doesn't match, which is the case for all non-empty lists that have fewer or more than three elements. The function fails for empty lists.)

 
  middle :: [a] -> a
  middle [x,y,z] = y
  middle (x:xs)  = x
The pattern [x,y,z] is again just an abbreviation for the more elaborate pattern using the cons constructor, that is, we can write middle also as follows.
 
  middle :: [a] -> a
  middle (x:(y:(z:us))) = y
  middle (x:xs)         = x
Again, the parentheses inside the pattern can be omitted since cons associates to the right.
 
  middle :: [a] -> a
  middle (x:y:z:us) = y
  middle (x:xs)     = x
Note that we could use the wildcard pattern to emphasize what the function is doing and write the function as follows.
 
  middle :: [a] -> a
  middle [_,y,_] = y
  middle (x:_)   = x
Or like this:
 
  middle :: [a] -> a
  middle (_:y:_:_) = y
  middle (_:_)     = x
Point-free definition
This means that a function definition avoids, as much as possible, variables for objects that are not functions (lists, numbers, ...) and defines new functions by application of higher order functions. In other words, point-free function definitions avoid unnecessary lambda-abstractions and also recursion in the definition.
Polymorphic show
... is basically not possible. If you try to evaluate polymorphic values, you encounter the problem that they cannot be printed. Consider, for instance:
  Prelude> Nothing
  ERROR: Cannot find "show" function for:
  *** Expression : Nothing
  *** Of type    : Maybe a

  Prelude> []
  ERROR: Cannot find "show" function for:
  *** Expression : []
  *** Of type    : [a]
This is a limitation of Haskell's type system: in order to print a polymorphic value like [], Haskell required a polymorphic show function, that is, a show function that works for all types a, which does not exist. Therefore, the error message that says that there is no show function for all types, say [a]. We could think of constraining the type to all the types for which show is defined, for example, by writing:
  Prelude> [] :: Show a => [a]
  ERROR: Unresolved overloading
  *** Type       : Show a => [a]
  *** Expression : []
but then we get another error message that tells us that Haskell does not know which a's show function it should use. However, using a monomorphic type as a type constraint works well:
  Prelude> [] :: [Int]
  []
The bottom line is: you can only print monomorphic values.
Prolog Atoms, Functors, and Terms
For example, x, man, or adam are all atoms (note that atoms begin with a lowercase letter); atoms are symbols that can represent (unstructured) objects. In contrast, man(adam) is a term, and man is the functor of that term. In this term, adam is still an atom. Terms are used to represent structured objects.
Prolog Terms vs. Prolog Predicates
Terms look a lot like predicates. How are they related? Syntactically, any term can be used as a predicate. You can have terms like man(adam) in your program, and you can also use man as a unary predicate at the same time (although it is not recommended to do that unless you really understand the difference). For example,
  man(adam).
  man(X) :- ...
defines a predicate man. Even in one goal you can use terms and predicates of the same name at the same time. For example, the following goal asks whether the predicate man is true for the term man(adam) and for the atom adam.
  man(man(adam)), man(adam).
Notice that the first and third occurrence of man are as predicate names, whereas the second use of man is as a functor. Similarly, the first expression man(adam) is a term (as an argument of the predicate man), whereas the second expression man(adam) is a goal. Note that there are no declarations of functors, unlike in Haskell where constructors have to be declared in data type definitions.

A source of possible confusion is the fact that Prolog terms are used to represent two different things: (i) structured objects (terms) and (ii) programs (predicates), and there is no name space distinction between the two. Hence, if you use terms to represent structured objects or to just tag or mark objects (like var(x)), you usually won't have rules for these terms. Rather these terms are used in your program as patterns of other predicates.

Singleton Variables
If you get a warning about a "singleton variable" for one of your rule, you should check your rule carefully. Singleton variables are variables that are used only once, which often indicates a typo. If it is correct that the variable is used only once, you can always replace it with a wildcard (_), which is a Good Thing to do. Then the error message disappears.
Non unifiable
Consider the following definition of a predicate to extract two different colors from a database.
  twocolorsWrong(X,Y) :- X\=Y, color(X), color(Y).
  
  color(red).
  color(blue).
Why does the following goal fail?
  ?- twocolorsWrong(X,Y).
  false.
The goal fails, because the first subgoal X\=Y fails. It asks whether the two arguments are not unifiable, i.e., whether it is impossible to make them equal. But X and Y are unifiable (e.g. we can substitute X for Y). X and Y are unifiable since they are unbound. The situation changes if we move the subgoal to the end.
  twocolors(X,Y) :- color(X), color(Y), X\=Y.
Now the subgoal X\=Y can succeed since X and Y are bound.
  ?- twocolors(X,Y).
  X = red,
  Y = blue ;
  X = blue,
  Y = red ;
  false.


Martin Erwig  erwig@cs.orst.edu