public StructType bind(StructType inputSchema) {
        if (binding != null && binding.get().inputSchema == inputSchema) {
            return binding.get().xschema;
        }

        Formula formula = expand(inputSchema);

        Binding binding = new Binding();
        binding.inputSchema = inputSchema;

        List<Feature> features = Arrays.stream(formula.predictors)
                .filter(predictor -> !(predictor instanceof Delete) && !(predictor instanceof Intercept))
                .flatMap(predictor -> predictor.bind(inputSchema).stream())
                .collect(Collectors.toList());

        binding.x = features.toArray(new Feature[0]);
        binding.xschema = DataTypes.struct(
                features.stream()
                     .map(Feature::field)
                     .toArray(StructField[]::new)
        );

        if (response != null) {
            try {
                features.addAll(0, response.bind(inputSchema));
                binding.yx = features.toArray(new Feature[0]);
                binding.yxschema = DataTypes.struct(
                        features.stream()
                             .map(Feature::field)
                             .toArray(StructField[]::new)
                );
            } catch (IllegalArgumentException ex) {
                logger.debug("The response variable {} doesn't exist in the schema {}", response, inputSchema);
            }
        }

        this.binding = new ThreadLocal<>() {
            protected synchronized Binding initialValue() {
                return binding;
            }
        };
        return binding.xschema;
    }
@Test
    public void testBind() {
        System.out.println("bind");

        Formula formula = Formula.of("revenue", dot(), cross("water", "sowing_density") , mul("humidity", "wind"), delete("wind"));
        StructType inputSchema = DataTypes.struct(
                new StructField("revenue", DataTypes.DoubleType, Measure.Currency),
                new StructField("water", DataTypes.ByteType, new NominalScale("dry", "wet")),
                new StructField("sowing_density", DataTypes.ByteType, new NominalScale("low", "high")),
                new StructField("humidity", DataTypes.FloatType, Measure.Percent),
                new StructField("wind", DataTypes.FloatType)
        );
        assertEquals("revenue ~ . + (water x sowing_density) + (humidity * wind) - wind", formula.toString());
        System.out.println(formula.expand(inputSchema));

        StructType outputSchema = formula.bind(inputSchema);
        StructType schema = DataTypes.struct(
                new StructField("humidity", DataTypes.FloatType, Measure.Percent),
                new StructField("water", DataTypes.ByteType, new NominalScale("dry", "wet")),
                new StructField("sowing_density", DataTypes.ByteType, new NominalScale("low", "high")),
                new StructField("water:sowing_density", DataTypes.IntegerType, new NominalScale("dry:low", "dry:high", "wet:low", "wet:high")),
                new StructField("humidity * wind", DataTypes.FloatType)
        );
        assertEquals(schema, outputSchema);
    }