Functional programming - application of procedures as return values: step-by-step procedure

The previous article mentioned that one of the four properties of first-class functions in functional programming is "the process can be used as a return value", but this is not as good as "the process can be used as a parameter" in actual use (high order function) is used more often. This article introduces an application of this property to step functions.

(Note: This article is intended to introduce a programming technique, hoping to give readers some inspiration. It does not introduce the optimal solution to a certain type of problem. Actual use requires detailed analysis of specific problems)

problem scenario

I believe everyone has been exposed to a type of need: to complete a certain task x. This task . Edit and optimize photos; 3. Save photos to the album. So the simplest form of this type of function is:

class Demo{
    
    
    public static void main(String[] args) {
    
    
        taskX();
    }
    
    static void taskX(){
    
    
        //1. 执行第一步
        System.out.println("step 1...");
        //1. 执行第二步
        System.out.println("step 2...");
        //1. 执行第三步
        System.out.println("step 3...");
    }
}

However, sometimes we do not require these three steps to be completed in one go. We allow you to do other things after completing each step, and then come back to do the remaining work. Or perhaps one of the steps cannot be executed directly in some cases and requires some preparation work. For example, in the photo-taking example mentioned above, calling the camera on the Android platform requires camera permissions, and then saving the photo album requires file access permissions. This type of behavior is related to the system platform. If we want a set of code to run on multiple platforms, we have to separate core functions and platform-related functions.

One way is to directly write the three steps into three sub-processes:

class Demo {
    
    
    public static void main(String[] args) {
    
    
        TaskX.step1();
        System.out.println("do other task....");
        TaskX.step2();
        System.out.println("do other task2....");
        TaskX.step3();
    }
}

class TaskX{
    
    
    static void step1() {
    
    
        System.out.println("step 1...");
    }

    static void step2() {
    
    
        System.out.println("step 1...");
    }

    static void step3() {
    
    
        System.out.println("step 1...");
    }
}

But this is equivalent to directly exposing three sub-steps. The three steps can be called externally in any order, and if the execution of step2 depends on step1 If the execution is successful, it may cause problems and require additional documentation/comments to explain. Even with comments, security cannot be guaranteed. (This assumes that the person who wrote the TaskX module is not the same person who uses it)

What if we could express a process like this:

主过程:
    1. 子过程1
    2. 子过程2
    3. 子过程3

And the execution of these sub-processes is performed step by step, then this situation can be handled.

Implementation of step-by-step process

If we write the type of a procedure with no parameters and no return value as () -> void, then can the type of step-by-step function be written as () -> -> -> void, which means It can be executed in three steps. This representation with a few more elements is () -> () -> () -> void, which is very similar to the currying form, right? The next step is to take advantage of this.

Such a process is easy to express in Scala:

val taskX = () => {
    
    
  println("step 1...")
  () => {
    
    
    println("step 2...")
    () => {
    
    
      println("step 3...")
    }
  }
}

But in Java, we have to write:

class Demo {
    
    
    public static void main(String[] args) {
    
    
        Supplier<Supplier<Runnable>> taskXSupplier = doTaskX();
        Supplier<Runnable> step2Supplier = taskXSupplier.get();//执行step1
        System.out.println("do other task....");
        Runnable step3 = step2Supplier.get();//执行step2
        System.out.println("do other task....");
        step3.run();执行step3
    }

    static Supplier<Supplier<Runnable>> doTaskX() {
    
    
        return () -> {
    
    
            System.out.println("step 1...");
            return () -> {
    
    
                System.out.println("step 2...");
                return () -> System.out.println("step 3...");
            };
        };
    }
}

The caller's code is too ugly, so we add a few functional interfaces to beautify it a little:

class Demo {
    
    
    public static void main(String[] args) {
    
    
        Step1 step1 = doTaskX();
        Step2 step2 = step1.run();//执行step1
        System.out.println("do other task....");
        Step3 step3 = step2.run();//执行step2
        System.out.println("do other task....");
        step3.run();//执行step3
    }

    static Step1 doTaskX() {
    
    
        return () -> {
    
    
            System.out.println("step 1...");
            return () -> {
    
    
                System.out.println("step 2...");
                return () -> System.out.println("step 3...");
            };
        };
    }

    public interface Step1 {
    
    
        Step2 run();
    }

    public interface Step2 {
    
    
        Step3 run();
    }

    public interface Step3 extends Runnable {
    
    

    }
}

It is quite troublesome to write interface definition every time. We can pre-define Step1, Step2Step10, When writing a process like this, if you want to divide it into several steps, just choose the interface of the corresponding type.

Simplify writing step-by-step procedures

If we don’t want to define an interface, we can write such a toolMultiStepTask:

class MultiStepTask {
    
    
    private final Iterator<Runnable> stepIterator;

    private MultiStepTask(List<Runnable> stepList) {
    
    
        stepIterator = stepList.iterator();
    }

    public static MultiStepTask create(Runnable... actions) {
    
    
        List<Runnable> stepList = Arrays.stream(actions).toList();
        return new MultiStepTask(stepList);
    }

    public void doNext() {
    
    
        if (stepIterator.hasNext()) {
    
    
            stepIterator.next().run();
        }
    }

    public void doComplete() {
    
    
        while (stepIterator.hasNext()) {
    
    
            stepIterator.next().run();
        }
    }

    public boolean isCompleted(){
    
    
        return !stepIterator.hasNext();
    }
}

So our code for creating a step-by-step process becomes:

static MultiStepTask taskX() {
    
    
    return MultiStepTask.create(
            () -> System.out.println("step 1..."),
            () -> System.out.println("step 2..."),
            () -> System.out.println("step 3...")
    );
}

Then the calling code is:

class Demo {
    
    
    public static void main(String[] args) {
    
    
        MultiStepTask taskX = taskX();
        taskX.doNext();//执行step1
        System.out.println("do other task....");
        taskX.doNext();//执行step2
        System.out.println("do other task....");
        taskX.doNext();//执行step3
    }
}

Supongo que te gusta

Origin blog.csdn.net/zssrxt/article/details/132132414
Recomendado
Clasificación