Search code examples
functional-programmingfunctional-dependenciesfunctional-java

Functional Programming Dependencies: How to share dependencies between functions that aren't connected together direct


these different functions are composed together like so

f1 -> f2 -> f3 -> f4 -> f5

I need to add two new functions fx and fy around f3 like so:

f1 -> f2 -> fx -> f3 -> fy -> f4 -> f5

so I need to pre-process the argument for f3 and then post-process the output from f3

here function fx makes a change and fy reverts the change back, but fy needs additional details from fx to be able to revert, to know what values to revert to for example

the problem is that function fx needs to produce two outputs

first: output that is needed by f3
second: output that is needed by fy

Question: How to share dependencies between functions that are not connected together direct, is there a spacial way/technique to solve this?

FYI, I'm using Java8


Solution

  • I will use some type A for all function return values; you will need to sort out the real types yourself.

    Here's the intention as you described it:

    • Function fx will yield two parameters, let's call them a and b.
    • Function f3 will process the value a and yield a'.
    • Finally, fy will consume a' and b and produce c.

    Let's see how can we achieve that (I will write pseudocode for type signatures):

    1. Define fy as a function that takes two parameters and returns one:

      (A, A) => A
      
    2. Define fyp (short for "fy preprocessed") as a function that takes some preprocessing function p and performs the logic of your fy, but with first parameter preprocessed with p. We will write this in the form P => FY, where P is the type signature of the preprocessing function, and FY is the type signature of fy:

      P => FY, which expands to  
      (A => A) => (A, A) => A
      

      So the implementation of this function should take the preprocessing function p of type A => A as input, and perform the logic of fy (this is the right hand side, (A, A) => A... note how it corresponds to the signature of fy), but before performing the logic, it will map the first parameter of fy with p. Here's some Scala implementation for reference (I'm not so good with Java):

      val fyp: (A => A) => ((A, A)) => A =              
        f => fyuParams => performFyLogic((f(fyuParams._1), fyuParams._2))  
      

      where performFyLogic is the actual processing logic of your fy function.

    3. Define the final composition as:

      f1 -> f2 -> fx -> fyp(f3)
      

    Here's full Scala implementation for reference (again, don't know Java 8 that well):

    val f1: Int => Int = _ + 1
    val f2: Int => Int = _ + 2
    val f3: Int => Int = _ + 3
    
    val fx: Int => (Int, Int) = i => (i + 4, i + 5)
    
    // let's assume that the original fy simply sums up its two parameters
    val fyp: (Int => Int) => ((Int, Int)) => Int =
      f => fyArgs => f(fyArgs._1) + fyArgs._2
    
    val composed1 = f1 andThen f2 andThen f3
    println(composed1(42)) // 48
    
    val composed2 = f1 andThen f2 andThen fx andThen fyp(f3)
    println(composed2(42)) // 102
    
    // because:
    // f1 -> f2 -> fx = (42 + 1 + 2 + 4, 41 + 1 + 2 + 5) = (49, 50)
    // fyp(f3)((49, 50)) = (49 + 3) + 50 = 102