Notez que j'ai expérimenté ce qui se passerait si j'implémentais le type Option de Scala en Java. Forme évoluée de type énumération et instruction switch! ?? Essayer d'obtenir des types de données algébriques et des correspondances de modèles en Java.
Je veux utiliser Lombok, donc je vais expérimenter avec le projet Gradle au lieu de JShell. Créez un projet Gradle avec la commande suivante.
$ mkdir option
$ cd option
$ gradle init \
--type java-library \
--dsl groovy \
--test-framework junit \
--project-name option \
--package com.example
Mettez à jour le fichier build.gradle
pour installer Lombok.
--- 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 {
Essayez d'implémenter le type Option en Java comme indiqué ci-dessous. Le modèle de classe scellé n'est pas utilisé pour une meilleure visibilité.
Il existe un type «T» détenu par la classe «Some» et un type de résultat «R» de correspondance de modèle, ce qui était assez déroutant lors de l'implémentation. Ceux qui lisent le code doivent être prudents.
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);
}
Écrivez un test qui utilise le type Option. C'est un test qui n'a d'autre sens que de vérifier le fonctionnement du pattern matching.
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);
}
}
Implémentez les méthodes map
et flatMap
comme suit:
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 explique la différence entre map et flatMap comme suit.
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 est utilisé lorsque vous souhaitez convertir une valeur contenue dans un contexte en une nouvelle valeur, et conserver le même contexte entre-temps. flatMap est utilisé lorsque vous souhaitez convertir une valeur et lui donner un nouveau contexte.
Le type Option a un contexte [^ 1] avec les valeurs Some et None. Lorsque vous appliquez la carte, Certains restent Certains et Aucun reste Aucun. Lorsque flatMap est appliqué, Some devient Some ou None et None reste None. Vous pouvez considérer map comme étant capable d'appliquer des fonctions qui n'échouent pas, et flatMap comme étant capable d'appliquer des fonctions qui peuvent échouer (fonctions qui aboutissent à un type Option).
Écrivons un test qui utilise les méthodes de type Option map
et flatMap
.
Voici les méthodes qui peuvent échouer, qui renvoient ʻOption , les méthodes
mightFail1 et
mightFail2et
mightFail3. En utilisant ces trois méthodes, testons la méthode
map et la méthode
flatMap` d'une manière qui tient compte de Scala pour la notation d'inclusion.
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<>();
}
}
Le processus utilisant trois méthodes est décrit dans le contexte de type Option [^ 2]. L'important ici est que vous pouvez écrire le processus sans être conscient du contexte de «peut échouer». Si toutes les méthodes réussissent, ou si l'une des méthodes échoue, vous pouvez imposer une casse au contexte de type Option et écrire uniquement le traitement que vous souhaitez réaliser. La soi-disant monade est devenue un type Option. peut être.
Cette fois, j'ai essayé de réaliser une correspondance de modèle, une carte et une flatMap de type Option de type Scala en Java. C'est juste une expérience à des fins d'étude, et je ne pense pas que ce soit pratique, mais j'ai écrit cet article pour garder mon esprit en ordre. Je pense que ce code pratique exigerait une description des modifications et des limites de type. J'espère que cela vous sera utile.
[^ 1]: Essential Scala affiche les valeurs Some et None dans le type Option comme contexte. Je ne pense pas que ce soit une expression exacte, mais dans cet article, nous l'appelons "contexte de valeur". [^ 2]: Lorsque vous parlez du contexte en tant que monade dans le type Option, cela indique le contexte qui "peut échouer". Je ne pense pas que ce soit une représentation exacte, mais dans cet article, nous l'appelons «contexte de type».
Recommended Posts