Versuchen Sie, Scala-ähnliche Optionstyp-Musterübereinstimmung und Map FlatMap in Java zu realisieren

Beachten Sie, dass ich experimentiert habe, was passieren würde, wenn ich den Optionstyp von Scala in Java implementieren würde. Weiterentwickelte Form des Aufzählungstyps und der switch-Anweisung! ?? Versuch, algebraische Datentypen und Musterübereinstimmungen in Java zu erreichen.

Generieren Sie ein experimentelles Gradle-Projekt

Ich möchte Lombok verwenden, also werde ich mit dem Gradle-Projekt anstelle von JShell experimentieren. Erstellen Sie ein Gradle-Projekt mit dem folgenden Befehl.

$ mkdir option
$ cd option
$ gradle init \
    --type java-library \
    --dsl groovy \
    --test-framework junit \
    --project-name option \
    --package com.example

Aktualisieren Sie die Datei "build.gradle", um Lombok zu installieren.

--- a/build.gradle
+++ b/build.gradle
@@ -9,6 +9,7 @@
 plugins {
     // Apply the java-library plugin to add support for Java Library
     id 'java-library'
+    id "io.freefair.lombok" version "4.1.6"
 }
 
 repositories {

Implementieren Sie einen Optionstyp, der mit Mustern übereinstimmen kann

Versuchen Sie, den Optionstyp wie unten gezeigt in Java zu implementieren. Das Muster der versiegelten Klasse wird nicht für eine bessere Sichtbarkeit verwendet.

Es gibt einen Typ "T", der von der Klasse "Some" gehalten wird, und einen Ergebnistyp "R" für den Mustervergleich, der während der Implementierung ziemlich verwirrend war. Diejenigen, die den Code lesen, sollten vorsichtig sein.

src/main/java/com/example/Option.java


package com.example;

import lombok.Value;

public interface Option<T> {
    @Value
    class Some<T> implements Option<T> {
        T value;

        public <R> R match(CaseBlock<T, R> caseBlock) {
            return caseBlock._case(this);
        }
    }

    @Value
    class None<T> implements Option<T> {
        public <R> R match(CaseBlock<T, R> caseBlock) {
            return caseBlock._case(this);
        }
    }

    interface CaseBlock<T, R> {
        R _case(Some<T> some);
        R _case(None<T> none);
    }

    <R> R match(CaseBlock<T, R> caseBlock);
}

Schreiben Sie einen Test, der den Optionstyp verwendet. Es ist ein Test, der keine andere Bedeutung hat als die Überprüfung der Funktionsweise des Mustervergleichs.

src/test/java/com/example/OptionTypeTest.java


package com.example;

import org.junit.Test;

import static org.junit.Assert.*;

public class OptionTest {
    @Test
    public void testSomeType() {
        Option<Integer> some = new Option.Some<>(1);

        var actual = some.match(new Option.CaseBlock<>() {
            @Override
            public Integer _case(Option.Some<Integer> some) {
                return some.getValue();
            }

            @Override
            public Integer _case(Option.None<Integer> none) {
                return 0;
            }
        });

        assertEquals(1, actual);
    }

    @Test
    public void testNoneType() {
        Option<Integer> none = new Option.None<>();

        var actual = none.match(new Option.CaseBlock<>() {
            @Override
            public Integer _case(Option.Some<Integer> some) {
                return some.getValue();
            }

            @Override
            public Integer _case(Option.None<Integer> none) {
                return 0;
            }
        });

        assertEquals(0, actual);
    }
}

Implementieren Sie den Optionstyp, der Map und FlatMap kann

Implementieren Sie die Methoden map und flatMap wie folgt:

src/main/java/com/example/Option.java


package com.example;

import lombok.Value;

import java.util.function.Function;

public interface Option<T> {
    @Value
    class Some<T> implements Option<T> {
        T value;

        public <R> R match(CaseBlock<T, R> caseBlock) {
            return caseBlock._case(this);
        }
    }

    @Value
    class None<T> implements Option<T> {
        public <R> R match(CaseBlock<T, R> caseBlock) {
            return caseBlock._case(this);
        }
    }

    interface CaseBlock<T, R> {
        R _case(Some<T> some);
        R _case(None<T> none);
    }

    <R> R match(CaseBlock<T, R> caseBlock);

    default <R> Option<R> map(Function<T, R> f) {
        return this.match(new CaseBlock<>() {
            @Override
            public Option<R> _case(Some<T> some) {
                return new Some<>(f.apply(some.getValue()));
            }

            @Override
            public Option<R> _case(None<T> none) {
                return new None<>();
            }
        });
    }

    default <R> Option<R> flatMap(Function<T, Option<R>> f) {
        return this.match(new CaseBlock<>() {
            @Override
            public Option<R> _case(Some<T> some) {
                return f.apply(some.getValue());
            }

            @Override
            public Option<R> _case(None<T> none) {
                return new None<>();
            }
        });
    }
}

Essential Scala erklärt den Unterschied zwischen Map und FlatMap wie folgt.

We use map when we want to transform the value within the context to a new value, while keeping the context the same. We use flatMap when we want to transform the value and provide a new context.

map wird verwendet, wenn Sie einen in einem Kontext enthaltenen Wert in einen neuen Wert konvertieren und in der Zwischenzeit denselben Kontext beibehalten möchten. flatMap wird verwendet, wenn Sie einen Wert konvertieren und ihm einen neuen Kontext geben möchten.

Der Optionstyp hat einen Kontext [^ 1] mit den Werten Some und None. Wenn Sie eine Karte anwenden, bleiben einige einige und keine keine. Wenn flatMap angewendet wird, wird Some zu Some oder None und None bleibt None. Stellen Sie sich Map vor, dass sie Funktionen anwenden kann, die nicht fehlschlagen, und flatMap kann Funktionen anwenden, die möglicherweise fehlschlagen (Funktionen, die zu einem Optionstyp führen).

Schreiben wir einen Test, der die Methoden "map" und "flatMap" vom Optionstyp verwendet.

Hier sind die Methoden, die fehlschlagen können und die die Option "Option <Ganzzahl", die Methoden "mayFail1" und "mayFail2" und "mayFail3" zurückgeben. Testen Sie mit diesen drei Methoden die "map" -Methode und die "flatMap" -Methode auf eine Weise, die Scalas für die Einschlussnotation bewusst ist.

src/test/java/com/example/OptionMapAndFlatMapTest.java


package com.example;

import org.junit.Test;

import static org.junit.Assert.assertEquals;

public class OptionMapAndFlatMapTest {
    @Test
    public void testSomeResultOfMapAndFlatMap() {
        // for {
        //   a <- mightFail1
        //   b <- mightFail2
        // } yield a + b
        var actual = mightFail1().flatMap(a ->
                     mightFail2().map    (b ->
                     a + b));

        assertEquals(new Option.Some<>(3), actual);
    }

    @Test
    public void testNoneResultOfMap() {
        // for {
        //   a <- mightFail1
        //   b <- mightFail2
        //   c <- mightFail3
        // } yield a + b + c
        var actual = mightFail1().flatMap(a ->
                     mightFail2().flatMap(b ->
                     mightFail3().map    (c ->
                     a + b + c)));

        assertEquals(new Option.None<>(), actual);
    }

    @Test
    public void testNoneResultOfFlatMap() {
        // for {
        //   a <- mightFail3
        //   b <- mightFail2
        //   c <- mightFail1
        // } yield a + b + c
        var actual = mightFail3().flatMap(a ->
                     mightFail2().flatMap(b ->
                     mightFail1().map    (c ->
                     a + b + c)));

        assertEquals(new Option.None<>(), actual);
    }

    private Option<Integer> mightFail1() {
        return new Option.Some<>(1);
    }

    private Option<Integer> mightFail2() {
        return new Option.Some<>(2);
    }

    private Option<Integer> mightFail3() {
        return new Option.None<>();
    }
}

Der Prozess mit drei Methoden wird im Kontext des Optionstyps [^ 2] beschrieben. Das Wichtigste dabei ist, dass Sie den Prozess schreiben können, ohne sich des Kontexts von "möglicherweise fehlgeschlagen" bewusst zu sein. Wenn alle Methoden erfolgreich sind oder eine der Methoden fehlschlägt, können Sie dem Kontext vom Optionstyp einen Fall auferlegen und nur die Verarbeitung schreiben, die Sie erreichen möchten. Die sogenannte Monade ist zu einem Optionstyp geworden. vielleicht.


Dieses Mal habe ich versucht, Scala-ähnliche Optionstyp-Mustervergleiche, Karten und flatMap in Java zu realisieren. Es ist nur ein Experiment für Studienzwecke, und ich denke nicht, dass es praktisch ist, aber ich habe diesen Artikel geschrieben, um meine Gedanken in Ordnung zu halten. Ich denke, dass praktischer Code eine Beschreibung der Modifikation und der Typgrenzen erfordern würde. Ich hoffe es wird für Sie hilfreich sein.

[^ 1]: Essential Scala zeigt die Werte Some und None im Optionstyp als Kontext an. Ich denke nicht, dass es ein exakter Ausdruck ist, aber in diesem Artikel nennen wir es "Wertekontext". [^ 2]: Wenn Sie über den Kontext als Monade im Optionstyp sprechen, wird der Kontext angegeben, der "möglicherweise fehlschlägt". Ich denke nicht, dass es eine exakte Darstellung ist, aber in diesem Artikel nennen wir es "Typkontext".

Recommended Posts

Versuchen Sie, Scala-ähnliche Optionstyp-Musterübereinstimmung und Map FlatMap in Java zu realisieren
Unterschied zwischen Stream Map und FlatMap
Zum Verständnis von Karte und Flatmap in Stream (1)
Verwenden Sie den schnellen Filter und die Karte
[Rubiy] Vereinfache jedes mit Map und Inject
Versuchen Sie, Scala-ähnliche Optionstyp-Musterübereinstimmung und Map FlatMap in Java zu realisieren
Probieren Sie den Funktionstyp in Java aus! ①
Versuchen Sie, Yuma in Java zu implementieren
[Android] Konvertieren Sie Map in JSON mit GSON mit Kotlin und Java
Versuchen Sie, Project Euler in Java zu lösen
Versuchen Sie, n-ary Addition in Java zu implementieren
Versuchen Sie, ein Bulletin Board in Java zu erstellen
Versuchen Sie, Ruby und Java in Dapr zu integrieren
Zum Verständnis von Karte und Flatmap in Stream (1)
[Java] So erhalten Sie den Schlüssel und den Wert, die in Map gespeichert sind, durch iterative Verarbeitung
In der Abbildung verstandene Java-Klassen und -Instanzen
[Java] Hinweise zur Typinferenz in Java 10
So konvertieren Sie A in a und a in A mit logischem Produkt und Summe in Java
Versuchen Sie, ein eingeschränktes FizzBuzz-Problem in Java zu lösen
Typbestimmung in Java
In Java 2D-Karte speichern und mit for-Anweisung drehen
Einführung in effektives Java durch Üben und Lernen (Builder-Muster)
Implementieren Sie Thread in Java und versuchen Sie, die anonyme Klasse Lambda zu verwenden
[Java] Ändern Sie Sprache und Gebietsschema mit JVM-Optionen in Englisch
Was ist in "Java 8 bis Java 11" passiert und wie wird eine Umgebung erstellt?
Aufrufen und Verwenden der API in Java (Spring Boot)
Gründe, Servlet und JSP in der Java-Entwicklung getrennt zu verwenden
Ich habe versucht, in Java von einer Zeichenfolge in einen LocalDate-Typ zu konvertieren
So entwickeln und registrieren Sie eine Sota-App in Java
Unterschiede im Umgang mit Zeichenfolgen zwischen Java und Perl