javagenericsfunctional-programmingvavr

Either flatmap type mix-up in Vavr


Sample available here: https://github.com/codependent/hexapp-java/blob/vavr/src/main/java/com/codependent/hexapp/application/port/in/impl/CreateDepartmentUseCaseImpl.java

I have chained Either operations that require the usage of Either.flatmap:

    @Override
    public Either<? extends ApplicationError, Department> createDepartment(CreateDepartmentCommand command) {
        Either<? extends ApplicationError, Department> department = Department.create(command.id(), command.name());
        Either<? extends ApplicationError, Department> createdDepartment = department.flatMap((Department dep) -> {
            Optional<Department> existingDepartment = getDepartmentDrivenPort.getByName(command.name());
            if (existingDepartment.isPresent()) {
                Either<? extends ApplicationError, Department> left = Either.left(new DepartmentExistsError());
                return left;
            } else {
                Either<? extends ApplicationError, Department> right = createDepartmentDrivenPort.create(dep);
                return right;
            }
        });        
         
        return createdDepartment;
    }

This code shows this compilation error:

java: method flatMap in interface io.vavr.control.Either<L,R> cannot be applied to given types;
  required: java.util.function.Function<? super com.codependent.hexapp.application.domain.Department,? extends io.vavr.control.Either<capture#1 of ? extends com.codependent.hexapp.application.domain.error.ApplicationError,? extends U>>
  found:    (Departmen[...]; } }
  reason: cannot infer type-variable(s) U
    (argument mismatch; bad return type in lambda expression
      io.vavr.control.Either<capture#2 of ? extends com.codependent.hexapp.application.domain.error.ApplicationError,com.codependent.hexapp.application.domain.Department> cannot be converted to io.vavr.control.Either<capture#1 of ? extends com.codependent.hexapp.application.domain.error.ApplicationError,? extends U>)

I can't see whats the problem here since U is Department...

I also tried autogenerating the Function using IntelliJ's assistant but this code doesn't compile either:

        department.flatMap(new Function<Department, Either<? extends ApplicationError, ?>>() {
            @Override
            public Either<? extends ApplicationError, ?> apply(Department department) {
                return null;
            }
        });

java: method flatMap in interface io.vavr.control.Either<L,R> cannot be applied to given types;
  required: java.util.function.Function<? super com.codependent.hexapp.application.domain.Department,? extends io.vavr.control.Either<capture#1 of ? extends com.codependent.hexapp.application.domain.error.ApplicationError,? extends U>>
  found:    <anonymous java.util.function.Function<com.codependent.hexapp.application.domain.Department,io.vavr.control.Either<? extends com.codependent.hexapp.application.domain.error.ApplicationError,?>>>
  reason: cannot infer type-variable(s) U
    (argument mis

Solution

  • I would suggest to get rid of all the wildcards for the left side of the Either, as they seem unnecessary to me. It just needlessly complicates your types without adding any real benefit.

    The issue in your code is that ? extends SomeType is not assignment compatible with ? extends Sometype, which makes sense, since an unknown subtype of some type is not necessarily assignment compatible with an unknown subtype of the same type. While the base type in both cases is the same, the unknown subtype might be different, hence the resulting incompatibility and compilation error.

    If you transform your code in the following way (with the necessary changes in related code), it will compile:

    @Override
    public Either<ApplicationError, Department> createDepartment(CreateDepartmentCommand command) {
        Either<ApplicationError, Department> department = Department.create(command.id(), command.name());
        Either<ApplicationError, Department> createdDepartment = department.flatMap((Department dep) -> {
            Optional<Department> existingDepartment = getDepartmentDrivenPort.getByName(command.name());
            if (existingDepartment.isPresent()) {
                Either<ApplicationError, Department> left = Either.left(new DepartmentExistsError());
                return left;
            } else {
                Either<ApplicationError, Department> right = Either.right(createDepartmentDrivenPort.create(dep));
                return right;
            }
        });
    
        return createdDepartment;
    }