Re: Records — Dealing with nullable values
Brian Goetz
brian.goetz at oracle.com
Sat Dec 14 22:37:54 UTC 2019
Records are _transparent, shallowly immutable containers for their
state_. So either the record holds a Gender and exposes that as the
API, or it holds an Optional<Gender>, and exposes that. If you are
using what is essentially a macro generator (like Lombok), I'm sure it
has a hundred knobs you can twiddle to represent your favorite
patterns. But that's not what records are -- records are _nominal
tuples_ (just as functional interfaces are _nominal function types_.)
I get why you are seeking a nulls-in, Optional-out protocol -- you're
trying to follow Postel's law. But if you really want to treat "no
gender" as a valid point in the domain, then just make a record with an
Optional<Gender> component, and push the `Optional.ofNullable()` to the
caller (or, provide an overloaded constructor that does it for them.)
But I would still use this pattern sparingly; the majority of uses I see
of Optional in domain models are gratuitous.
On 12/14/2019 5:31 PM, Arash Shahkar wrote:
> In my example, gender can be left undefined, but not name. Dropping
> requireNonNull(gender) from the compact constructor works, but leaves
> the accessor for gender returning null (possibly).
>
> What I’d like to achieve is to have the accessor for gender return
> Optional<Gender>, to clearly communicate that it might be null. I have
> found this pretty common, and using this pattern has helped a lot.
>
> I was wondering if my “workaround” to be able to use records and drop
> Lombok for these cases is a big no-no.
> On Dec 14, 2019, 5:16 PM -0500, Brian Goetz <brian.goetz at oracle.com>,
> wrote:
>> No, that would not be a good idea. If you find yourself using Optional
>> for a method parameter, or a field type, or a record component type,
>> you've almost certainly gone Optional-crazy. It's a common, but
>> curable, disease.
>>
>> What you want to do is this:
>>
>> record Foo(String name, Gender gender) {
>> Foo {
>> requireNonNull(name);
>> requireNonNull(gender);
>> }
>> }
>>
>> The `Foo { ... }` is a _compact constructor_, which is a constructor
>> whose signature is the same as that of the record, and which
>> automatically handles the `this.x = x` boilerplate for you. This is
>> where you put argument validation / normalization.
>>
>>
>> On 12/14/2019 5:01 PM, Arash Shahkar wrote:
>>> Hi,
>>>
>>> I’ve been using Lombok for quite a while now to automatically
>>> generate boilerplate code for "data carrier" classes. However, a
>>> pattern I really like and have found to work very well is to verify
>>> non-nullity of all the fields that must not be null, and return an
>>> Optional<Field> from the accessor of a field if it’s allowed to have
>>> a null value. Consider:
>>>
>>> @lombok.Value
>>> class Person {
>>>
>>> String name;
>>> Gender gender;
>>>
>>> Person(String name, Gender gender) { … } // customize the
>>> constructor, verifying that name is defined and valid
>>>
>>> Optional<Gender> getGender() { // customize the accessor for
>>> gender, to make sure calling code deals with the possibility of
>>> gender being null
>>> return ofNullable(gender);
>>> }
>>> }
>>>
>>> Doing a similar thing with records is not possible, due to the fact
>>> that the accessor for gender has to exactly return a Gender. With
>>> the current specification for records, the only option is to do:
>>>
>>> record Person(String name, Optional<Gender> gender) {
>>> …
>>> }
>>>
>>> Is this considered good practice? Do you suggest any alternatives?
>>
More information about the amber-dev
mailing list