Following the Dec 1 notes, derive type constraints for this language:
   <expr> ::= <num>
            | true
            | false
            | {+ <expr> <expr>}
            | {- <expr> <expr>}
            | {* <expr> <expr>}
            | {iszero <expr>}
            | {bif <expr> <expr> <expr>}
            | <id>
            | {with {<id> <expr>} <expr>}
            | {rec {<id> <expr>} <expr>}
            | {fun {<id>} <expr>}
            | {<expr> <expr>}
            | tempty
            | {tcons <expr> <expr>}
            | {tempty? <expr>}
            | {tfirst <expr>}
            | {trest <expr>}
   
  The only novelty of this language is that the list operations are now
  polymorphic; that is, you can create lists of values of any type.
  
  
  Note: The right hand side of the rec binding does not have to be a 
  syntactic function. However, you may assume that the rec-bound identifier only
  appears under a {fun ...} in the right hand side of the binding.  In
  other words, the following expressions are legal:
    
     {rec {f {fun {x} {f x}}}
       ...}
     {rec {f {with {y 4}
               {fun {x} {f y}}}}
       ...}
    
    while the following are not legal:
    
     {rec {f f}
       ...}
     {rec {f {+ 1 f}}
       ...}
    
  
  
  Then, write a function which consumes an expression of this language, and returns a list of constraints (of the type defined in Part II).The correspondence between type constraints and the terms in Part II is as follows:
'number or 'boolean.
   '-> and a list of two arguments, or'listof and a list of one argument.gensym returns a unique
  identifier on every call.
 
 Implement the unification algorithm from the Dec 1 notes. The algorithm should work for a generic term representation, as defined below.
A term is either:
In addition, you will need data types for representing a constraint (a pair of terms) and substitution (a variable and a term). The unification algorithm will consume a list of constraints (as defined in Part I) and produce either a list of substitutions or an error string.
Errors can arise from two situations: when the unification of two terms is impossible, or when the occurs check fails. In both cases, you should return a string with an appropriate error message.
  Finally, when comparing variables for equality, use Scheme's built-in
  eq? function.  For symbols, it behaves exactly as
  symbol=?; for other values, it compares them for identity (like
  Java's == comparison).  We will rely on identical
  variables being deemed equivalent by eq? when solving the
  constraints generated in the following section.
 
To infer the type of a program, first parse it, then generate constraints, and finally unify the constraints using the functions written for parts I & II as appropriate. The result will be a list of substitutions; by looking up the subsitution for the entire expression, you can access its type.
  To implement this, your code needs to define a function, 
      infer-type,
  which consumes a concrete representation of the program (as given above), and produces
  either an error string or a representation of the inferred type.
  Represent types concretely as:
  
   <type> ::= number
            | boolean
            | (listof <type>)
            | (<type> -> <type>)
            | <string>
  
  where strings are used to represent type variables.  For example, the
  type of length would be:
  ((listof "a") -> number)
  For a very small amount of extra credit, write a program in this
  language for which your algorithm infers the type
  ("a" -> "b").  You shouldn't
  attempt this problem until you've fully completed the assignment.
 
You do not need to implement an interpreter for this language.