|
Boost : |
From: Fernando Cacciola (fernando_cacciola_at_[hidden])
Date: 2003-09-01 11:53:23
Brian McNamara <lorgon_at_[hidden]> wrote in message
news:20030831031628.GA22725_at_lennon.cc.gatech.edu...
> On Sun, Aug 31, 2003 at 09:58:45AM +0800, Joel de Guzman wrote:
> > Fernando Cacciola <fcacciola_at_[hidden]> wrote:
> > > Hi Mat,
> > >
> > > Thanks for the input.
> > >
> > > optional<> is now out on the field and it is the only utility of its
> > > kind I've ever seen in C++, at least in real use. This has the
> > > drawback that there is very little experience with it. Therefore, in
> >
> > There's a lot of experience with it in other languages. Why not
> > leverage that? Haskell::Maybe for instance.
>
> I feel like Maybe works more smoothly in Haskell than in C++, but I
> haven't ever really tried to nail down the reasons why. This thread has
> me thinking about it more, though, so I'll share some general thoughts
> (as well as some suggestions for "optional" at the end).
>
> Two issues spring to mind immediately: pattern matching and monads.
>
> Pattern matching:
> Haskell's Maybe is a type constructor, and thus the value constructors
> Just and Nothing can be used in pattern matches. Pattern
> matching provides a common way to do the "test" and the "unwrap"
> steps. Code like
>
> optional<int> o;
> ...
> if( o )
> do_something( *o );
> else
> maybe_do_other();
>
> in C++ sometimes takes a form like
>
> o :: Maybe int
> ...
> case o of Just x -> doSomething x
> Nothing -> maybeDoOther
>
> in Haskell. Since this form of using pattern matching to "case-ify" and
> deconstruct a datatype is a standard idiom in Haskell, nothing special
> needs to be done for Maybe.
>
> Monads:
> In addition, Maybe forms a monad, which means that you can use
> do-notation to string together computations involving Maybes. Code like
>
> optional<int> x, y, z, result;
> ...
> if( x ) {
> y = foo( *x );
> if( y ) {
> z = bar( *x, *y );
> if( z )
> result = baz( *x, *y, *z );
> else
> result.reset(); // being more explicit than I have to be
> }
> else
> result.reset();
> }
> else
> result.reset();
>
> can be written as
>
> x, y, z, result :: Maybe int
> ...
> do x <- x
> y <- foo x
> z <- bar x y
> result <- baz x y z
> return result
>
> in Haskell, which uses the Monad's bind operation to sugarize over all
> the if-then-else plumbing to push Nothings all the way through the
> computation. In FC++, we have monad comprehensions, so it could be
> written as just
>
> maybe<int> x;
> ...
> fcomp_m<maybe_m>()[ baz[X,Y,Z] | X <= x,
> Y <= foo[X],
> Z <= bar[X,Y] ]
>
> Comprehensions are unlikely to be used much in normal C++ code, though.
>
>
> In any case, I suppose my point with both of those issues is that
> Haskell already has some kind of standard/idiomatic way to sugarize over
> the details of dealing with Maybes. I don't think we have anything
> equivalent in C++, so it's not clear to me what we can learn from
> Haskell when trying to design the interface for optional in C++.
>
Hi Brian,
thanks fot the input.
I've reach a similar conclusion when I looked at Maybe for inspiration.
>
> Offhand, the "pointer interface" for reading the value of an optional
> seems good to me. Being able to say
>
> optional<int> x;
> ...
> if( x ) foo( *x );
>
> seems like a natural way to "sugarize" this in C++.
>
Great!
That's for I think too.
> As for the constructors, why not have a separate function for
> initializing optionals, a la Just/Nothing in Haskell? I am imagining
> something like this:
>
> template <class T>
> class optional {
> optional(); // or maybe public, depending on preference
> optional( const T& ); // private
> public:
> optional( const optional& ); // still copyable,
> optional& operator=( const optional& ); // assignable
> ... operator bool ... operator*
> };
> template <class T>
> optional<T> just( const T& ); // the constructor (a friend)
>
> Then you no longer have the issue of
>
> optional<int> x(3);
> int r = *x;
> optional<int> y = 4;
> r = *y;
>
> looking "funny" or "unbalanced", because now it's
>
> optional<int> x( just(3) );
> int r = *x;
> optional<int> y = just(4);
> r = *y;
>
> and the "just" balances out the "*", in a way.
>
> (This is similar to what we do with maybe<T> in FC++, though we don't
> have the op-bool/op-* sugar for reading maybes. In FC++, the constant
> NOTHING can also be used to initialize an "empty" maybe.)
>
I'm not so sure anymore if the unbalance really needs to be addressed.
Solutions like the one you propose above do certainly add 'purity'
to the interface, but OTOH makes it harder to use.
I stress as fundamental the fact that initialization and assignment is
always defined, so I'm not sure if special syntax is really needed since
there is nothing to protect against.
Sure, the concept may be confusing _at first_, but once the user learns it,
she can leverage the power of a terser syntax.
So in this regard I prefer the variant way:
support implicit construction and direct assignment.
The only suggarized operation is thus value access, why is the one with the
Possibly Uninitialized problem.
Fernando Cacciola
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk