TypeName cataMapperTypeName(DataConstructor dc) {

    TypeName[] argsTypeNames =
        concat(
                dc.arguments().stream().map(DataArgument::type),
                dc.typeRestrictions()
                    .stream()
                    .map(TypeRestriction::idFunction)
                    .map(DataArgument::type))
            .map(t -> Utils.asBoxedType.visit(t, utils.types()))
            .map(this::substituteTypeWithRecursionVar)
            .map(TypeName::get)
            .toArray(TypeName[]::new);

    return adt.dataConstruction().isVisitorDispatch()
        ? argsTypeNames.length == 0
            ? ParameterizedTypeName.get(
                ClassName.get(FlavourImpl.findF0(context.flavour(), utils.elements())),
                TypeName.get(adt.matchMethod().returnTypeVariable()))
            : argsTypeNames.length == 1
                ? ParameterizedTypeName.get(
                    ClassName.get(FlavourImpl.findF(context.flavour(), utils.elements())),
                    argsTypeNames[0],
                    TypeName.get(adt.matchMethod().returnTypeVariable()))
                : ParameterizedTypeName.get(
                    Utils.getClassName(context, MapperDerivator.mapperInterfaceName(dc)),
                    concat(
                            concat(
                                dc.typeVariables().stream().map(TypeVariableName::get),
                                fold(
                                    MapperDerivator.findInductiveArgument(utils, adt, dc),
                                    Stream.<TypeName>of(),
                                    tm ->
                                        Stream.of(
                                            ParameterizedTypeName.get(
                                                ClassName.get(
                                                    FlavourImpl.findF0(
                                                        context.flavour(), utils.elements())),
                                                TypeName.get(
                                                    adt.matchMethod().returnTypeVariable()))))),
                            Stream.of(TypeVariableName.get(adt.matchMethod().returnTypeVariable())))
                        .toArray(TypeName[]::new))
        : TypeName.get(
            utils
                .types()
                .getDeclaredType(
                    Utils.asTypeElement.visit(dc.deconstructor().visitorType().asElement()).get(),
                    dc.deconstructor()
                        .visitorType()
                        .getTypeArguments()
                        .stream()
                        .map(this::substituteTypeWithRecursionVar)
                        .toArray(TypeMirror[]::new)));
  }
  private DeriveResult<DerivedCodeSpec> functionDispatchImpl(List<DataConstructor> constructors) {

    NameAllocator nameAllocator = nameAllocator(constructors);

    TypeElement f = FlavourImpl.findF(context.flavour(), utils.elements());
    TypeName returnType =
        TypeName.get(
            utils
                .types()
                .getDeclaredType(
                    f,
                    adt.typeConstructor().declaredType(),
                    adt.matchMethod().returnTypeVariable()));

    TypeSpec wrapper =
        TypeSpec.anonymousClassBuilder("")
            .addField(
                FieldSpec.builder(returnType, nameAllocator.get("cata"))
                    .initializer(
                        CodeBlock.builder()
                            .addStatement(
                                "$L -> $L.$L($L)",
                                nameAllocator.get("adt var"),
                                nameAllocator.get("adt var"),
                                adt.matchMethod().element().getSimpleName(),
                                Utils.joinStringsAsArguments(
                                    constructors
                                        .stream()
                                        .map(
                                            constructor ->
                                                constructor
                                                        .arguments()
                                                        .stream()
                                                        .map(DataArguments::getType)
                                                        .noneMatch(
                                                            tm ->
                                                                utils
                                                                    .types()
                                                                    .isSameType(
                                                                        tm,
                                                                        adt.typeConstructor()
                                                                            .declaredType()))
                                                    ? constructor.name()
                                                    : CodeBlock.builder()
                                                        .add(
                                                            "($L) -> $L.$L($L)",
                                                            Utils.asLambdaParametersString(
                                                                constructor.arguments(),
                                                                constructor.typeRestrictions()),
                                                            constructor.name(),
                                                            MapperDerivator.mapperApplyMethod(
                                                                utils, context, constructor),
                                                            Utils.joinStringsAsArguments(
                                                                Stream.concat(
                                                                    constructor
                                                                        .arguments()
                                                                        .stream()
                                                                        .map(
                                                                            argument ->
                                                                                utils
                                                                                        .types()
                                                                                        .isSameType(
                                                                                            argument
                                                                                                .type(),
                                                                                            adt.typeConstructor()
                                                                                                .declaredType())
                                                                                    ? "() -> this."
                                                                                        + nameAllocator
                                                                                            .get(
                                                                                                "cata")
                                                                                        + "."
                                                                                        + FlavourImpl
                                                                                            .functionApplyMethod(
                                                                                                utils,
                                                                                                context)
                                                                                        + "("
                                                                                        + argument
                                                                                            .fieldName()
                                                                                        + ")"
                                                                                    : argument
                                                                                        .fieldName()),
                                                                    constructor
                                                                        .typeRestrictions()
                                                                        .stream()
                                                                        .map(
                                                                            TypeRestriction
                                                                                ::idFunction)
                                                                        .map(
                                                                            DataArgument
                                                                                ::fieldName))))
                                                        .build()
                                                        .toString())))
                            .build())
                    .build())
            .build();

    MethodSpec cataMethod =
        MethodSpec.methodBuilder("cata")
            .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
            .addTypeVariables(
                Stream.concat(
                        adt.typeConstructor().typeVariables().stream(),
                        Stream.of(adt.matchMethod().returnTypeVariable()))
                    .map(TypeVariableName::get)
                    .collect(Collectors.toList()))
            .returns(returnType)
            .addParameters(
                constructors
                    .stream()
                    .map(
                        dc ->
                            ParameterSpec.builder(
                                    cataMapperTypeName(dc), MapperDerivator.mapperFieldName(dc))
                                .build())
                    .collect(toList()))
            .addStatement("return $L.$L", wrapper, nameAllocator.get("cata"))
            .build();

    return result(methodSpec(cataMethod));
  }