lmi
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [lmi] Are NAND assertions difficult to understand?


From: Vadim Zeitlin
Subject: Re: [lmi] Are NAND assertions difficult to understand?
Date: Sun, 25 Apr 2021 16:07:16 +0200

On Sun, 25 Apr 2021 11:11:18 +0000 Greg Chicares <gchicares@sbcglobal.net> 
wrote:

GC> An insurance policy comes into being when it is "issued";
GC> that occurs on its "issue date".
GC> 
GC> Some events can occur only on the issue date, while others
GC> can occur only after the issue date, so it's important to
GC> know whether the policy was issued today.

 After being traumatized by dealing with dates and times for so many years,
I've acquired a conditional reflex consisting in becoming very nervous
whenever I hear the word "today". So, even though it's probably irrelevant
here, I'd like to ask: what does "today" mean exactly, i.e. in which time
zone is the date determined? And do we really want the "issued today" flag
to become false as soon as the clock strikes midnight in it?

GC> A policy has a "duration", which is the number of full
GC> years elapsed since the issue date.
GC> 
GC> For example:
GC> 
GC>   issue date  date today  duration?  issued today?
GC>   ----------  ----------  ---------  -------------
GC>   2021-03-05  2021-03-05      0           yes
GC>   2021-03-05  2021-03-07      0            no
GC>   2021-03-05  2028-03-05      7            no
GC> 
GC> One subsystem passes
GC>   struct {int duration; bool issued_today;} args = {/*values*/};
GC> to another, which must validate the passed-in object by asserting
GC> the following invariants

 I also realize that you're not asking about this and probably can't change
it, but it would be really great if we could sidestep the question by not
having to assert these invariants in the first place and rather ensuring
that they're always satisfied by construction.

 If we don't need to deal with the not yet issued policies at all, this
could be as simple as using "std::optional<int> duration", where invalid
duration would indicate that the policy has been issued today. If we do
need to distinguish between not issued at all and just issued policies, it
would need to be slightly more complicated but I can still see a couple of
ways of encoding the invariant into the data structure itself, the simplest
of which would use std::variant<not_issued, issued_today, issued_before>
with the first 2 structs being empty and issued_before containing the
single "int duration" field (this is the most direct generalization of the
std::optional approach).

 Again, I understand that changing the existing code might be difficult,
but IMO it's worth an effort to try doing it, or maybe at least imposing
the good data format/abstraction at subsystem boundary, even if we keep the
existing code unchanged inside the subsystem itself, because the existing
representation is clearly problematic and writing the assertion validating
it is surely not the only, or worst, problem with it. By using a better
data structure, even if it's as simple as "optional<int>", we could ensure,
at compile-time, that both cases are correctly dealt with, which would seem
to be an even bigger advantage than not having to write the assertion at
all, although this would already be appreciable in its own right.

GC> --expressed as simply as I can imagine:
GC>   args.issued_today → 0 == args.duration
GC>   0 != args.duration → !args.issued_today
GC> or in natural language:
GC>   if the policy was issued today, its duration must be zero
GC>   if duration != 0, the policy cannot have been issued today
GC> What's the clearest way to express that in code?

 The clearest way is to translate the statements above to the code word by
word, i.e.

        if(args.issued_today)
            {
            assert(args.duration == 0);
            }
        if(args.duration != 0)
            {
            assert(!args.issued_today);
            }

 This is, of course, verbose, but -- assuming we have to keep this data
representation -- I would define functions for working with it anyhow and
we could (and should) just have a member function in the struct containing
these fields called assert_duration_consistency() or something like that
and just call it from all the places where it's needed. And writing the
above just once, in this function, seems to be clearly worth it from
clarity vs brevity point of view.

GC> Is the following "NAND" assertion too obscure?
GC> 
GC>   assert(!(args.issued_today && 0 != args.duration)

 Yes. I did write conditions like this in the past myself and I stopped
because I realize that this was just too error-prone and took too long to
decipher later.

GC> Is the following equivalent more understandable?
GC> 
GC>   assert(!args.issued_today || 0 == args.duration);

 IMO it's not meaningfully different from the above. Both can be shown to
be correct once you make an effort, neither is simple enough to understand
it when just glancing at it.

GC> I tried combining those assertions into one statement,
GC> but found it difficult to do correctly:

 Which is just another indication that we shouldn't do it, and why would we
even try? It's not like we have a quote on statements that we can use.

GC> The problem vanishes if we traffic in dates or JDNs,
GC> but that cure is worse than the disease because it
GC> requires a calendar class, and forces us to use
GC> concrete dates where we'd rather use abstractions.

 Sorry if I'm missing something, but to me there is really no problem here
at all, just two solutions requiring (maybe vastly) different amounts of
effort to put in place:

1. If at all possible, stop using "{ int duration, bool issued_today }"
   completely, this is clearly a bad data representation because it's
   redundant and hence possibly self-contradictory, requiring to have the
   assert(s) in the first place.

2. If this really can't be done at all, add a function checking for the
   data consistency, express the condition in it in the obvious and clear
   way using multiple checks and call this function from elsewhere.

 (2) shouldn't be difficult to do at all, so even if (1) is impossible for
practical reasons, we could at the very least do it like this. But I hope
you can actually use (1) because it's so clearly problematic.

 Please let me know if the problem is more complicated than I think, but,
again, to me it seems as simple as this and I would rather just solve it
than concentrate on optimizing a condition that should have been never
necessary in the first place and doesn't need to be written as a single
condition/statement even if it is.

 Regards,
VZ

Attachment: pgpUjTviitqQA.pgp
Description: PGP signature


reply via email to

[Prev in Thread] Current Thread [Next in Thread]