// 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/environements/TypeInferenceEnv.java
// public class TypeInferenceEnv {
//     private TypeGenerator typeGenerator;
//     private TypeEnv typeEnv;
//     private List<Constraint> constraints;
// 
//     public TypeInferenceEnv() {
//         typeGenerator = new TypeGenerator();
//         typeEnv = new TypeEnv();
//         constraints = new LinkedList<>();
//     }
// 
//     private TypeInferenceEnv(TypeInferenceEnv globalEnv) {
//         typeGenerator = globalEnv.typeGenerator;
//         typeEnv = new TypeEnv().mergeWith(globalEnv.typeEnv);
//         constraints = globalEnv.constraints();
//     }
// 
//     public TypeInferenceEnv inEnv(Variable var, Scheme scheme) {
//         TypeInferenceEnv localEnv = new TypeInferenceEnv(this);
//         localEnv.bind(var, scheme);
//         return localEnv;
//     }
// 
//     public void bind(Variable var, Scheme scheme) {
//         typeEnv.extend(var, scheme);
//     }
// 
//     public void unify(Type t1, Type t2) {
//         constraints.add(new Constraint(t1, t2));
//     }
// 
// //    public Type instantiate(Scheme scheme) {
// //        List<Type> freshVars = new LinkedList<>();
// //        scheme.variables().forEach(var -> freshVars.add(freshName()));
// //
// //        Type instatiation = scheme.type().duplicate();
// //        instatiation.apply(new Substitution(scheme.variables(), freshVars));
// //
// //        return instatiation;
// //    }
// 
//     public Scheme generalize(Type t) {
//         HashSet<TVariable> freeVars = t.ftv();
//         freeVars.removeAll(typeEnv.ftv());
//         return forall(new ArrayList<>(freeVars), t);
//     }
// 
//     public Scheme lookup(Variable variable) {
//         return typeEnv.lookup(variable);
//     }
// 
//     public TVariable freshName() {
//         return typeGenerator.freshName();
//     }
// 
//     public List<Constraint> constraints() {
//         return constraints;
//     }
// }
// 
// 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();
// }


import inference.Substitution;
import inference.environements.TypeInferenceEnv;
import inference.interfaces.Substitutable;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;

    // Object override - Begin
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Scheme scheme = (Scheme) o;

        return variables.equals(scheme.variables) && type.equals(scheme.type);
    }

    @Override
    public int hashCode() {
        int result = variables.hashCode();
        result = 31 * result + type.hashCode();
        return result;
    }

    @Override
    public String toString() {
        String res = "∀";
        if(!variables.isEmpty()){
            res += variables.get(0).name();
            for (int i = 1; i < variables.size(); i++) res += variables.get(i).name();
        }

        return res + "." + type;
    }

// 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/environements/TypeInferenceEnv.java
// public class TypeInferenceEnv {
//     private TypeGenerator typeGenerator;
//     private TypeEnv typeEnv;
//     private List<Constraint> constraints;
// 
//     public TypeInferenceEnv() {
//         typeGenerator = new TypeGenerator();
//         typeEnv = new TypeEnv();
//         constraints = new LinkedList<>();
//     }
// 
//     private TypeInferenceEnv(TypeInferenceEnv globalEnv) {
//         typeGenerator = globalEnv.typeGenerator;
//         typeEnv = new TypeEnv().mergeWith(globalEnv.typeEnv);
//         constraints = globalEnv.constraints();
//     }
// 
//     public TypeInferenceEnv inEnv(Variable var, Scheme scheme) {
//         TypeInferenceEnv localEnv = new TypeInferenceEnv(this);
//         localEnv.bind(var, scheme);
//         return localEnv;
//     }
// 
//     public void bind(Variable var, Scheme scheme) {
//         typeEnv.extend(var, scheme);
//     }
// 
//     public void unify(Type t1, Type t2) {
//         constraints.add(new Constraint(t1, t2));
//     }
// 
// //    public Type instantiate(Scheme scheme) {
// //        List<Type> freshVars = new LinkedList<>();
// //        scheme.variables().forEach(var -> freshVars.add(freshName()));
// //
// //        Type instatiation = scheme.type().duplicate();
// //        instatiation.apply(new Substitution(scheme.variables(), freshVars));
// //
// //        return instatiation;
// //    }
// 
//     public Scheme generalize(Type t) {
//         HashSet<TVariable> freeVars = t.ftv();
//         freeVars.removeAll(typeEnv.ftv());
//         return forall(new ArrayList<>(freeVars), t);
//     }
// 
//     public Scheme lookup(Variable variable) {
//         return typeEnv.lookup(variable);
//     }
// 
//     public TVariable freshName() {
//         return typeGenerator.freshName();
//     }
// 
//     public List<Constraint> constraints() {
//         return constraints;
//     }
// }
// 
// 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/types/Scheme.java
import inference.Substitution;
import inference.environements.TypeInferenceEnv;
import inference.interfaces.Substitutable;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;


    // Object override - Begin
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Scheme scheme = (Scheme) o;

        return variables.equals(scheme.variables) && type.equals(scheme.type);
    }

    @Override
    public int hashCode() {
        int result = variables.hashCode();
        result = 31 * result + type.hashCode();
        return result;
    }

    @Override
    public String toString() {
        String res = "∀";
        if(!variables.isEmpty()){
            res += variables.get(0).name();
            for (int i = 1; i < variables.size(); i++) res += variables.get(i).name();
        }

        return res + "." + type;
    }

    public Type instantiate(TypeInferenceEnv env) {