"Full" Functional Programming

Primer

With object-oriented programming, functional programming need to do? Functional Programming, what is beauty?

The main idea is functional:

  • Functional programming procedure is considered as a combination of a series of functions. May be assigned as a function of variables, passed as function arguments, it may be returned as a return value, the function everywhere.
  • Immutable. Functional programming does not alter the object passed, the returned object is newly created.
  • Uncertainty. The same input, functional after treatment, must obtain the same output.

These results lead to the idea that: functional programming, easier to reach the target program behavior predictable and inference.

This article will take "functional programming colored glasses" to re-examine the structure of daily programming. Key is a new way of looking at the problem, it will not address the performance factor, and software engineering. The sample code written in Java, which aims to close Java developer's reading habits. At the same time, it should be noted that, since the main characteristic of Java is not functional, and therefore is limited capacity in Java.

basic structure

Assignment

Assignment program is the most basic behavior. From a function point of view, the assignment is actually a constant function. The following code shown in FIG. int i = 100, In fact, can be used Supplier<Integer> f = () -> 100instead. Values appear anywhere, can be replaced with a value provider. In doing so, enhance the flexibility of function: because the values are fixed, but the value of the source can be varied: variables, functions, files, networks.

public class Assignment {

  public static void main(String[]args) {
    int i = 100;
    System.out.println(i);

    Supplier<Integer> f = () -> 100;
    System.out.println(f.get());
  }
}


condition

Conditions of view how to use functional programming to achieve. As shown in the following code, it is a very common function, each return different values ​​according to three different conditions.

public static Integer plainIfElse(int x) {
    if (x > 0) {
      return 1;
    }
    else if (x < 0) {
      return -1;
    }
    else {
      return 0;
    }
  }

How to use functional programming to transform it? Said earlier, functional programming procedure is considered as a combination of a series of functions. First extracted from plainIfElse the six basic functions:

public static boolean ifPositive(int x) {
    return x > 0;
  }

  public static boolean ifNegative(int x) {
    return x < 0;
  }

  public static boolean ifZero(int x) {
    return x == 0;
  }

  public static Integer positiveUnit() {
    return 1;
  }

  public static Integer negativeUnit() {
    return -1;
  }

  public static Integer zero() {
    return 0;
  }

The question now is: how combinations of these basic functions and plainIfElse get the same effect? It is easy, the if-elseif-else resolves to:

if (A) { actA }
if (B) { actB }
if (C) { actC }

This is a Map structure. It is a key condition function, value is a function of behavior. It can be considered a Map [Predicate, Supplier] to simulate the following code:

public static Supplier<Integer> mapFunc(int x) {
    Map<Predicate<Integer>, Supplier<Integer>> condMap = new HashMap<>();
    condMap.put(Condition::ifPositive, Condition::positiveUnit);
    condMap.put(Condition::ifNegative, Condition::negativeUnit);
    condMap.put(Condition::ifZero, Condition::zero);
    return travelWithGeneric(condMap, x);
  }

Next, simply through all the key, find the first key function to meet the conditions, you can then call value:

public static <T,R> Supplier<R> travelWithGeneric(Map<Predicate<T>, Supplier<R>> map, T x) {
    return map.entrySet().stream().filter((k) -> k.getKey().test(x)).findFirst().map((k) -> k.getValue()).get();
  }

Emmm ... Seems Perfect .


However, completely eliminating the if-else yet? not at all. Indeed filter + findFirst implicitly contains the if-else taste. This means: not completely eliminate the condition, but hidden at the appropriate level of abstraction.

Further reflection, if-else can be split into two if-then statements. if-then it can be said to be the most atomic operation. Sequential statements, in essence, is the if-then: if exec current ok, then next; if exec not ok, exit.

Spoken IF

Since the if-then an atomic operation, the function may be provided several convenient:

public class CommonIF {

  public static <T, R> R ifElse(Predicate<T> cond, T t, Function<T, R> ifFunc, Supplier<R> defaultFunc ) {
    return cond.test(t) ? ifFunc.apply(t) : defaultFunc.get();
  }

  public static <T, R> R ifElse(Predicate<T> cond, T t, Supplier<R> ifSupplier, Supplier<R> defaultSupplier ) {
    return cond.test(t) ? ifSupplier.get() : defaultSupplier.get();
  }

  public static <T, R> Supplier<R> ifElseReturnSupplier(Predicate<T> cond, T t, Supplier<R> ifSupplier, Supplier<R> defaultSupplier ) {
    return cond.test(t) ? ifSupplier : defaultSupplier;
  }

  public static <T> void ifThen(Predicate<T> cond, T t, Consumer<T> action) {
    if (cond.test(t)) {
      action.accept(t);
    }
  }

  public static <T> boolean alwaysTrue(T t) {
    return true;
  }
}

CommonIF application function may be overridden if-elseif-else is nested if-else:

public static Supplier<Integer> ifElseWithFunctional(int x) {
    return CommonIF.ifElseReturnSupplier(Condition::ifPositive, x,
                                         Condition::positiveUnit,
                                         CommonIF.ifElseReturnSupplier(Condition::ifNegative, x, Condition::negativeUnit, Condition::zero ) );
  }


cycle

Now, we look at the cycle. Here is a very common loop code:

    Integer sum = 0;
    List<Integer> list = Arrays.asList(1,2,3,4,5);
    for (int i=0; i < list.size(); i++) {
      sum += list.get(i);
    }
    System.out.println(sum);

    Integer multiply = 1;
    for (int i=0; i < list.size(); i++) {
      multiply *= list.get(i);
    }
    System.out.println(multiply);

The astute reader will soon be seen: the structure of the above two code elements of the container are to be reduce, only difference is that: an initial value and reduce operator. How to reduce this similarity pulled out of it? The following code shows:

public static <T> T reduce(List<T> list, BinaryOperator<T> bifunc, Supplier<T> init) {
    return list.stream().reduce(init.get(), bifunc);
}

You can now write:

System.out.println("func sum:" + reduce(list, (x,y) -> x+y, () -> 0));
System.out.println("func multiply: " + reduce(list, (x,y) -> x*y, () -> 1));

Is not simple enough? We found a great beauty of functional programming.

Practical application

After using the function of viewing angle resolved basic programming structure, look a little practical application.

PipeLine

PipeLine is a typical application of functional programming. PipeLine In layman's terms, that is a pipeline, through a series of processes work together to complete a certain goal. For example, the export function is to "inquire - Details - Filter - Sorting - Formatting - generated file - upload file" in the pipeline. The following code, showing how to use a function formula PipeLine: data set provided by supplier, through a series of processing filters, and finally through the output format to format.

public class PipeLine {

  public static void main(String[] args) {
    List<String> result = pipe(PipeLine::supplier, Arrays.asList(PipeLine::sorter, PipeLine::uniq), PipeLine::format);
    System.out.println(result);
  }

  public static <T,R> R pipe(Supplier<List<T>> supplier, List<Function<List<T>, List<T>>> filters,
                                   Function<List<T>,R> format) {
    List<T> result = supplier.get();
    for (Function<List<T>, List<T>> filter: filters) {
      result = filter.apply(result);
    }
    return format.apply(result);
  }


  public static List<String> supplier() {
    return Arrays.asList("E20191219221321025200001", "E20181219165942035900001", "E20181219165942035900001", "E20191119165942035900001");
  }

  public static List<String> sorter(List<String> list) {
    Collections.sort(list);
    return list;
  }

  public static List<String> uniq(List<String> list) {
    return list.stream().distinct().collect(Collectors.toList());
  }

  public static List<String> format(List<String> list) {
    return list.stream().map(
        (s) -> s + " " + s.substring(1,5) + " " + s.substring(6,8) + ":" + s.substring(9,11) + ":" + s.substring(12,14)
    ).collect(Collectors.toList());
  }
}


Decorator

Finally, look at a decorator chestnut, to show the power of combination of function.

I also vaguely remember a formula: (sinx) ^ 2 + (cosx) ^ 2 = 1. If you want to write a program, it is also very easy:

double x = Math.pow(sin(x),2) + Math.pow(cos(x), 2); 

If I need is f (x) ^ 2 + g (x) ^ 2 it? Careful readers found out, the structure here is f (x) ^ n, thus pulled out of the structure. pow to f made a power of the package, now we get F (x) = f (x) ^ n + g (x) ^ n of capacity.

  /** 将指定函数的值封装幂次函数 pow(f, n) = (f(x))^n */
  public static <T> Function<T, Double> pow(final Function<T,Double> func, final int n) {
    return x -> Math.pow(func.apply(x), (double)n);
  }

You can now write:double x = pow(Math::sin, 2).apply(x) + pow(Math::cos, 2).apply(x);

Please note that this + is still fixed, I hope not limited to the plus, but any possible operators, but also just want to structure: H (x) = Hop (f (x), g (x)). Thus, the need to support this operator +, passed as a function of parameters:

public static <T> Function<BiFunction<T,T,T>, Function<T,T>> op(Function<T,T> funcx, Function<T,T> funcy) {
    return opFunc -> aT -> opFunc.apply(funcx.apply(aT), funcy.apply(aT));
}

You can now write:

 Function<Double,Double> sumSquare = op(pow(Math::sin, 2), pow(Math::cos, 2)).apply((a,b)->a+b);
System.out.println(sumSquare.apply(x));


For f (x) ^ n, in fact, can be written in a more abstract form: f (g (x)) = y -> f (y), y = x -> g (x):

/** 将两个函数组合成一个叠加函数, compose(f,g) = f(g) */
  public static <T> Function<T, T> compose(Function<T,T> funcx, Function<T,T> funcy) {
    return x -> funcx.apply(funcy.apply(x));
  }

  /** 将若干个函数组合成一个叠加函数, compose(f1,f2,...fn) = f1(f2(...(fn))) */
  public static <T> Function<T, T> compose(Function<T,T>... extraFuncs) {
    if (extraFuncs == null || extraFuncs.length == 0) {
      return x->x;
    }
    return x -> Arrays.stream(extraFuncs).reduce(y->y,  FunctionImplementingDecrator::compose).apply(x);
  }

Now, we get more flexibility, can any constructors wanted:

Function<Double,Double> another = op(compose((d)->d*d, Math::sin), compose((d)->d*d, Math::cos)).apply((a,b)->a+b);
System.out.println(another.apply(x));

Function<Double,Double> third = compose(d->d*d, d->d+1, d->d*2, d->d*d*d); // (2x^3+1)^2
System.out.println(third.apply(3d));

Here shows the power of functional programming: a simple function of short, easily a complex combination of functions with powerful functions.

summary

Functional programming any combination of short and simple function, a function of the composite structure having high capability, while the code can be kept very simple. By functional programming training, we can gradually gain more powerful constructs abstract and refining capacity.

After reading this article, whether from you inspired by it?

Guess you like

Origin www.cnblogs.com/lovesqcc/p/12075147.html