// Path: src/main/java/exceptions/UnificationFailException.java
// public class UnificationFailException extends RuntimeException {
//     public UnificationFailException(Unifyable first, Unifyable second) {
//         super("The unification of inferred type for this expression has failed : " + first + " <=/=> " + second);
//     }
// }
// 
// Path: src/main/java/inference/interfaces/Substitutable.java
// public abstract class Substitutable<T extends Substitutable<T>> {
//     public T apply(Substitution s) {
//         T result = identity();
//         for (TVariable var : s.variables()) {
//             result = result.substitute(var, s.substituteOf(var));
//         }
//         return result;
//     }
//     abstract public HashSet<TVariable> ftv();
// 
//     abstract public T substitute(TVariable var, Type t);
// 
//     abstract public T identity();
// }
// 
// Path: src/main/java/inference/Substitution.java
// public class Substitution extends Substitutable<Substitution> {
//     private HashMap<TVariable, Type> subs = new HashMap<>();
// 
//     public Substitution() {}
// 
//     public Substitution(TVariable variable, Type type) {
//         subs.put(variable, type);
//     }
// 
//     private Substitution(Substitution previous) {
//         subs.putAll(previous.subs);
//     }
// 
//     public Substitution(List<TVariable> vars, List<Type> substitute) {
//         if(vars.size() != substitute.size())
//             throw new IllegalArgumentException("Number of variables have to match number of types");
// 
//         for (int i = 0; i < vars.size(); i++) subs.put(vars.get(i), substitute.get(i));
//     }
// 
//     public List<TVariable> variables() {
//         return new LinkedList<>(subs.keySet());
//     }
// 
//     public Type substituteOf(TVariable var) {
//         return subs.get(var);
//     }
// 
//     public Substitution composeWith(Substitution sub) {
//         Substitution substituted = new Substitution(sub);
//         substituted.apply(this);
//         subs.putAll(substituted.subs);
//         return this;
//     }
// 
//     @Override
//     public HashSet<TVariable> ftv() {
//         HashSet<TVariable> freeVars = new HashSet<>();
//         for (TVariable var : variables()) freeVars.addAll(substituteOf(var).ftv());
//         return freeVars;
//     }
// 
//     @Override
//     public Substitution substitute(TVariable var, Type t) {
//         for (TVariable variable : variables())
//             subs.replace(variable, substituteOf(variable).substitute(var, t));
//         return this;
//     }
// 
//     @Override
//     public String toString() {
//         return subs.toString();
//     }
// 
//     @Override
//     public Substitution identity() {
//         return this;
//     }
// }
// 
// Path: src/main/java/inference/interfaces/Unifyable.java
// public interface Unifyable {
//     Substitution unifyWith(Type type);
// 
//     default Substitution bind(TVariable var, Type type) {
//         if(var.equals(type)) return new Substitution();
//         if(type.ftv().contains(var)) throw new InfiniteTypeException(this, type);
//         return new Substitution(var, type);
//     }
// }


import exceptions.UnificationFailException;
import inference.interfaces.Substitutable;
import inference.Substitution;
import inference.interfaces.Unifyable;
package types;


/**
 * Created by valentin on 18/10/2016.
 *
 * Interface générale de type.
 */
public abstract class Type extends Substitutable<Type> implements Unifyable{

    @Override
    public Type identity() {
        return this;
    }

    @Override
    public final Substitution unifyWith(Type type) {
        if(this.equals(type)) return new Substitution();
        if(type instanceof TVariable) return unifyWith((TVariable) type);
        if(type instanceof TFunction) return unifyWith((TFunction) type);
        if(type instanceof TConstructor) return unifyWith((TConstructor) type);
        throw new UnificationFailException(this, type);
    }

    protected Substitution unifyWith(TVariable var) {
        return bind(var, this);
    }

// Path: src/main/java/exceptions/UnificationFailException.java
// public class UnificationFailException extends RuntimeException {
//     public UnificationFailException(Unifyable first, Unifyable second) {
//         super("The unification of inferred type for this expression has failed : " + first + " <=/=> " + second);
//     }
// }
// 
// Path: src/main/java/inference/interfaces/Substitutable.java
// public abstract class Substitutable<T extends Substitutable<T>> {
//     public T apply(Substitution s) {
//         T result = identity();
//         for (TVariable var : s.variables()) {
//             result = result.substitute(var, s.substituteOf(var));
//         }
//         return result;
//     }
//     abstract public HashSet<TVariable> ftv();
// 
//     abstract public T substitute(TVariable var, Type t);
// 
//     abstract public T identity();
// }
// 
// Path: src/main/java/inference/Substitution.java
// public class Substitution extends Substitutable<Substitution> {
//     private HashMap<TVariable, Type> subs = new HashMap<>();
// 
//     public Substitution() {}
// 
//     public Substitution(TVariable variable, Type type) {
//         subs.put(variable, type);
//     }
// 
//     private Substitution(Substitution previous) {
//         subs.putAll(previous.subs);
//     }
// 
//     public Substitution(List<TVariable> vars, List<Type> substitute) {
//         if(vars.size() != substitute.size())
//             throw new IllegalArgumentException("Number of variables have to match number of types");
// 
//         for (int i = 0; i < vars.size(); i++) subs.put(vars.get(i), substitute.get(i));
//     }
// 
//     public List<TVariable> variables() {
//         return new LinkedList<>(subs.keySet());
//     }
// 
//     public Type substituteOf(TVariable var) {
//         return subs.get(var);
//     }
// 
//     public Substitution composeWith(Substitution sub) {
//         Substitution substituted = new Substitution(sub);
//         substituted.apply(this);
//         subs.putAll(substituted.subs);
//         return this;
//     }
// 
//     @Override
//     public HashSet<TVariable> ftv() {
//         HashSet<TVariable> freeVars = new HashSet<>();
//         for (TVariable var : variables()) freeVars.addAll(substituteOf(var).ftv());
//         return freeVars;
//     }
// 
//     @Override
//     public Substitution substitute(TVariable var, Type t) {
//         for (TVariable variable : variables())
//             subs.replace(variable, substituteOf(variable).substitute(var, t));
//         return this;
//     }
// 
//     @Override
//     public String toString() {
//         return subs.toString();
//     }
// 
//     @Override
//     public Substitution identity() {
//         return this;
//     }
// }
// 
// Path: src/main/java/inference/interfaces/Unifyable.java
// public interface Unifyable {
//     Substitution unifyWith(Type type);
// 
//     default Substitution bind(TVariable var, Type type) {
//         if(var.equals(type)) return new Substitution();
//         if(type.ftv().contains(var)) throw new InfiniteTypeException(this, type);
//         return new Substitution(var, type);
//     }
// }


// Path: src/main/java/types/Type.java
import exceptions.UnificationFailException;
import inference.interfaces.Substitutable;
import inference.Substitution;
import inference.interfaces.Unifyable;

package types;


/**
 * Created by valentin on 18/10/2016.
 *
 * Interface générale de type.
 */
public abstract class Type extends Substitutable<Type> implements Unifyable{

    @Override
    public Type identity() {
        return this;
    }

    @Override
    public final Substitution unifyWith(Type type) {
        if(this.equals(type)) return new Substitution();
        if(type instanceof TVariable) return unifyWith((TVariable) type);
        if(type instanceof TFunction) return unifyWith((TFunction) type);
        if(type instanceof TConstructor) return unifyWith((TConstructor) type);
        throw new UnificationFailException(this, type);
    }

    protected Substitution unifyWith(TVariable var) {
        return bind(var, this);
    }

    protected Substitution unifyWith(TFunction fun) {