2017-05-27 10 views
6

ラムダを楽しみながら実験しようとしています。ラムダの構成を可能にするファンクタを作成しました。しかし、合成の手段は線形変換のみを許し、分岐を許さない。他のラムダのラムダを構成するときの分岐

私は、将来的に効果的に不変の状態のデータ構造を持つことを知っています。状態から値を抽出する変換を作成したいと思います。変換を実行するために状態を必要とするか、または必要としない一連のステップを実行する。

この目的のために、私は2つのクラスを作成します。 java.util.function.Functionのように機能するが、andThenメソッドでBiFunctionをとり、状態パラメータをラムダからラムダに渡すことができる機能的インタフェースです。

import java.util.Objects; 
import java.util.function.BiFunction; 

@FunctionalInterface 
public interface Procedure<S, T> { 

    T procede(S stateStructure); 

    default <R> Procedure<S, R> andThen(BiFunction<S, T, R> after) { 
     Objects.requireNonNull(after); 
     return (param) -> after.apply(param, procede(param)); 
    } 
} 

ファンクタは、2つのマッピング関数(状態を利用するもの、としないもの)、及び(再び、と状態なし)変換を確定2つの終端方法を有する、非常に簡単です。

import java.util.function.BiConsumer; 
import java.util.function.BiFunction; 
import java.util.function.Consumer; 
import java.util.function.Function; 

public class ProcedureContainer<S, T> { 

    protected final Procedure<S, T> procedure; 

    protected ProcedureContainer(final Procedure<S, T> procedure) { 
     this.procedure = procedure; 
    } 

    public static <S, R> ProcedureContainer<S, R> initializeContainer(
      final Function<S, R> initialDataRetriever) { 

     return new ProcedureContainer<>(initialDataRetriever::apply); 
    } 

    public <R> ProcedureContainer<S, R> map(final BiFunction<S, T, R> mapper) { 
     return new ProcedureContainer<>(procedure.andThen(mapper)); 
    } 

    public <R> ProcedureContainer<S, R> map(final Function<T, R> mapper) { 
     BiFunction<S, T, R> subMapper = 
       (ignored, stagedData) -> mapper.apply(stagedData); 
     return new ProcedureContainer<>(procedure.andThen(subMapper)); 
    } 

    public Consumer<S> terminate(final BiConsumer<S, T> consumer) { 
     return (param) -> consumer.accept(param, procedure.procede(param)); 
    } 

    public Consumer<S> terminate(final Consumer<T> consumer) { 
     return (param) -> consumer.accept(procedure.procede(param)); 
    } 
} 

簡単に(不自然な)例:

StateStruct state = new StateStruct(); 
state.setJson("{\"data\":\"meow, meow, I'm a cow\"}"); 
state.setRequestedField("data"); 

Consumer<StateStruct> consumer = ProcedureContainer 
    .initializeContainer(SateStruct::getJson) 
    .map(JSONObject::new) 
    .map((state, jsonObj) -> jsonObject.getString(state.getRequsetedField())) 
    .terminate(System.out::singLoudly); 

consumer.accept(state); 

誰もが、私は最終消費者の実行時に条件分岐を可能にするProcedureContainerbranchメソッドを実装する方法の任意のアイデアを持っています。私はmapterminateBranchメソッドを持つ新しいBranchProcedureContainerを作成することによって試みられてきた

StateStruct state = new StateStruct(); 
state.setJson("{\"data\":\"meow, meow, I'm a cow\"}"); 
state.setRequestedField("data"); 
state.setDefaultMessage("There is no data... only sheep"); 

Consumer<StateStruct> consumer = ProcedureContainer 
    .initializeContainer(SateStruct::getJson) 
    .map(JSONObject::new) 

    .branch((state, jsonObj) -> !jsonObject.getString(state.getRequsetedField())) 
    .terminateBranch((state, json) -> System.out.lament(state.getDefaultMessage())) 

    .map((state, jsonObj) -> jsonObject.getString(state.getRequsetedField())) 
    .terminate(System.out::singLoudly); 

consumer.accept(state); 

:私は、この例の作業になるだろう何かを考えています。この問題は、ブランチだけが実行されるように2つのブランチをマージする方法がわかりません。

新しいクラスの作成や既存のクラスへのメソッドの追加に制限はありません。

+1

このようなご心配はありませんでした。私も本当にこれに興味がありました。少なくとも私は投票できるのです。 – Eugene

答えて

1

解決策をまとめることができました。しかし、私はそれが特にエレガントではありません。ですので、他のソリューション(またはこのソリューションのより直感的なバージョン)をお寄せください。

私はもともと、特定のブランチが使用されたかどうかを示すブール値を含む状態コンテナを作成しようとしました。状態が正しく回らないので、これは無駄でした。だから、代わりに私は価値のコンテナを作成しました:

class ValueContainer<T> { 

    private final T value; 
    private final Boolean terminated; 

    private ValueContainer(final T value, final Boolean terminated) { 
     this.value = value; 
     this.terminated = terminated; 
    } 

    public static <T> ValueContainer<T> of(final T value) { 
     return new ValueContainer<>(value, false); 
    } 

    public static <T> ValueContainer<T> terminated() { 
     return new ValueContainer<>((T) null, true); 
    } 

    //...getters 
} 

私は、新しいコンテナを利用するために、機能のインタフェースを書き直し:

@FunctionalInterface 
public interface Procedure<S, T> { 

    ValueContainer<T> procede(S stateStructure); 
} 

ValueContainerの追加により、私はしたくありませんでしたユーザーは各メソッドの値をアンラップする必要があります。したがって、コンテナを考慮してデフォルトのメソッドが拡張されました。さらに、プロシージャーが未使用/終了分岐の一部である場合に対処するためのロジックが追加されました。そこから

default <R> Procedure<S, R> andThen(BiFunction<S, T, R> after) { 
    Objects.requireNonNull(after); 
    return (param) -> { 
     ValueContainer<T> intermediateValue = procede(param); 
     if (intermediateValue.isTerminated()) 
      return ValueContainer.<R>terminated(); 
     R returnValue = after.apply(param, intermediateValue.getValue()); 
     return ValueContainer.of(returnValue); 
    }; 
} 

、私はちょうどbranch方法を有することがProcedureContainerを展開していました。 ValueContainerと終了分岐の場合を考慮して終了方法を修正しました。

public class ProcedureContainer<S, T> { 

    protected final Procedure<S, T> procedure; 

    protected ProcedureContainer(final Procedure<S, T> procedure) { 
     this.procedure = procedure; 
    } 

    public static <S, R> ProcedureContainer<S, R> initializeContainer(
      final Function<S, R> initialDataRetriever) { 

     Procedure<S, R> initializer = (paramContainer) -> { 
      R initialValue = initialDataRetriever.apply(paramContainer); 
      return ValueContainer.of(initialValue); 
     }; 
     return new ProcedureContainer<>(initializer); 
    } 

    public <R> ProcedureContainer<S, R> map(final BiFunction<S, T, R> mapper) { 
     return new ProcedureContainer<>(procedure.andThen(mapper)); 
    } 

    public BranchProcedureContainer<S, T, T> branch(final BiPredicate<S, T> predicate) { 

     return BranchProcedureContainer.branch(procedure, predicate); 
    } 

    public Consumer<S> terminate(final BiConsumer<S, T> consumer) { 
     return (param) -> { 
      ValueContainer<T> finalValue = procedure.procede(param); 
      if (finalValue.isTerminated()) 
       return; 

      consumer.accept(param, finalValue.getValue()); 
     }; 
    } 
} 

(以下のコードは、オーバーロードされたメソッドをオフ葉)分岐方法は、分岐の構築処理する新しいクラス、BranchProcedureContainerを返します。このクラスは、ProcedureContainerの新しいインスタンスに結びつくendBranchメソッドを持っています。 (ここでも、オーバーロードされたメソッドがオフに残っている)

public class BranchProcedureContainer<S, T, R> { 

    private final Procedure<S, T> baseProcedure; 
    private final BiPredicate<S, T> predicate; 
    private final BiFunction<S, ValueContainer<T>, ValueContainer<R>> branchProcedure; 

    private BranchProcedureContainer(
      final Procedure<S, T> baseProcedure, 
      final BiPredicate<S, T> predicate, 
      final BiFunction<S, ValueContainer<T>, ValueContainer<R>> branchProcedure) { 

     this.baseProcedure = baseProcedure; 
     this.predicate = predicate; 
     this.branchProcedure = branchProcedure; 
    } 

    protected static <S, T> BranchProcedureContainer<S, T, T> branch(
      final Procedure<S, T> baseProcedure, 
      final BiPredicate<S, T> predicate) { 

     return new BranchProcedureContainer<>(baseProcedure, predicate, (s, v) -> v); 
    } 

    public <RR> BranchProcedureContainer<S, T, RR> map(
      final BiFunction<S, R, RR> mapper) { 

     BiFunction<S, ValueContainer<T>, ValueContainer<RR>> fullMapper = (s, vT) -> { 
      if (vT.isTerminated()) 
       return ValueContainer.<RR>terminated(); 

      ValueContainer<R> intermediateValue = branchProcedure.apply(s, vT); 
      if (intermediateValue.isTerminated()) 
       return ValueContainer.<RR>terminated(); 

      RR finalValue = mapper.apply(s, intermediateValue.getValue()); 
      return ValueContainer.of(finalValue); 
     }; 
     return new BranchProcedureContainer<>(baseProcedure, predicate, fullMapper); 
    } 

    public ProcedureContainer<S, T> endBranch(final BiConsumer<S, R> consumer) { 

     Procedure<S, T> mergedBranch = (state) -> { 
      ValueContainer<T> startingPoint = baseProcedure.procede(state); 
      if (startingPoint.isTerminated()) 
       return ValueContainer.<T>terminated(); 

      if (!predicate.test(state, startingPoint.getValue())) 
       return startingPoint; 

      ValueContainer<R> intermediateValue = branchProcedure.apply(state, startingPoint); 
      if (intermediateValue.isTerminated()) 
       return ValueContainer.<T>terminated(); 
      consumer.accept(state, intermediateValue.getValue()); 
      return ValueContainer.<T>terminated(); 
     }; 

     return new ProcedureContainer<>(mergedBranch); 
    } 
} 

(私は多くがあると確信しているが)、私はこのアプローチを見る一つの問題は、分岐が終了したか否かを判断するために繰り返し呼び出しです。チェックが分岐点でのみ行われていればいいでしょう。

フルコードはmy github pageです。

注:ブランチが完了したときとブランチが実行されないときの両方を指定するには、「終了」を使用したことを理解します。私はまだより良い命名規則を考えようとしています。提案に開放されています。

+0

これはおそらくモナドです。あなたはオプションと考えましたか? –

+0

@レイテーク、アドバイスありがとう。私はオプションを使ってみましたが、いくつかの課題に遭遇しました。結局、ValueContainerは私にもっと自由を与えました。 – JRogerC

関連する問題