Search code examples
javalambdajava-8java-streamfunctional-interface

Java 8 Composition, Currying shorthand


I have a stream of BusinessObjects, I need to set a value on each object, I want to use Stream.map but map takes a Function<T,R> and I have the target object, a discriminator value, and the new value. setNewValueInBusinessObjectExample shows what I want to do and setNewValueInBusinessObjectWithFun is what I need help with.

Agreed! I can just use setNewValueInBusinessObjectExample on the map but I want to see how the functional style looks like. Thanks

class BusinessObject {
    String firstField;
    String secondField;
}

class SomeDiscriminator {
    String value;
}

BusinessObject setNewValueInBusinessObjectExample(BusinessObject businessObject, 
     SomeDiscriminator discriminator, String newValue) {

    if(discriminator.value.equals("firstField")) {
        businessObject.firstField = newValue;
    } else {//secondField
        businessObject.secondField = newValue;
    }

    return businessObject;
}

Function<BusinessObject, Function<SomeDiscriminator, Function<String, BusinessObject>>> 
       setNewValueInBusinessObjectWithFun = {
    /* todo: using nested Function<T,R> */
}

Solution

  • If you are in doubt with the construction and usage of functional interfaces, I recommend you to expand the whole thing to anonymous classes where the structure becomes obvious.

    I also noticed the whole flow uses three parameters, the same your setNewValueInBusinessObjectExample does. Thus move the body of the method to the innermost anonymous class.

    Function<BusinessObject, Function<SomeDiscriminator, Function<String, BusinessObject>>> setNewValueInBusinessObjectWithFun =
        new Function<BusinessObject, Function<SomeDiscriminator, Function<String, BusinessObject>>>() {
            @Override
            public Function<SomeDiscriminator, Function<String, BusinessObject>> apply(final BusinessObject businessObject) {
                return new Function<SomeDiscriminator, Function<String, BusinessObject>>() {
                    @Override
                    public Function<String, BusinessObject> apply(final SomeDiscriminator someDiscriminator) {
                        return new Function<String, BusinessObject>() {
                            @Override
                            public BusinessObject apply(final String newValue) {
                                if (someDiscriminator.value.equals("firstField")) {
                                    businessObject.firstField = newValue;
                                } else {//secondField
                                    businessObject.secondField = newValue;
                                }
                                return businessObject;
                            }
                        };
                    }
                };
            }
        };
    

    Now, pack the whole thing to lambda expressions and see what happens:

    Function<BusinessObject, Function<SomeDiscriminator, Function<String, BusinessObject>>> setNewValueInBusinessObjectWithFun = 
        businessObject -> someDiscriminator -> newValue -> {
            if (someDiscriminator.value.equals("firstField")) {
                businessObject.firstField = newValue;
            } else {//secondField
                businessObject.secondField = newValue;
            }
            return businessObject;
        };
    

    For sake of clarity, name the variables inside the lambdas expression correctly, otherwise you would not be able to work with them well. The usage is fairly simple (I moved setters to constructor for sake of brevity:

    BusinessObject businessObject = new BusinessObject("oldValue");
    setNewValueInBusinessObjectWithFun
        .apply(businessObject)                                  // apply to an object
        .apply(new SomeDiscriminator("firstField"))             // finds its field
        .apply("newValue");                                     // sets a new value
    

    However, I recommend you to define a custom @FunctionalInterface with more straightforward definition...

    @FunctionalInterface
    interface MyFunction<T, R, U> {
        T apply(T t, R r, U u);
    }
    

    ... and usage ...

    MyFunction<BusinessObject, SomeDiscriminator, String> myFunction = 
        (businessObject, someDiscriminator, newValue) -> {
            if (someDiscriminator.value.equals("firstField")) {
                businessObject.firstField = newValue;
            } else {
                businessObject.secondField = newValue;
            }
            return businessObject;
        };
    
    BusinessObject businessObject = new BusinessObject("oldValue");
    myFunction.apply(businessObject, new SomeDiscriminator("firstField"), "newValue");