Smart constructors on data class unsafe when using copy: how to avoid that?

100 views Asked by At

In the doc about Validation, when speaking of Smart constructor, it's suggested to use them on data class, but making this possible:

object EmptyAuthorName

data class Author private constructor(val name: String) {
    companion object {
        operator fun invoke(name: String): Either<EmptyAuthorName, Author> = either {
            ensure(name.isNotEmpty()) { EmptyAuthorName }
            Author(name)
        }
    }
}

Author("fo")
    .map { it.copy(name = "") }
    .run { println(this) } // Either.Right(Author(name=))
 

The EmptyAuthorName constraint wasn't respected. Really bad isn't it?

Redefining copy for the data class isn't possible...

Currently my only way out is to not use data class.

Am i missing something?

1

There are 1 answers

3
Jorn On

The primary constructor of a data class is always available via the copy method. You either have to make sure that the result of the primary constructor is valid, or you must not use a data class.

In this case, the validation logic seems to be in a wrapper around the data class, not in the data class itself. That means you can do the validation on an instance of Author, instead of "faking" a constructor call. Then you can call it after operations like map, and you'll always get a properly validated Either.

data class Author(val name: String) {
        fun validate(): Either<EmptyAuthorName, Author> = either {
            ensure(name.isNotEmpty()) { EmptyAuthorName }
            this
        }
}

Author("fo").validate()
    .map { it.copy(name = "").validate() }
    .run { println(this) } // Either.Left(EmptyAuthorName)

Another upside of doing it this way is that your code becomes easier to understand. In your example, you have a function that looks like an Author constructor, but it doesn't return an instance of Author (but an instance of Either). That's confusing. Making a function that clearly specifies that it validates and returns a validated result is better and easier to understand.