Essayez de réaliser une correspondance de modèle de type Option de type Scala et une carte, flatMap en Java

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.

Générer un projet expérimental Gradle

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 {

Implémenter le type d'option qui peut correspondre aux modèles

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émenter le type d'option qui peut mapper et flatMap

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

Essayez de réaliser une correspondance de modèle de type Option de type Scala et une carte, flatMap en Java
Différence entre Stream map et flatMap
Vers la compréhension de la carte et de la flatmap dans Stream (1)
Utilisez un filtre et une carte rapides
[Rubiy] Simplifiez chaque utilisation de la carte et injectez
Essayez de réaliser une correspondance de modèle de type Option de type Scala et une carte, flatMap en Java
Essayez le type fonctionnel en Java! ①
Essayez d'implémenter Yuma en Java
[Android] Convertissez Map en JSON à l'aide de GSON avec Kotlin et Java
Essayez de résoudre Project Euler en Java
Essayez d'implémenter l'ajout n-aire en Java
Essayez de créer un babillard en Java
Essayez d'intégrer Ruby et Java avec Dapr
Vers la compréhension de la carte et de la flatmap dans Stream (1)
[Java] Comment obtenir la clé et la valeur stockées dans Map par traitement itératif
Classes et instances Java comprises dans la figure
[Java] Points à noter sur l'inférence de type étendue dans Java 10
Comment convertir A en A et A en A en utilisant le produit logique et la somme en Java
Essayez de résoudre un problème FizzBuzz restreint en Java
Détermination de type en Java
Stocker dans une carte Java 2D et tourner avec pour instruction
Introduction à Effective Java en pratiquant et en apprenant (modèle Builder)
Implémentez Thread en Java et essayez d'utiliser la classe anonyme Lambda
[Java] Changez la langue et les paramètres régionaux en anglais avec les options JVM
Que s'est-il passé dans «Java 8 to Java 11» et comment créer un environnement
Comment appeler et utiliser l'API en Java (Spring Boot)
Raisons d'utiliser séparément Servlet et JSP dans le développement Java
J'ai essayé de convertir une chaîne de caractères en un type LocalDate en Java
Comment développer et enregistrer une application Sota en Java
Différences dans la gestion des chaînes entre Java et Perl