「フル」関数型プログラミング

プライマー

オブジェクト指向プログラミングでは、関数型プログラミングの必要が行うには?関数型プログラミング、美しさとは何でしょうか?

主なアイデアは、機能的です:

  • 関数型プログラミングの手順は、一連の関数の組み合わせとして考えられています。関数の引数として渡され、変数の関数として割り当てることも、それはどこでも、戻り値としての機能を返されることがあります。
  • 不変。関数型プログラミングは、渡されたオブジェクトを変更していない、返されたオブジェクトを新たに作成されます。
  • 不確実性。同じ入力は、治療後の機能、同じ出力を得る必要があります。

関数型プログラミングは、簡単にターゲットプログラムの挙動が予測可能と推論到達するために:これらの結果は、という考えにつながります。

この記事では、毎日のプログラミングの再検討に構成する「関数型プログラミング色のメガネを」かかります。キーは、それがパフォーマンスの要因、およびソフトウェア工学に対処しません、問題を見ての新しい方法です。Javaで書かれたサンプル・コード、近くにJava開発者のための読書習慣を目指しています。同時に、Javaの主な特徴が機能していないので、Javaでの限られた容量であるため、ことに留意すべきです。

基本的な構造

割り当て

割り当てプログラムは、最も基本的な動作です。ビューの機能の観点から、割り当ては、実際には定数関数です。図に示される以下のコード。int i = 100、実際には、使用することができますSupplier<Integer> f = () -> 100代わりに。値はどこにでも現れ、値プロバイダーと交換することができます。そうすることで、機能の柔軟性を向上させる:値が固定されているが、元の値を変化させることができるので:変数、関数、ファイル、ネットワーク。

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());
  }
}


条件

ビューの条件はどのように達成するための関数型プログラミングを使用します。次のコードに示すように、3つの異なる条件に応じて各戻り異なる値、非常に一般的な機能です。

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

どのようにそれを変換する関数型プログラミングを使用するには?以前、関数型プログラミング手順は一連の関数の組み合わせとして考えられると述べました。まずplainIfElseから6つの基本的な機能を抽出しました:

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;
  }

質問は以下のようになります。これらの基本的な機能とplainIfElseの組み合わせが同じ効果を得ますか?それは、簡単にIF-elseifを-他に解決さです。

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

これは、マップの構造です。それは価値が行動の関数で、キー条件関数です。次のコードをシミュレートするマップ[述語、サプライヤー]と考えることができます。

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);
  }

次は、単純にすべてのキー、条件を満たすために、最初のキーの機能を見つけて、あなたはその値を呼び出すことができます。

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 ...パーフェクトようです。


しかし、完全であれば、他にまだ解消?そして、いや。実際のFindFirst +フィルタは、暗黙的であれば、他の味が含まれています。この手段は:完全に条件を排除するが、抽象化の適切なレベルで隠されていません。

if-elseさらに反射は、2のif-then文に分割することができます。場合は、ほとんどのアトミック操作であると言うことができます。EXEC OKでない場合は、終了し、EXEC現在、OKであれば、[次へ:シーケンシャル文は、本質的には、IF-THENです。

通用IF

IF-THENアトミック動作は、機能はいくつかの便利な提供されてもよいです。

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アプリケーション機能があれば-elseifを-他に上書きされる可能性がある場合、他のネストされます:

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


循環

今、私たちは、サイクルを見てください。ここでは非常に一般的なループのコードは次のとおりです。

    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);

抜け目のないリーダーはすぐに分かるであろう:初期値と演算子を減らす:容器の2つのコード要素上の構造を減らすことがあり、唯一の違いは、ということです。この類似性を減らすためにどのようにそれを引き出さ?次のコードに示します:

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

あなたは今書くことができます。

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

シンプル十分ではないですか?私たちは、関数型プログラミングの偉大な美しさを発見しました。

実用化

角度分解基本的なプログラミング構造を表示する機能を使用した後、少し実用的なアプリケーションを見て。

パイプライン

パイプラインは、関数型プログラミングの一般的なアプリケーションです。一連のプロセスを通じて、パイプラインであるパイプライン中の素人の用語は、特定の目標を完了するために一緒に働きます。パイプラインの例では、エクスポート機能は、「 - 詳細 - フィルタ - ソート - 書式 - - 生成されたファイルアップロードファイル問い合わせ」にあります。処理一連のフィルタを介して供給者によって提供されたデータ・セット、および最終的フォーマットへの出力形式を介し:関数式パイプラインを使用する方法を示す次のコード、。

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());
  }
}


デコレーター

最後に、機能の組み合わせの力を示すために、デコレータの栗を見てください。

膜(SiNx)^ 2 +(cosx)^ 2 = 1:私も漠然と式を覚えています。あなたがプログラムを書きたいならば、それはまた、非常に簡単です:

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

私の必要性がそれをf(x)は^ 2 + G(X)^ 2である場合は?注意深い読者はここで構造体は、F(X)^ nは、こうして構造から引き出され、見出しました。FへPOWは、今、我々はF(X)= F(X)^ N + G(X)^ n個の容量のを取得し、パッケージのパワーを作りました。

  /** 将指定函数的值封装幂次函数 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);
  }

あなたは今書くことができます。double x = pow(Math::sin, 2).apply(x) + pow(Math::cos, 2).apply(x);

この+はまだ固定されていることをしてくださいノートでは、私はプラスに限られるものではない願っていますが、任意の可能な事業者が、また、単に構造にしたい:H(x)は=ホップ(f(x)が、g(x))です。このように、パラメータの関数として渡されたこの演算子+をサポートする必要があります。

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));
}

あなたは今書くことができます。

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


F(X)のため^ nは、実際には、より抽象的な形式で書くことができる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);
  }

今、私たちはより多くの柔軟性を得る、任意のコンストラクタを望んでいたことができます。

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));

短いのシンプルな機能、強力な機能を持つ関数を簡単に複雑な組み合わせ:ここでは関数型プログラミングの力を示しています。

概要

機能コードは非常に簡単に保つことができるが、短くて簡単な機能の任意の組み合わせ、高い能力を有する複合構造体の機能をプログラムします。関数型プログラミングのトレーニングによって、我々は徐々に抽象的と精製能力より強力な構文を得ることができます。

この記事を読んだ後、あなたはそれに触発されたからか?

おすすめ

転載: www.cnblogs.com/lovesqcc/p/12075147.html