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.
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 {
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 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