|
Boost : |
From: Douglas Gregor (gregod_at_[hidden])
Date: 2002-04-13 01:57:49
We've talked about adding a switch-on-type construct for a while, so I
thought I'd whip up a prototype that we could talk about. The prototype is
here, but read on:
http://groups.yahoo.com/group/boost/files/typeswitch-20020413.tgz
Here are some requirements that I personally have for a switch-on-type
construct, which are, of course, open for debate:
1) Syntactically similar to a C++ switch() statements. I think Phoenix-like
syntax is the way to go, here.
2) Ability to return a value from a typeswitch. This makes it possible for
essentially pattern-based transformation systems. (My hidden goal here is to
simplify AST transformations).
3) Some form of pattern matching or decomposition of simple product (e.g.,
tuple) types, so that if our type contains two elements in it, they will be
the first and second arguments to the function object processing a value of
that type.
4) Not tied to any particular sum (variant, union, discriminated union,
etc.) or product (tuple, class, typelist, etc) type (any functional
programming gurus want to correct my terminology? FP isn't my area...). Any
type should be adaptable to one or both categories.
5) Safety checks for completeness of decoding, unmatchable clauses, etc.
6) Handle both tagged and untagged clauses (e.g., an Ocaml-like variant has
tags that may or may not have value types associated with them, whereas
boost::any is untagged)
Here's a short example that (I believe) should illustrate how I envision a
switch-on-type:
int compute(any expr)
{
using namespace boost::lambda;
return switch_<int>(expr)[
case_<Literal>((_1, _1))
| case_<Add>(bind(&compute, _1) + bind(&compute, _2))
| case_<Sub>(bind(&compute, _1) - bind(&compute, _2))
| case_<Mul>(bind(&compute, _1) * bind(&compute, _2))
| case_<Div>(bind(&compute, _1) / bind(&compute, _2))
];
}
The behavior of the code is, I hope, obvious: it takes a representation of an
expression, built from integer literals and the binary operations Add, Sub,
Mul, and Div, and computes the value of the expression recursively. Some
notes on how this works:
- The operators are product types, so an 'Add' contains two subexpressions
for its operands. When 'Add' is matched, it is decomposed into the two
subexpressions and those are passed as individual arguments to the supplied
function object. If you're an Ocaml person, think of 'Add' being equivalent
to:
Add of Expression * Expression
And the "| case_<Add>(do_something)" is:
| Add(_1, _2) -> do_something(_1, _2)
- The sum type used is boost::any. I added a specialization of the
'sum_traits' type trait for boost::any, but did not change any other source
code in boost::any.
- The product type used is boost::tuples::tuple. I added a specialization
of the 'product_traits' type trait for boost::tuples::tuple, but did not
change any other source code in the tuples library.
- The Lambda library is used for the operator syntax. The actual source
file this came from (libs/type_switch/compute.cpp in the archive) can also
use Boost.Bind, if you don't have Lambda around..
- I pulled a few dirty tricks to fake tag types with boost::any. Basically,
each tag (e.g., 'Add') is a class that derives from the appropriate tuple
type (boost::tuples::tuple<boost::any, boost::any>) and has a product_traits
specialization that forwards the tuple product_traits information. With a
tagged variant type, this hackery would all disappear but the code above
would remain the same.
Doug
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk