Re: [boost] Boost Iterable Range Library update (v1.5)

"Robert Ramey" <ramey@rrsd.com> wrote in message news:cmrl55$hng$1@sea.gmane.org...
"Thorsten Ottosen" <nesotto@cs.auc.dk> wrote in message news:cmrk1g$f70$1@sea.gmane.org...
Here's my general take on it: iterators are important and useful as the lower-level infrastructure. iterators are, however, not too user-friendly; the user-friendly interface can be build on top so easy task becomes , well, easy. and that is the purpose of ranges and johns range library. without a good iterator library underneith ranges where hard to craft, but now that we have a good iterator library, we should persue higher abstractions
I agree that iterators aren't that easy or intuitive to work with. So our motivations have much in common. My view is that iterators can be made easier to work with without changing their fundamental character and without the need to introduce what I see as a new concept (ranges) that is "almost the same" as an existing concept - pair of iterators.
Robert Ramey
While iterators are very good at what they do -- provide access to elements in a sequence -- they do not capture the concept of a sequence very well. If I want to access a sequence of elements, I'd like to see a clean front-end, and I believe that John's range library can provide me with such a clean front-end. Using for_each(s.begin(), s.end(), ...) is too low-level, it exposes details of a sequence that I do not want to know. I do not want to for_each on a begin and an end, I want to for_each on a sequence: for_each(s, ...). It's a conceptual layer, admittedly a small one, but it provides a great front-end for the back-end of iterators. std::pair<iterator, iterator> fails at creating a decent front-end. For one thing, the iterator type must by typed twice. This adds quite some text. Secondly, it stays in the 'begin-end' concept, instead of advancing to a real range, of which you can extract an element and advance to the next by shrinking the range. You said before that the STL had a very good reason to use iterators. I agree there, iterators are very effective at what they do, but I'd like a higher-level interface built on top of that. For me, the following example from the Range Adaptors and Composition(s) documentation illustrates that: // [1] print all even elements (not using range adaptors) typedef boost::filter_iterator<array> filter_iterator; rng::for_each( irange<filter_iterator>( filter_iterator(some_array.begin(), is_even), filter_iterator( some_array.end(), is_even), print ); // [2] print all even elements (using range adaptors) rng::for_each( filtered(some_array, is_even), print); [1], while already using ranges, is easily rewritten to an iterator-only version. In [1], two iterators have to be wrapped with a filter_iterator constructor, both containing the same predicate. In [2], a range adaptor is used, and the predicate need only be given once. When I read the second version aloud, I hear exactly what I want to hear: "Take the elements of some_array, filter out the even elements, and print them". With [1], it's much more difficult to read it aloud. It also contains redundancy: is_even is passed twice. While possibly even error-prone, I think this is just ugly. It's not filter_iterator's fault, the filter_iterator is great at what it does, but it is the range adaptor that wraps it up nicely and provides a clean interface to ranges, not just begin-end pairs. best regards, Richard Peters

Richard Peters <r.a.peters <at> student.tue.nl> writes:
"Robert Ramey" <ramey <at> rrsd.com> wrote in message news:cmrl55$hng$1 <at> sea.gmane.org...
I agree that iterators aren't that easy or intuitive to work with. So our motivations have much in common. My view is that iterators can be made easier to work with without changing their fundamental character and without the need to introduce what I see as a new concept (ranges) that is "almost the same" as an existing concept - pair of iterators.
Like Robert I am uncomfortable with a range concept that has iteration capabilities. For one thing, standard containers don't satisfy that concept, and it seems to me that a container ought to be a range without any special adaptation. Furthermore I have doubts about how well this "range/iterator" concept maps onto bidirectional and random access. That said...
While iterators are very good at what they do -- provide access to elements in a sequence -- they do not capture the concept of a sequence very well. If I want to access a sequence of elements, I'd like to see a clean front-end, and I believe that John's range library can provide me with such a clean front-end. Using for_each(s.begin(), s.end(), ...) is too low-level, it exposes details of a sequence that I do not want to know. I do not want to for_each on a begin and an end, I want to for_each on a sequence: for_each(s, ...). It's a conceptual layer, admittedly a small one, but it provides a great front-end for the back-end of iterators.
...I agree with Richard that iterators are often at the wrong level of abstraction, and...
For me, the following example from the Range Adaptors and Composition(s) documentation illustrates that:
// [1] print all even elements (not using range adaptors) typedef boost::filter_iterator<array> filter_iterator; rng::for_each( irange<filter_iterator>( filter_iterator(some_array.begin(), is_even), filter_iterator( some_array.end(), is_even), print );
// [2] print all even elements (using range adaptors) rng::for_each( filtered(some_array, is_even), print);
...Perhaps more importantly, iterator interfaces don't lend themselves to functional composition: ranges::for_each( transformed(filtered(some_array, is_even), _1 / 2) , print); Try to say *that* with iterators. This is the design approach taken in MPL and Fusion, and it works very well. I am strongly for an algorithm and lazy adapter library based on ranges, and moderately strongly against directly giving ranges any direct iteration capability. -- Dave Abrahams Boost Consulting http://www.boost-consulting.com

David Abrahams writes:
"Robert Ramey" <ramey <at> rrsd.com> wrote in message news:cmrl55$hng$1 <at> sea.gmane.org...
I agree that iterators aren't that easy or intuitive to work with. So
our
motivations have much in common. My view is that iterators can be made easier to work with without changing their fundamental character and without the need to introduce what I see as a new concept (ranges) that is "almost the same" as an existing concept - pair of iterators.
Like Robert I am uncomfortable with a range concept that has iteration capabilities. For one thing, standard containers don't satisfy that concept, and it seems to me that a container ought to be a range without any special adaptation. Furthermore I have doubts about how well this "range/iterator" concept maps onto bidirectional and random access. That said...
I think it will be very difficult to have the standard containers satisfy a range concept. Containers hold elements, and provide access to them via iterators. Those two iterators model a range. Algorithms like for_each take that range, and process the items element by element, by using the first element, and shrinking the range by incrementing the first iterator. When you view a container as a range, you can't shrink the range without deleting elements. This would mean that when passing a container to an algorithm that that algorithm either deletes items or has to copy the container. Ranges live at the level of iterators, providing access. They do not live at the level of containers, which store elements. Therefore, I think a container does not need to satisfy the range concept. If I were allowed to make changes to the standard library, I would add a member function range() to containers, returning some range object holding a begin and an end: Containers hold objects, and provide access to them by representing those objects as a range. The range provides access to individual elements using iterators. Of course, changes to the standard library are not considered here. A make_range(container), however, comes very close to this, and seems like a good solution to me. As to the iteration capabilities: What would we loose if the iteration capabilities are removed? Are for-loops the only place that would be significantly changed? for ( crange<some_array_type> r(some_array); r; ++r) do_something( *r, some_data); I think this syntax is a bit too short anyway. What about adding member functions empty(), size(), front(), back()? for ( crange<some_array_type> r(some_array); !r.empty(); ...) do_something( r.front(), some_data); Looks nice enough, IMHO. This leaves only the problem of shrinking the range at the start. What do you think about range.begin() (non-const version) returning a reference to its begin iterator? Then ++r.begin() would be the equivalent of the current ++r. Similarly end() can return a reference, making shortening the range at the end possible. Another option I thought of was using pop_front(), but this has the disadvantage that it doesn't do exactly what is says: it does resemble list's pop_front, but no element is really erased. best regards, Richard Peters

"Richard Peters" <r.a.peters@student.tue.nl> writes:
David Abrahams writes:
"Robert Ramey" <ramey <at> rrsd.com> wrote in message news:cmrl55$hng$1 <at> sea.gmane.org...
I agree that iterators aren't that easy or intuitive to work with. So
our
motivations have much in common. My view is that iterators can be made easier to work with without changing their fundamental character and without the need to introduce what I see as a new concept (ranges) that is "almost the same" as an existing concept - pair of iterators.
Like Robert I am uncomfortable with a range concept that has iteration capabilities. For one thing, standard containers don't satisfy that concept, and it seems to me that a container ought to be a range without any special adaptation. Furthermore I have doubts about how well this "range/iterator" concept maps onto bidirectional and random access. That said...
I think it will be very difficult to have the standard containers satisfy a range concept.
It's difficult to have standard containers satisfy *your* range concept. That's exactly my point.
Containers hold elements, and provide access to them via iterators. Those two iterators model a range. Algorithms like for_each take that range, and process the items element by element, by using the first element, and shrinking the range by incrementing the first iterator. When you ^^^^^^^^^^^^^^^^^^^ This is the part that you added to "everyone else's" idea of what a range should be.
view a container as a range, you can't shrink the range without deleting elements.
Right. So don't shrink anything.
This would mean that when passing a container to an algorithm that that algorithm either deletes items or has to copy the container. Ranges live at the level of iterators, providing access. They do not live at the level of containers, which store elements. Therefore, I think a container does not need to satisfy the range concept.
That's just needlessly inconvenient. Unless you think people should be writing a lot of hand-coded loops instead of using algorithms, you're optimizing for the wrong case. Traversal should be done, for the most part, by libraries. Libraries can afford to touch iterators.
If I were allowed to make changes to the standard library, I would add a member function
Yet another place where you'd impede genericity. How would built-in arrays fit into the picture?
range() to containers, returning some range object holding a begin and an end: Containers hold objects, and provide access to them by representing those objects as a range. The range provides access to individual elements using iterators. Of course, changes to the standard library are not considered here. A make_range(container), however, comes very close to this, and seems like a good solution to me.
But oh, so unneccessary.
As to the iteration capabilities: What would we loose if the iteration capabilities are removed? Are for-loops the only place that would be significantly changed?
for ( crange<some_array_type> r(some_array); r; ++r) do_something( *r, some_data);
boost::for_each(some_array, bind(do_something, _1, some_data))
I think this syntax is a bit too short anyway.
Why would you want to make it longer?
What about adding member functions empty(), size(), front(), back()?
for ( crange<some_array_type> r(some_array); !r.empty(); ...) do_something( r.front(), some_data);
Looks nice enough, IMHO.
Worse and worse, IMO.
This leaves only the problem of shrinking the range at the start. What do you think about range.begin() (non-const version) returning a reference to its begin iterator? Then ++r.begin() would be the equivalent of the current ++r. Similarly end() can return a reference, making shortening the range at the end possible.
Iteration should mutate anything but iterators, IMO. I'd hate to think that I couldn't store a range in some object and then iterate over it without modifying it.
Another option I thought of was using pop_front(), but this has the disadvantage that it doesn't do exactly what is says: it does resemble list's pop_front, but no element is really erased.
All these problems go away if you don't try to get ranges to solve the BOOST_FOR_EACH problem. http://www.nwcpp.org/Meetings/2004/01.html -- Dave Abrahams Boost Consulting http://www.boost-consulting.com

"David Abrahams" <dave@boost-consulting.com> wrote in message news:ubre4mogk.fsf@boost-consulting.com... | "Richard Peters" <r.a.peters@student.tue.nl> writes: | | > David Abrahams writes: | >> Like Robert I am uncomfortable with a | >> range concept that has iteration capabilities. | >> For one thing, standard containers don't | >> satisfy that concept, and it seems to me | >> that a container ought to be a range without | >> any special adaptation. Furthermore | >> I have doubts about how well this "range/iterator" | >> concept maps onto bidirectional | >> and random access. That said... | > | > I think it will be very difficult to have the standard containers | > satisfy a range concept. | | It's difficult to have standard containers satisfy *your* range | concept. That's exactly my point. yes, there is two different concepts lurking here. I think it is feasible to have 1. std containers being ranges as defined in boost.range 2. a new concepts that refine the concepts of boost.range by adding iteration capabilities. I believe (2) is exactly what John is doing. -Thorsten

"Thorsten Ottosen" <nesotto@cs.auc.dk> writes:
I think it is feasible to have
1. std containers being ranges as defined in boost.range 2. a new concepts that refine the concepts of boost.range by adding iteration capabilities.
I believe (2) is exactly what John is doing.
-Thorsten
It's a mistake to think of John's range as being a refinement of the boost.range concept. If I have a non-const range and I pass it off to an algorithm that traverses it, will the range be modified or not? That should not change depending on which level of this "refinement hierarchy" the algorithm requires, or depending on whether the range was only non-const by chance (but not really intended to be modified). -- Dave Abrahams Boost Consulting http://www.boost-consulting.com

1. std containers being ranges as defined in boost.range 2. a new concepts that refine the concepts of boost.range by adding iteration capabilities.
I believe (2) is exactly what John is doing.
-Thorsten
It's a mistake to think of John's range as being a refinement of the boost.range concept. If I have a non-const range and I pass it off to an algorithm that traverses it, will the range be modified or not?
Note: the range will *not* change (in case you pass it to a rng:: algorithm). Best, John -- John Torjo, Contributing editor, C/C++ Users Journal -- "Win32 GUI Generics" -- generics & GUI do mix, after all -- http://www.torjo.com/win32gui/ -- v1.5 - tooltips at your fingertips (work for menus too!) + bitmap buttons (work for MessageBox too!) + tab dialogs, hyper links, lite html

John Torjo <john.lists@torjo.com> writes:
1. std containers being ranges as defined in boost.range 2. a new concepts that refine the concepts of boost.range by adding iteration capabilities.
I believe (2) is exactly what John is doing.
-Thorsten
It's a mistake to think of John's range as being a refinement of the boost.range concept. If I have a non-const range and I pass it off to an algorithm that traverses it, will the range be modified or not?
Note: the range will *not* change (in case you pass it to a rng:: algorithm).
If it is passed by reference it will change. Should I be unconcerned about that possibility? I'm not sure. The sort of range that a container is will normally be passed by reference. -- Dave Abrahams Boost Consulting http://www.boost-consulting.com

"David Abrahams" <dave@boost-consulting.com> wrote in message news:u3bzeo654.fsf@boost-consulting.com... | John Torjo <john.lists@torjo.com> writes: | | >>> | >>>1. std containers being ranges as defined in boost.range | >>>2. a new concepts that refine the concepts of boost.range by adding iteration | >>>capabilities. | >>> | >>>I believe (2) is exactly what John is doing. | >>> | >>>-Thorsten | >> It's a mistake to think of John's range as being a refinement of the | >> boost.range concept. If I have a non-const range and I pass it off to | >> an algorithm that traverses it, will the range be modified or not? | > | > Note: the range will *not* change (in case you pass it to a rng:: | > algorithm). | | If it is passed by reference it will change. Should I be unconcerned | about that possibility? I'm not sure. The sort of range that a | container is will normally be passed by reference. that is a good point. In fact, boost.range ranges *must* always be passed by reference or const reference. -Thorsten

David Abrahams <dave@boost-consulting.com> writes:
This leaves only the problem of shrinking the range at the start. What do you think about range.begin() (non-const version) returning a reference to its begin iterator? Then ++r.begin() would be the equivalent of the current ++r. Similarly end() can return a reference, making shortening the range at the end possible.
Iteration should mutate anything but iterators, IMO.
In case it wasn't clear, I meant to write Iteration should not mutate anything but iterators, IMO. ^^^ -- Dave Abrahams Boost Consulting http://www.boost-consulting.com

Like Robert I am uncomfortable with a range concept that has iteration capabilities. For one thing, standard containers don't satisfy that concept, and it seems to me that a container ought to be a range without any special adaptation. Furthermore
Well... This was to allow easy manual loops.
I have doubts about how well this "range/iterator" concept maps onto bidirectional and random access. That said...
It maps ok with bidirectional/random access. If an iterator has a given iterator category, the range will preserve it. I have used it in code, and it's quite powerful.
...Perhaps more importantly, iterator interfaces don't lend themselves to functional composition:
ranges::for_each( transformed(filtered(some_array, is_even), _1 / 2) , print);
Try to say *that* with iterators.
Indeed ;)
This is the design approach taken in MPL and Fusion, and it works very well. I am strongly for an algorithm and lazy adapter library based on ranges, and moderately strongly against directly giving ranges any direct iteration capability.
As you probably know, the library is around 1 year old. I've used it heavily on some of my projects, and all I can say is that iteration capability has helped me much. Best, John -- John Torjo, Contributing editor, C/C++ Users Journal -- "Win32 GUI Generics" -- generics & GUI do mix, after all -- http://www.torjo.com/win32gui/ -- v1.5 - tooltips at your fingertips (work for menus too!) + bitmap buttons (work for MessageBox too!) + tab dialogs, hyper links, lite html

John Torjo wrote:
Like Robert I am uncomfortable with a range concept that has iteration capabilities. For one thing, standard containers don't satisfy that concept, and it seems to me that a container ought to be a range without any special adaptation. Furthermore
Well... This was to allow easy manual loops.
I have doubts about how well this "range/iterator" concept maps onto bidirectional and random access. That said...
It maps ok with bidirectional/random access. If an iterator has a given iterator category, the range will preserve it. I have used it in code, and it's quite powerful.
...Perhaps more importantly, iterator interfaces don't lend themselves to functional composition:
ranges::for_each( transformed(filtered(some_array, is_even), _1 / 2) , print);
Try to say *that* with iterators.
Indeed ;)
This is the design approach taken in MPL and Fusion, and it works very well. I am strongly for an algorithm and lazy adapter library based on ranges, and moderately strongly against directly giving ranges any direct iteration capability.
As you probably know, the library is around 1 year old. I've used it heavily on some of my projects, and all I can say is that iteration capability has helped me much.
If you write all your algorithms to a range interface instead of iterator interface, that's a big win when you really want ranges. When you have to manually iterate in a loop, it's a loss. Now you have to say: template<class in_cont, class out_cont> algorithm (in_cont const& in, out_cont & out) { typename boost::range_const_iterator<in_cont>::type i = boost::begin (in); typename boost::range_iterator<out_cont>::type o = boost::begin (out); for (; i != boost::end (in); ++i, ++o) ... In this case, I'd say it's a net loss.

"Neal D. Becker" <ndbecker2@verizon.net> wrote in message news:cn35c3$g4g$1@sea.gmane.org... | John Torjo wrote: | > As you probably know, the library is around 1 year old. I've used it | > heavily on some of my projects, and all I can say is that iteration | > capability has helped me much. | > | | If you write all your algorithms to a range interface instead of iterator | interface, that's a big win when you really want ranges. When you have to | manually iterate in a loop, it's a loss. Now you have to say: | | template<class in_cont, class out_cont> | algorithm (in_cont const& in, out_cont & out) { | typename boost::range_const_iterator<in_cont>::type i = boost::begin (in); | typename boost::range_iterator<out_cont>::type o = boost::begin (out); | for (; i != boost::end (in); ++i, ++o) ... | | In this case, I'd say it's a net loss. Sorry, could you elaborate on what the loss is? Using a new iterable range concept we can say sub_range<const in_cont> r( in ); for( ; r; ++r ) { ... } If your question is: "should algorithms be implemented in terms of iterators or ranges?", then answer is "iterators". However, it makes sense not to expose the iterator interface once you have added the range layer and the iterator interface might use iterable ranges in its implementation. -Thorsten

Thorsten Ottosen wrote:
"Neal D. Becker" <ndbecker2@verizon.net> wrote in message news:cn35c3$g4g$1@sea.gmane.org... | John Torjo wrote:
| > As you probably know, the library is around 1 year old. I've used it | > heavily on some of my projects, and all I can say is that iteration | > capability has helped me much. | > | | If you write all your algorithms to a range interface instead of | iterator | interface, that's a big win when you really want ranges. When you have | to | manually iterate in a loop, it's a loss. Now you have to say: | | template<class in_cont, class out_cont> | algorithm (in_cont const& in, out_cont & out) { | typename boost::range_const_iterator<in_cont>::type i = boost::begin | (in); typename boost::range_iterator<out_cont>::type o = boost::begin | (out); for (; i != boost::end (in); ++i, ++o) ... | | In this case, I'd say it's a net loss.
Sorry, could you elaborate on what the loss is? Using a new iterable range concept we can say
sub_range<const in_cont> r( in ); for( ; r; ++r ) { ... }
I'm confused. We are talking about http://www.torjo.com/rangelib/? I can't find any mention of the above "sub_range" in the zip I grabbed from that site.
If your question is: "should algorithms be implemented in terms of iterators or ranges?", then answer is "iterators". However, it makes sense not to expose the iterator interface once you have added the range layer and the iterator interface might use iterable ranges in its implementation.
Don't know what you mean by "in terms of". I'm thinking that we want algorithms to use ranges for interfaces, like the example I gave of "algorithm" above. Internally, algorithm could use range if it is convenient, like if we could pass to rng::transform, but in general, iterators might be necessary. Is this what you mean?

"Neal Becker" <ndbecker2@verizon.net> wrote in message news:cn3o25$ml6$1@sea.gmane.org... | Thorsten Ottosen wrote: | | > "Neal D. Becker" <ndbecker2@verizon.net> wrote in message | > | In this case, I'd say it's a net loss. | > | > Sorry, could you elaborate on what the loss is? Using a new iterable range | > concept we can say | > | > sub_range<const in_cont> r( in ); | > for( ; r; ++r ) { ... } | > | | I'm confused. We are talking about http://www.torjo.com/rangelib/? I can't | find any mention of the above "sub_range" in the zip I grabbed from that | site. yeah, sorry. sub_range is in boost.range, not in John's lib. But it dosn't change anything, just think of irange or ctange. So again, what is the "net loss"? | Don't know what you mean by "in terms of". I'm thinking that we want | algorithms to use ranges for interfaces, like the example I gave of | "algorithm" above. Internally, algorithm could use range if it is | convenient, like if we could pass to rng::transform, but in general, | iterators might be necessary. Is this what you mean? well, sort of. an underlying implementation in terms of iterators is always necessary. -Thorsten

Thorsten Ottosen wrote:
"Neal Becker" <ndbecker2@verizon.net> wrote in message news:cn3o25$ml6$1@sea.gmane.org... | Thorsten Ottosen wrote: | | > "Neal D. Becker" <ndbecker2@verizon.net> wrote in message
| > | In this case, I'd say it's a net loss. | > | > Sorry, could you elaborate on what the loss is? Using a new iterable | > range concept we can say | > | > sub_range<const in_cont> r( in ); | > for( ; r; ++r ) { ... } | > | | I'm confused. We are talking about http://www.torjo.com/rangelib/? I | can't find any mention of the above "sub_range" in the zip I grabbed | from that site.
yeah, sorry. sub_range is in boost.range, not in John's lib. But it dosn't change anything, just think of irange or ctange.
So again, what is the "net loss"?
If the example you show above actually worked, that would be great.

John Torjo <john.lists@torjo.com> writes:
Like Robert I am uncomfortable with a range concept that has iteration capabilities. For one thing, standard containers don't satisfy that concept, and it seems to me that a container ought to be a range without any special adaptation. Furthermore
Well... This was to allow easy manual loops.
I have doubts about how well this "range/iterator" concept maps onto bidirectional and random access. That said...
It maps ok with bidirectional/random access. If an iterator has a given iterator category, the range will preserve it. I have used it in code, and it's quite powerful.
Don't you need a current position, start and an end for bidirectional iteration? Forward "ranges" just get a current position and an end. -- Dave Abrahams Boost Consulting http://www.boost-consulting.com

It maps ok with bidirectional/random access. If an iterator has a given iterator category, the range will preserve it. I have used it in code, and it's quite powerful.
Don't you need a current position, start and an end for bidirectional iteration? Forward "ranges" just get a current position and an end.
In my view, bidirectional means that the begin() iterator from the range can move both ways, and that it still has an end() of the range. Best, John -- John Torjo, Contributing editor, C/C++ Users Journal -- "Win32 GUI Generics" -- generics & GUI do mix, after all -- http://www.torjo.com/win32gui/ -- v1.5 - tooltips at your fingertips (work for menus too!) + bitmap buttons (work for MessageBox too!) + tab dialogs, hyper links, lite html

John Torjo <john.lists@torjo.com> writes:
It maps ok with bidirectional/random access. If an iterator has a given iterator category, the range will preserve it. I have used it in code, and it's quite powerful.
Don't you need a current position, start and an end for bidirectional iteration? Forward "ranges" just get a current position and an end.
In my view, bidirectional means that the begin() iterator from the range can move both ways, and that it still has an end() of the range.
Why is it important to have an end but not to have a beginning? -- Dave Abrahams Boost Consulting http://www.boost-consulting.com

Why is it important to have an end but not to have a beginning?
Yup, you do have a point. Having an end is useful for traversing it from begin to end. Thus, the range concept applies to all iterators. It might be useful to think about having a beginning, for bidirectional ranges. It certainly needs more thought. Best, John -- John Torjo, Contributing editor, C/C++ Users Journal -- "Win32 GUI Generics" -- generics & GUI do mix, after all -- http://www.torjo.com/win32gui/ -- v1.5 - tooltips at your fingertips (work for menus too!) + bitmap buttons (work for MessageBox too!) + tab dialogs, hyper links, lite html
participants (6)
-
David Abrahams
-
John Torjo
-
Neal Becker
-
Neal D. Becker
-
Richard Peters
-
Thorsten Ottosen