Thank you very much for this exhaustive explanation and example. I really
like it and agree with you that the implementation provided by your example
is much more well designed.

The problem I have with it is that I feel like it assumes that I have a way
to introduce such changes when writing the API, whereas I was talking more
about using existing solutions.
The provided example was based on common situations encountered when
writing the code in Django. It may be that I'm using this tool not to its
full potential, but it seems like many of your points were about bad API
design, not the actual maybe-dot operator.

Such operator would make it much easier to work with those frameworks that
do not allow for more readable/well thought-out solutions. I'm thinking
about it as a syntactic sugar that can speed things up (and even make it
more readable) in a situation where there is no time and/or budget to come
up with a better architecture (like e.g. the one you have provided). The
reality is that usually from the business perspective nobody wants a well
designed system, only the functioning one.

It may be that I misunderstood your response and didn't provide a valid
answer - please let me know in that case.

Best regards

On Tue, Oct 19, 2021 at 1:29 AM Steve Dower <steve.do...@python.org> wrote:

> Okay, I'll let myself get sucked into responding ONE TIME, but only
> because you gave me such a nice API to work with :)
>
> On 10/18/2021 9:11 PM, Piotr Waszkiewicz wrote:
> >  > class User(DBModel):
> >  >    phone: str | None
> >  >
> >  > class Publisher(DBModel):
> >  >   owner: ForeignKey[User] | None
> >  >
> >  > class Book(DBModel)
> >  >     publisher: ForeignKey[Publisher] | None
> >
> >
> > Imagine wanting to get the phone number of the person that published a
> > certain book from the database.
> > In this situation, with maybe-dot operator I can write:
> >
> >  > phone_number = book.publisher?.owner?.phone
>
> Consider today, you wrote this as "book.publisher.owner.phone". You
> would potentially get AttributeError, from any one of the elements - no
> way to tell which, and no way to react.
>
> Generally, AttributeError indicates that you've provided a value to an
> API which doesn't fit its pattern. In other words, it's an error about
> the *type* rather than the value.
>
> But in this case, the (semantic, not implementation) *type* is known and
> correct - it's a publisher! It just happens that the API designed it
> such that when the *value* is unknown, the *type* no longer matches.
>
> This is PRECISELY the kind of (IMHO, bad) API design that None-aware
> operators will encourage.
>
>
> Consider an alternative:
>
> class ForeignKey:
>      ...
>      def __bool__(self):
>          return not self.is_dbnull
>
>      def value(self):
>          if self.is_dbnull:
>              return self.Type.empty() # that is, DBModel.empty()
>          return self._value
>
>
> class DBModel:
>      @classmethod
>      def empty(cls):
>          return cls(__secret_is_empty_flag=True)
>
>      def __bool__(self):
>          return not self._is_empty
>
>      def __getattr__(self, key):
>          if not self:
>              t = self._get_model_type(key)
>              return t.empty() if isinstance(t, DBModel) else None
>          ...
>
> class User(DBModel):
>      phone: str | None
>
> class Publisher(DBModel):
>      owner: ForeignKey[User]
>
> class Book(DBModel)
>      publisher: ForeignKey[Publisher]
>
>
> Okay, so as the API implementer, I've had to do a tonne more work.
> That's fine - *that's my job*. The user hasn't had to stick "| None"
> everywhere (and when we eventually get around to allowing named
> arguments in indexing then they could use "ForeignKey[User,
> non_nullable=True]", but I guess for now that would be some subclass of
> ForeignKey).
>
> But now here's the example again:
>
>  > book.publisher.owner.phone
>
> If there is no publisher, it'll return None. If there is no owner, it'll
> return None. If the owner has no phone number, it'll return None.
>
> BUT, if you misspell "owner", it will raise AttributeError, because you
> referenced something that is not part of the *type*. And that error will
> be raised EVERY time, not just in the cases where 'publisher' is
> non-null. It takes away the random value-based errors we've come to love
> from poorly coded web sites and makes them reliably based on the value's
> type (and doesn't even require a type checker ;) ).
>
> Additionally, if you want to explicitly check whether a FK is null, you
> can do everything with regular checks:
>
> if book.publisher.owner:
>      # we know the owner!
> else:
>      # we don't know
>
> # Get all owner names - including where the name is None - but only if
> # Mrs. None actually published a book (and not just because we don't
> # know a book's publisher or a publisher's owner)
> owners = {book.id: book.publisher.owner.name
>            for book in all_books
>            if book.publisher.owner}
>
> # Update a null FK with a lazy lookup
> book.publisher = book.publisher or publishers.get(...)
>
>
> You can't do anything useful with a native None here besides test for
> it, and there are better ways to do that test. So None is not a useful
> value compared to a rich DBModel subclass that *knows* it is empty.
>
> ---
>
> So to summarise my core concern - allowing an API designer to "just use
> None" is a cop out, and it lets people write lazy/bad APIs rather than
> coming up with good ones.
>
> Cheers,
> Steve
> _______________________________________________
> Python-Dev mailing list -- python-dev@python.org
> To unsubscribe send an email to python-dev-le...@python.org
> https://mail.python.org/mailman3/lists/python-dev.python.org/
> Message archived at
> https://mail.python.org/archives/list/python-dev@python.org/message/BRTRKGY6RLTHZJQ2US4LO7DYLSGXQ5GM/
> Code of Conduct: http://python.org/psf/codeofconduct/
>
_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-le...@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at 
https://mail.python.org/archives/list/python-dev@python.org/message/XMDUUUOAJTLU5NHWFLDZE7BYUCRNA62H/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to