2017-05-27 1 views
6

Ich versuche, mit Lambda zum Spaß zu experimentieren. Ich habe einen Funktor erstellt, der die Zusammensetzung eines Lambda ermöglicht. Aber die Mittel zur Komposition erlauben nur eine lineare Transformation und erlauben keine Verzweigung.Verzweigen beim Komponieren von Lambdas von anderen Lambdas

Die Idee ist, dass ich weiß, dass ich in Zukunft eine effektiv unveränderliche Zustandsdatenstruktur haben werde. Ich möchte eine Transformation erstellen, die einen Wert aus dem Zustand extrahiert. und führt eine Reihe von Schritten durch, die den Zustand erfordern können oder nicht, um die Transformation durchzuführen.

Zu diesem Zweck erstelle ich zwei Klassen. Die funktionale Schnittstelle funktioniert wie java.util.function.Function, nimmt aber eine BiFunction in der andThen-Methode, die es ermöglicht, den Zustandsparameter von Lambda an Lambda zu übergeben.

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

Die Funktors ist recht einfach, zwei Abbildungsfunktionen aufweist (eine, die den Zustand verwendet, und eines, das nicht der Fall ist), und zwei Verfahren zur Einstellung, die die Transformationen (auch hier mit und ohne Zustand), abzuschließen.

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

Ein kurzes (erfundenes) Beispiel:

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

Hat jemand irgendwelche Ideen, wie ich eine branch Methode auf den ProcedureContainer implementieren könnte, die einer bedingte Verzweigung in der Ausführung der Endverbraucher ermöglichen würden, . Ich denke, etwas, das dieses Beispiel Arbeit machen würde:

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

ich versucht habe, indem ein neues BranchProcedureContainer zu schaffen, die eine map und terminateBranch Methode hat. Dieses Problem ist, dass ich nicht weiß, wie die zwei Zweige so zusammengeführt werden, dass nur der Zweig ausgeführt wird.

Es gibt keine Einschränkungen beim Erstellen neuer Klassen oder Hinzufügen von Methoden zu vorhandenen Klassen.

+1

so eine Schande, dass Sie hier keine Aufmerksamkeit bekommen haben. Das hat mich auch sehr interessiert. Am wenigsten kann ich wählen. – Eugene

Antwort

1

Ich konnte eine Lösung zusammenstellen. Aber ich finde es nicht besonders elegant. Also, zögern Sie nicht, andere Lösungen (oder mehr einfallsreiche Version dieser Lösung) einzureichen.

Ich habe ursprünglich versucht, einen State Container zu erstellen, der einen Boolean enthielt, der angibt, ob ein bestimmter Zweig verwendet wurde oder nicht. Dies war ein No-Go, da der Zustand nicht korrekt weitergegeben wird. Anstatt also ich einen Wert Container erstellt:

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 
} 

ich dann die funktionale Schnittstelle neu geschrieben Verwendung des neuen Behälters zu machen:

@FunctionalInterface 
public interface Procedure<S, T> { 

    ValueContainer<T> procede(S stateStructure); 
} 

Mit der Zugabe der ValueContainer, ich wollte nicht die Benutzer, um den Wert für jede Methode auszupacken. Daher wurde die Standardmethode erweitert, um den Container zu berücksichtigen. Zusätzlich wurde eine Logik hinzugefügt, um den Fall zu behandeln, in dem die Prozedur Teil einer nicht verwendeten/abgeschlossenen Verzweigung ist.

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

Von dort hatte ich nur die ProcedureContainer zu erweitern, um eine branch Methode zu haben; und überarbeitete die Beendigungsmethode, um den ValueContainer und den Fall des abgeschlossenen Zweigs zu berücksichtigen.(Der folgende Code verläßt off ladenen Methoden)

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

Die Verzweigungs Methode liefert eine neue Klasse, BranchProcedureContainer, die den Zweig Griffe zu bauen. Diese Klasse verfügt über eine endBranch Methode, die in eine neue Instanz von ProcedureContainer bindet. (Auch hier sind überladene Methoden aufhörte)

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

Das einzige Problem, das ich mit diesem Ansatz sehen (obwohl ich bin sicher, es gibt viele) ist die wiederholte Aufforderung, um zu bestimmen, ob eine Verzweigung beendet ist oder nicht. Es wäre schön, wenn diese Überprüfung nur am Verzweigungspunkt durchgeführt würde.

Der vollständige Code kann my github page gefunden werden.

HINWEIS: Ich verstehe, dass ich 'beenden' verwendet habe, um anzugeben, wann eine Verzweigung abgeschlossen ist und wann nie eine Verzweigung ausgeführt wird. Ich versuche immer noch an eine bessere Namenskonvention zu denken. Offen für Vorschläge.

+0

vielleicht ist dies eine Monade. Hast du optional gedacht? –

+0

@RayTayek, danke für die Beratung. Ich habe versucht, ein optionales, aber lief ein paar Herausforderungen. Am Ende gab mir der ValueContainer mehr Freiheit. – JRogerC