Derive4Jの概要

  • Java

  • link:/category/programming/ [プログラミング]

1. 前書き

Derive4Jは、Java 8のさまざまな機能概念を可能にする注釈プロセッサです。
このチュートリアルでは、Derive4Jと、フレームワークによって可能になる最も重要な概念を紹介します。
  • 代数データ型

  • 構造パターンマッチング

  • ファーストクラスの怠iness

2. メーベン依存

Derive4Jを使用するには、https://search.maven.org/search?q = g:org.derive4j%20AND%20a:derive4j [dependency]をプロジェクトに含める必要があります。
<dependency>
    <groupId>org.derive4j</groupId>
    <artifactId>derive4j</artifactId>
    <version>1.1.0</version>
    <optional>true</optional>
</dependency>

3. 代数データ型

3.1. 説明

代数データ型(ADT)は一種の複合型であり、他の型またはジェネリックの組み合わせです。
ADTは通常、2つの主要なカテゴリに分類されます。
  • sum

  • 製品

    HaskellやScalaなどの多くの言語には、代数データ型がデフォルトで存在します。

3.2. 合計タイプ

  • Sumは、論理OR演算を表すデータ型です。 **これは、どちらか一方であっても、両方ではないことを意味します。 簡単に言えば、合計タイプはさまざまなケースのセットです。 「sum」という名前は、個別の値の総数がケースの総数であるという事実に由来しています。

    __Enum __は、Javaでsum型に最も近いものです。 _Enum_には可能な値のセットがありますが、一度に1つの値しか持つことができません。 ただし、* Javaで_Enum_に追加データを関連付けることはできません。これは、_Enum_に対する代数データ型の主な利点です。*

3.3. 製品型

**製品は、論理AND演算を表すデータ型です。 **複数の値の組み合わせです。
Javaの_Class_は、製品タイプと見なすことができます。 製品タイプは、それらのフィールド全体の組み合わせによって定義されます。
ADTの詳細については、https://en.wikipedia.org/wiki/Algebraic_data_typeをご覧ください[このウィキペディアの記事]。

3.4. 使用法

一般的に使用される代数データ型の1つは__Eitherです。 __ _Either_は、値が欠落している可能性がある場合、または操作によって例外が発生する可能性がある場合に使用できる、より洗練された_Optional_と考えることができます。
ADTの構造を生成するためにDerive4Jによって使用される少なくとも1つの抽象メソッドで、_abstract class_または_interface_に注釈を付ける必要があります。
Derive4Jで_Either_データ型を作成するには、_interface_を作成する必要があります。
@Data
interface Either<A, B> {
    <X> X match(Function<A, X> left, Function<B, X> right);
}
_interface_には_ @ Data_の注釈が付けられ、Derive4Jが適切なコードを生成できるようになります。 生成されたコードには、ファクトリメソッド、レイジーコンストラクター、およびその他のさまざまなメソッドが含まれています。
デフォルトでは、生成されたコードは注釈付きの_class_の名前を複数形で取得します。 ただし、_inClass_パラメーターを使用して構成する可能性があります。
これで、生成されたコードを使用して_Either_ ADTを作成し、適切に機能することを確認できます。
public void testEitherIsCreatedFromRight() {
    Either<Exception, String> either = Eithers.right("Okay");
    Optional<Exception> leftOptional = Eithers.getLeft(either);
    Optional<String> rightOptional = Eithers.getRight(either);
    Assertions.assertThat(leftOptional).isEmpty();
    Assertions.assertThat(rightOptional).hasValue("Okay");
}
生成された__match()__methodを使用して、_Either_のどちら側にあるかに応じて関数を実行することもできます。
public void testEitherIsMatchedWithRight() {
    Either<Exception, String> either = Eithers.right("Okay");
    Function<Exception, String> leftFunction = Mockito.mock(Function.class);
    Function<String, String> rightFunction = Mockito.mock(Function.class);
    either.match(leftFunction, rightFunction);
    Mockito.verify(rightFunction, Mockito.times(1)).apply("Okay");
    Mockito.verify(leftFunction, Mockito.times(0)).apply(Mockito.any(Exception.class));
}

4. パターンマッチング

代数データ型を使用することで可能になる機能の1つは、パターンマッチングです。*
**パターンマッチングは、パターンに対して値をチェックするためのメカニズムです。 **基本的に、パターンマッチングはより強力な_https://www.baeldung.com/java-switch [switch] _ statementですが、マッチングタイプやパターンが一定であるという要件に制限はありません。 詳細については、https://en.wikipedia.org/wiki/Pattern_matching [パターンマッチングに関するこのウィキペディアの記事]を確認してください。
パターンマッチングを使用するには、HTTP要求をモデル化するクラスを作成します。 ユーザーは、指定されたHTTPメソッドのいずれかを使用できます。
  • GET

  • POST

  • DELETE

  • PUT

    _HTTPRequest_インターフェースから始めて、Derive4JでADTとしてリクエストクラスをモデル化します。
@Data
interface HTTPRequest {
    interface Cases<R>{
        R GET(String path);
        R POST(String path);
        R PUT(String path);
        R DELETE(String path);
    }

    <R> R match(Cases<R> method);
}
生成されたクラス_HttpRequests_(複数形に注意)により、リクエストのタイプに基づいてパターンマッチングを実行できるようになります。
この目的のために、リクエストのタイプに応じて異なる_Status_で応答する非常にシンプルな__HTTPServer ___classを作成します。
まず、サーバーからクライアントへの応答として機能するシンプルな__HTTPResponse __classを作成しましょう。
public class HTTPResponse {
    int statusCode;
    String responseBody;

    public HTTPResponse(int statusCode, String responseBody) {
        this.statusCode = statusCode;
        this.responseBody = responseBody;
    }
}
次に、パターンマッチングを使用して適切な応答を送信するサーバーを作成できます。
public class HTTPServer {
    public static String GET_RESPONSE_BODY = "Success!";
    public static String PUT_RESPONSE_BODY = "Resource Created!";
    public static String POST_RESPONSE_BODY = "Resource Updated!";
    public static String DELETE_RESPONSE_BODY = "Resource Deleted!";

    public HTTPResponse acceptRequest(HTTPRequest request) {
        return HTTPRequests.caseOf(request)
          .GET((path) -> new HTTPResponse(200, GET_RESPONSE_BODY))
          .POST((path,body) -> new HTTPResponse(201, POST_RESPONSE_BODY))
          .PUT((path,body) -> new HTTPResponse(200, PUT_RESPONSE_BODY))
          .DELETE(path -> new HTTPResponse(200, DELETE_RESPONSE_BODY));
    }
}
_class_の__acceptRequest()___methodは、リクエストのタイプでパターンマッチングを使用し、リクエストのタイプに基づいて異なるレスポンスを返します。
@Test
public void whenRequestReachesServer_thenProperResponseIsReturned() {
    HTTPServer server = new HTTPServer();
    HTTPRequest postRequest = HTTPRequests.POST("http://test.com/post", "Resource");
    HTTPResponse response = server.acceptRequest(postRequest);
    Assert.assertEquals(201, response.getStatusCode());
    Assert.assertEquals(HTTPServer.POST_RESPONSE_BODY, response.getResponseBody());
}

5. ファーストクラスの怠azine

Derive4Jでは、遅延の概念を導入できます。つまり、オブジェクトに対して操作を実行するまで、オブジェクトは初期化されません。 _interface_を__LazyRequest __として宣言し、生成されたクラスの名前を_LazyRequestImpl_に設定します。
@Data(value = @Derive(
  inClass = "{ClassName}Impl",
  make = {Make.lazyConstructor, Make.constructors}
))
public interface LazyRequest {
    interface Cases<R>{
        R GET(String path);
        R POST(String path, String body);
        R PUT(String path, String body);
        R DELETE(String path);
    }

    <R> R match(LazyRequest.Cases<R> method);
}
生成された遅延コンストラクターが正常に機能していることを確認できます。
@Test
public void whenRequestIsReferenced_thenRequestIsLazilyContructed() {
    LazyRequestSupplier mockSupplier = Mockito.spy(new LazyRequestSupplier());
    LazyRequest request = LazyRequestImpl.lazy(() -> mockSupplier.get());
    Mockito.verify(mockSupplier, Mockito.times(0)).get();
    Assert.assertEquals(LazyRequestImpl.getPath(request), "http://test.com/get");
    Mockito.verify(mockSupplier, Mockito.times(1)).get();
}

class LazyRequestSupplier implements Supplier<LazyRequest> {
    @Override
    public LazyRequest get() {
        return LazyRequestImpl.GET("http://test.com/get");
    }
}
ファーストクラスの遅延と例についての詳細は、https://www.scala-lang.org/blog/2017/11/28/view-based-collections.html [Scalaのドキュメント]を参照してください。

6. 結論

このチュートリアルでは、Derive4Jライブラリを紹介し、通常はJavaでは使用できない代数データ型やパターンマッチングなどの機能概念を実装するために使用しました。
ライブラリに関する詳細情報は、https://github.com/derive4j/derive4j [Derive4Jの公式ドキュメント]にあります。
いつものように、すべてのコードサンプルはhttps://github.com/eugenp/tutorials/tree/master/libraries-data-3[GitHubで]にあります。