/var/log/jsoizo

メモ帳 技術とか趣味とか

Arrow.ktのnullable DSLでjOOQがnullable大量に生み出すのを何とかする

過去にjOOQのRecord型から値を取り出すときに、一旦nullableをArrow.ktのOption型に変換してから合成すると良いよ的な書き方をしたんだけど、

jsoizo.hatenablog.com

同じくArrow.ktのnullable DSLでもっとスッキリ書けるとわかったので改めて。

以下のようなコードがあり、 当該フィールドに値が入っていることは確定しているから !! で強制的にnullableを無視している。これでも良いのだけど、コンパイラがめっちゃwarn出してきてただならぬ雰囲気になるんだよね。

val query = create.select().from(TODOS)

val todos = query.fetch().map { record ->
    ToDo(
        id = record.get(TODOS.ID)!!,
        title = record.get(TODOS.TITLE)!!,
        body = record.get(TODOS.BODY)!!,
        created_at = record.get(TODOS.CREATED_AT)!!,
        modified_at. = record.get(TODOS.MODIFIED_AT)!!
    )
}

Arrow.ktのnullable DSLを利用するとこのようにできる。
※ 利用しているArrow.ktのバージョンは 1.2.0-RC

val todos = query.fetch().mapNotNull { record ->
    nullable {
        ToDo(
            id = record.get(TODOS.ID).bind(),
            title = record.get(TODOS.TITLE).bind(),
            body = record.get(TODOS.BODY).bind(),
            created_at = record.get(TODOS.CREATED_AT).bind(),
            modified_at = record.get(TODOS.MODIFIED_AT).bind()
        )
    }
}

万が一どこかの値がnullだった場合に、元のコードとしてはNullPointerExceptionがはっせいしてしまうところであるが、今回のコードだとmapしたあとの ToDo() もnullになり、mapNotNull で消えてくれるので無視することができる。

不用意にOption型に詰め直す必要がないのはとても楽である。

なお、 Raise<T> を利用したパターンでこのように書くこともできる

val todos = query.fetch().mapNotNull { record ->
    nullable {
        ToDo(
            id = ensureNotNull(record.get(TODOS.ID)),
            title = ensureNotNull(record.get(TODOS.TITLE)),
            body = ensureNotNull(record.get(TODOS.BODY)),
            created_at = ensureNotNull(record.get(TODOS.CREATED_AT)),
            modified_at = ensureNotNull(record.get(TODOS.MODIFIED_AT))
        )
    }
}

さらにさらに、このようなjOOQのRecord型の拡張を用意しておくとシュッとしたコードになる。
※ build.gradleでcontext receiverの設定を追加する必要あり

context(NullableRaise)
fun <T> org.jooq.Record.getOrRaise(field: org.jooq.Field<T>) = 
    ensureNotNull(get(field))

val todos = query.fetch().mapNotNull { record ->
    nullable {
        ToDo(
            id = record.getOrRaise(TODOS.ID),
            title = record.getOrRaise(TODOS.TITLE),
            body = record.getOrRaise(TODOS.BODY),
            created_at = record.getOrRaise(TODOS.CREATED_AT),
            modified_at = record.getOrRaise(TODOS.MODIFIED_AT)
        )
    }
}

参考:

arrow-kt.io