// 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/types/TVariable.java
// public class TVariable extends Type {
//     private final String name;
//     private final static HashSet<TVariable> instantiated = new HashSet<>();
// 
//     private TVariable(String name) {
//         this.name = name;
//     }
// 
//     public String name() {
//         return name;
//     }
// 
//     public static TVariable variable(String name) {
//         TVariable var = new TVariable(name);
//         if(!instantiated.add(var)) System.out.println("Creation avoidable : TVariable(" + name + ")");
//         return var;
//     }
// 
//     public static List<TVariable> variables(String... names) {
//         List<TVariable> variables = new LinkedList<>();
//         for (String name : names) variables.add(new TVariable(name));
//         return variables;
//     }
// 
//     @Override
//     public Type substitute(TVariable var, Type type) {
//         return this.equals(var) ? type : this;
//     }
// 
//     @Override
//     public HashSet<TVariable> ftv() {
//         return new HashSet<>(Collections.singleton(this));
//     }
// 
//     @Override
//     protected Substitution unifyWith(TFunction fun) {
//         return bind(this, fun);
//     }
// 
//     @Override
//     protected Substitution unifyWith(TConstructor cons) {
//         return bind(this, cons);
//     }
// 
//     @Override
//     public Type instantiate(Substitution sub) {
//         return sub.variables().contains(this) ? sub.substituteOf(this) : this;
//     }
// 
//     @Override
//     protected Substitution unifyWith(TVariable var) {
//         return bind(this, var);
//     }
// 
//     @Override
//     public boolean equals(Object o) {
//         if (this == o) return true;
//         if (o == null || getClass() != o.getClass()) return false;
// 
//         TVariable tVariable = (TVariable) o;
// 
//         return name.equals(tVariable.name);
// 
//     }
// 
//     @Override
//     public int hashCode() {
//         return ("TVariable_" + name).hashCode();
//     }
// 
//     @Override
//     public String toString() {
//         return name();
//     }
// }
// 
// Path: src/main/java/types/Type.java
// 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) {
//         throw new UnificationFailException(this, fun);
//     }
// 
//     protected Substitution unifyWith(TConstructor cons) {
//         throw new UnificationFailException(this, cons);
//     }
// 
//     public abstract Type instantiate(Substitution sub);
// }
// 
// Path: src/main/java/types/TFunction.java
// public static TFunction function(Type left, Type right) {
//     int hash = hash(left, right);
//     TFunction f = instantiated.get(hash);
//     if(f == null) {
//         f = new TFunction(left, right);
//         instantiated.put(hash, f);
//     }
//     return f;
// }


import inference.environements.TypeInferenceEnv;
import types.TVariable;
import types.Type;
import java.util.Arrays;
import static types.TFunction.function;
package ast;




/**
 * Represent an Application in Lambda calculus.
 */
public class Application extends Expression {
	private Expression left;
	private Expression right;

	public Application(Expression left, Expression right) {
		this.left = left;
		this.right = right;
	}


	@Override
// 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/types/TVariable.java
// public class TVariable extends Type {
//     private final String name;
//     private final static HashSet<TVariable> instantiated = new HashSet<>();
// 
//     private TVariable(String name) {
//         this.name = name;
//     }
// 
//     public String name() {
//         return name;
//     }
// 
//     public static TVariable variable(String name) {
//         TVariable var = new TVariable(name);
//         if(!instantiated.add(var)) System.out.println("Creation avoidable : TVariable(" + name + ")");
//         return var;
//     }
// 
//     public static List<TVariable> variables(String... names) {
//         List<TVariable> variables = new LinkedList<>();
//         for (String name : names) variables.add(new TVariable(name));
//         return variables;
//     }
// 
//     @Override
//     public Type substitute(TVariable var, Type type) {
//         return this.equals(var) ? type : this;
//     }
// 
//     @Override
//     public HashSet<TVariable> ftv() {
//         return new HashSet<>(Collections.singleton(this));
//     }
// 
//     @Override
//     protected Substitution unifyWith(TFunction fun) {
//         return bind(this, fun);
//     }
// 
//     @Override
//     protected Substitution unifyWith(TConstructor cons) {
//         return bind(this, cons);
//     }
// 
//     @Override
//     public Type instantiate(Substitution sub) {
//         return sub.variables().contains(this) ? sub.substituteOf(this) : this;
//     }
// 
//     @Override
//     protected Substitution unifyWith(TVariable var) {
//         return bind(this, var);
//     }
// 
//     @Override
//     public boolean equals(Object o) {
//         if (this == o) return true;
//         if (o == null || getClass() != o.getClass()) return false;
// 
//         TVariable tVariable = (TVariable) o;
// 
//         return name.equals(tVariable.name);
// 
//     }
// 
//     @Override
//     public int hashCode() {
//         return ("TVariable_" + name).hashCode();
//     }
// 
//     @Override
//     public String toString() {
//         return name();
//     }
// }
// 
// Path: src/main/java/types/Type.java
// 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) {
//         throw new UnificationFailException(this, fun);
//     }
// 
//     protected Substitution unifyWith(TConstructor cons) {
//         throw new UnificationFailException(this, cons);
//     }
// 
//     public abstract Type instantiate(Substitution sub);
// }
// 
// Path: src/main/java/types/TFunction.java
// public static TFunction function(Type left, Type right) {
//     int hash = hash(left, right);
//     TFunction f = instantiated.get(hash);
//     if(f == null) {
//         f = new TFunction(left, right);
//         instantiated.put(hash, f);
//     }
//     return f;
// }


// Path: src/main/java/ast/Application.java
import inference.environements.TypeInferenceEnv;
import types.TVariable;
import types.Type;
import java.util.Arrays;
import static types.TFunction.function;

package ast;




/**
 * Represent an Application in Lambda calculus.
 */
public class Application extends Expression {
	private Expression left;
	private Expression right;

	public Application(Expression left, Expression right) {
		this.left = left;
		this.right = right;
	}


	@Override
	public Type infer(TypeInferenceEnv env) {