What's your experience with optional values?

Ö

Öö Tiib

On 12/28/2010 01:30 AM, Öö Tiib wrote:
On 12/27/2010 02:47 AM, Tiib wrote:
[...]
When i want uniform semantics for different types then i write a
family of function overloads. Something like bool IsMissing( T
const&      ) that expresses such state.
But where do you store that information? Let's say you have a struct
like this.
struct MyData
{
      std::string name;
      std::string address;
      int age;
      double height;
      int weight;
};
How would you distinguish missing, invalid, empty, etc. values in this
struct? And how would would you implement the signaling of those?
You think too much about data. You even name it as "Data". For me
behavior of object is way more important aspect than data. So for any
other object in system designed by me it is an interface exposed to
them and not a pile of data. Interface may be abstract and references
to all missing MyDatas in system may point at single immutable
MissingMyData that behaves in all situations like missing one should.
When it is so on case of MyData then it can be distinguished by memory
address:
   bool IsMissing( MyData const&    data )
   {
       return (&data ==&MissingMyData);
   }
Hm, interesting. Do you define MissingMyData as a global constant?
Anyway, still, with this solution you force all MyData to be pointers,
right?
All interfaces are anyway passed as pointers or references so that
solution should fit on lot of cases. MissingMyData can be immutable
constant in translation unit implementing MyData (and
IsMissing(MyData) or MyData::isMissing() ). Actual MissingMyData and
ExistingMyData are both derived from MyData interface.
Modification of it is together with pimpl idiom where ImplMissing is
used as special private implementation for exceptional Missing state.
That one works on more cases, only that classes should be built then
using pimpl idiom. Objects in exceptional state may even externally
appear mutable since they may switch underlying implementations
dynamically.
If it is good solution depends what behavioral flexibility is expected
from objects in exceptional state. If the object in Exceptional state
must be mutable during being in that exceptional state (rather rare
need) then you obviously need to instantiate such and can not use
single MissingMyData instance to incarnate them all. Exceptional
states can be built also as options into MyData class but that results
with if-else-polymorphism or switch-case-polymorphism that i consider
not that elegant.

Your solution is a good alternative. The only thing that seems
impossible in this whole discussion is to have a same solution for
non-pointer- and pointer types.

That does not bother me in general. When pointer does not point at
anything then it is NULL. I like to keep that like it is, so if it is
pointer that may be NULL then i am comparing it with NULL. That is
simple idiom and easy to see and understand i think. I mostly use such
with pointers to POD stuctures. Only thing is that it is sort of code
bloat to check for that nullness everywhere.

The code appears way more robust if NULLs are forbidden when you pass
polymorphic pointers (like for abstract interfaces) or envelope
objects (that use pimpl). Then there remain only contract checks about
nullness. Contract violation is programming error so you do not need
to find too graceful ways out of them.
 
Ö

Öö Tiib

This is a naming problem.  The traditional name for the idiom
implemented in boost::eek:ptional is Fallible: although the
implementations are more or less identical, the names imply
a completely different use.  In data base type uses, Nullable
would be the preferred name.  The best name I've seen to date is
Maybe, which seems fairly neutral with regards to why the value
might not be there.  (Historically, Fallible was more or less
universal before someone at Boost decided otherwise.  And I've
used my "Fallible" for things like cached values, where neither
Fallible nor Optional really correspond to the use case.)

Yes, the idiom still puts lot of work on the shoulders of receiver of
such Maybe, real exceptional object can be made to fulfill part of
objects actual contract (for example accept visitors) and thus may
make its usage simpler.

For example when navigating in a graph it may be useful when there is
some sort of "nothing" in direction where there is nothing (instead of
NULL). Otherwise all nodes in graph and/or navigators in graph may
have to deal with the NULL directions to make the NULL to look and
behave like a place where can be something but currently nothing is
there.
Or more likely:
    int intFromT = t.getMyInt().elseDefaultTo(someDefault);
(I'm not familiar with boost::eek:ptional, so I don't know what
name they actually use for "elseDefaultTo", but some such
function should definitely exist.)  Otherwise, you save the
optional, and verify its validity before trying to access its
value.

Yes, thanks for correction, with boost::eek:ptional it is something
like:

int intFromT = t.getMyInt().get_value_or( INT_MIN );

It is shorter than:

boost::eek:ptional<int> optInt = t.getMyInt();
int intFromT = !optInt ? INT_MIN : optInt.get();

However both look not that elegant from callers side and if INT_MIN is
what caller really wants as exceptional value then i think that
getMyInt() should return that instead of optional.

[...]
Good idea.  In general.  Most of the time, it's
over-engineering, but from time to time, it's justified.

Yes, sometimes uniform interface can be handy but mostly it is good
idea to use as domain-specific language in interfaces as only
possible. One benefit is that i can then better discuss the algorithms
with specialists of domain who feel my talk is closer to their slang.
Second benefit is that a maintainer who is unfamiliar with domain is
more motivated to make themself familiar with terminology and theory
of that domain.
Note that both the pointer idiom and data base use would suggest
something along the lines of == NULL or != NULL.  I've never had
to go that route systematically, but the possibility should also
be considered.

Usually the nullable classes for database i have seen have nullify()
and isNull() members ... that is close enough. ;-)
 
B

Bart van Ingen Schenau

Perhaps I misunderstood the intended message, but it seemed to me you
advocated using exceptional values as a default implementation for option
types, with the only reason against that being inability to spare a value
for a NULL/Nothing/None/... flag. I wanted to offer an alternate opinion,
as I feel implementation using exceptional values is inferior (even if you
wrap it in an interface somehow precluding the possibility of incorrect
usage of said exceptional value) unless there are performance
considerations involved.

Perhaps my C background is showing through here and perhaps I put it a
bit too black-and-white.
In my view, if there is already a reasonably obvious in-band value for
indicating "no value" (like NULL for pointers), then I see no reason
to use an out-of-band signalling mechanism for indicating the same.
If there is no reasonably obvious "absent" value, then boost::eek:ptional
is a very good alternative.

There are several main reasons why I find
optional<T> a better default choice:

1. It seems unnecessarily clunky to me to roll out a generic implementation
of a safe interface to option types implemented using exceptional values.

- The corollary here being that a single generic option type, such as
optional<T>, makes the intent clear and explicit at the
point-of-declaration and uniform across the codebase.

When writing generic code, you should always assume that the worst
type that fits the minimum requirements gets used.
This means that there is no a-priori known value that you could use
for signalling "no value", and out-of-band signalling is the only
remaining option in such a case. (Unless the template only works with
pointer types, where NULL is a more obvious choice.)
2. Option types using exceptional values do not generalize well to variant
types.

3. Option types using exceptional values are more fragile in face of spec
changes (although this is probably only a minor consideration in most
practical settings).

If the exceptional value(s) have to change due to spec changes, then
you chose the wrong values in the first place.
When using in-band signalling with a type that does not have an
intrinsic "no value" value, the values used for that should represent
a physical impossibility or at the very least something that is
physically highly impractical (cf. std::string::npos).
I hope this does clarify my viewpoint a bit? As I mentioned before, perhaps
I simply misinterpreted your words upthread.

Thanks for the clarification. Different vciewpoints are good for the
discussion.

Bart v Ingen Schenau
 
P

Pavel Lepin

Bart said:
Perhaps my C background is showing through here and perhaps I put it a
bit too black-and-white.
In my view, if there is already a reasonably obvious in-band value for
indicating "no value" (like NULL for pointers), then I see no reason
to use an out-of-band signalling mechanism for indicating the same.
If there is no reasonably obvious "absent" value, then boost::eek:ptional
is a very good alternative.


Thanks for the clarification. Different vciewpoints are good for the
discussion.

Indeed. Let those who face this choice read the discussion and decide for
themselves which advantages and drawbacks of various approaches are more
relevant in view of their requirements.
 
D

DeMarcus

Hi,

In databases you provide NULL if the value is missing. When using
objects on the free store in C++ you can use NULL to represent a missing
value. When using non-pointer values you may not.

I know several ways to solve this, but what I'm looking for is a
/uniform/ way to deal with missing values both for non-pointer values
and free store values.

When dealing with non-pointer values I can do the following.
boost::eek:ptional<int> getMyInt();

When dealing with free store values I can do the following.
std::unique_ptr<int> getMyInt();

However, I would like to have something uniform for my whole
application. Something like.
my::eek:pt<int> getMyInt();
my::eek:pt<int*> getMyInt();
my::eek:pt<std::unique_ptr<int>> getMyInt();

where my::eek:pt works like boost::eek:ptional for non-pointer values and as a
transparent container if a pointer (to avoid the unnecessary check for
the object since we already can check if the pointer is NULL).

With some hard work I could probably come up with some template
solution, but my question is:

* Does this look intuitive to you? Would this uniformity add to the
understanding and consistency of the code?


I could also just use std::unique_ptr everywhere I need optional values,
but with big structures of data it would be a performance hit to
allocate memory for each non-pointer member.

The simplest solution would be to just go for boost::eek:ptional when using
non-pointer types and std::unique_ptr for pointer types, but I thought
it could be a value having a uniform name. Do you agree with me?


Thanks,
Daniel

I'm back from vacation. Thanks all of you for your input!
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,995
Messages
2,570,225
Members
46,815
Latest member
treekmostly22

Latest Threads

Top