This is about a class interface design. Let's say I have an Array class
which is able to encapsulate a N-dimensional array of a dynamic element
type and has runtime operations for resizing the dimensions, converting
the element type, etc.
Should these operations be const member functions returning a new Array
(functional style) or non-const void member functions modifying the
object? The actual content is shared and reference-counted behind the
scenes so just copying an Array object is pretty cheap, so this question
is more about style, not performance, thus I am mostly leaving out
optimization issues here.
*Functional style*
class Array {
public:
Array Reshape(size_t width, size_t height, size_t depth) const;
template Array Convert() const;
// ...
};
Array PrepareSomeArray();
void ConsumeArray(Array a);
// usage:
ConsumeArray( PrepareSomeArray().Reshape(10,20,30).Convert());
Advantages:
- Conscise usage
- No side effects (pure functional style)
Drawbacks:
- Deviates from the std::vector usage (e.g. std::vector::resize()).
- The caller may forgot to assign the result to a new object and assume
the modifications happen in place
*Imperative style*
class Array {
public:
void Reshape(size_t width, size_t height, size_t depth);
template void Convert();
// ...
};
Array PrepareSomeArray();
void ConsumeArray(Array a);
// usage example:
Array a = PrepareSomeArray();
a.Reshape(10,20,30);
a.Convert();
ConsumeArray(a);
Advantages:
- Follows std::vector design
Drawbacks:
- More verbose usage
- Code is less robust because of side effects
- Worse optimization possibilities because unneeded named objects tend
to remain lying around.
So, any comments or insights? Which style should I aim for? This
interface is intended for external library users, but at the moment we
don't have any yet, so cannot ask directly. Which style would *you*
prefer when given the task to accomplish something by such a library?
Maybe I should provide both styles? They would probably need different
member function names, are there any naming conventions for these
different styles?
TIA
Paavo
Melzzzzz wrote in news:ltnsfr$mv1$1@solani.org:
> On Thu, 28 Aug 2014 13:19:51 -0500
> Paavo Helde wrote:
>
>>
>> I fail to see how refcount changing is bad for thread sharing, but at
>> the same time any other state changing is not.
>>
>
> When object is copied across threads user won't expect any surprises.
> Eg changing state would imply COW which has to have some
> synchronization. You know that story about COW strings and why
> are they bad for multi-threaded programs.
This is because in case of std::basic_string COW has to work across
threads as nobody has told the users they cannot just pass strings over
to another thread (possibly because the C++ standard did not acknowledge
the existence of threads at the time...). This causes all the thread
synchronization pessimizations. In our library, there is a special
function to be called before passing the object to another thread, which
ensures the data is not shared.
When COW works in a single thread (and when the class interface is not so
silly as to expose char& reference from operator[] which will not be used
for mutation), it is pretty fast and useful.
Cheers
Paavo
On Thu, 28 Aug 2014 13:19:51 -0500
Paavo Helde wrote:
>
> I fail to see how refcount changing is bad for thread sharing, but at
> the same time any other state changing is not.
>
When object is copied across threads user won't expect any surprises.
Eg changing state would imply COW which has to have some
synchronization. You know that story about COW strings and why
are they bad for multi-threaded programs.
--
press any key to continue or any other to quit...
Melzzzzz wrote in news:ltlidv$6sa$1@news.albasani.net:
> On Wed, 27 Aug 2014 02:47:57 -0500
> Paavo Helde wrote:
>
>> This is about a class interface design. Let's say I have an Array
>> class which is able to encapsulate a N-dimensional array of a dynamic
>> element type and has runtime operations for resizing the dimensions,
>> converting the element type, etc.
>>
>> Should these operations be const member functions returning a new
>> Array (functional style) or non-const void member functions modifying
>> the object? The actual content is shared and reference-counted behind
>> the scenes so just copying an Array object is pretty cheap, so this
>> question is more about style, not performance, thus I am mostly
>> leaving out optimization issues here.
>
> Hm.
Copying an Array object more or less means writing a pointer value in a
local variable and incrementing the refcount in the actual object. No
dynamic memory allocation or thread synchronization is involved (pass to
another thread is a separate explicit operation and ensures first that
the refcount is 1).
[...]
> I have some experience with Haskell and I can tell that pure functional
> style is performance killer. I always end up writing imperative
> style if I want performance.
> Go for imperative style and make your implementation simpler: get
> rid of reference counting as it is not good for thread sharing.
For some other more lower level interface you might have a point, but
this is actually a pretty high level C++ interface to a script language
engine. Value semantics along with the reference counting and COW are
already there in the script language and the C++ interface ought to have
the same semantics.
Besides, the goal is to make the usage simple, not the implementation.
> Synchronization problems and ....
> After all OOP is about objects which change states.
I fail to see how refcount changing is bad for thread sharing, but at the
same time any other state changing is not.
Anyway, I'm currently indeed inclined to go for the imperative style, but
only because there are several other member functions which seem more
natural in imperative style (SetPixel(), SetProperty() etc.) and I would
like to avoid any confusion.
Cheers
Paavo
Spike wrote in news:ltn0ov$u6v$1@dont-email.me:
> On 27/08/2014 09:47, Paavo Helde wrote:
>>
>> Should these operations be const member functions returning a new Array
>> (functional style) or non-const void member functions modifying the
>> object? The actual content is shared and reference-counted behind the
>> scenes so just copying an Array object is pretty cheap, so this question
>> is more about style, not performance, thus I am mostly leaving out
>> optimization issues here.
>>
>> Advantages:
>> - Conscise usage
>> - No side effects (pure functional style)
>
> Maybe I'm missing something, but if the actual content is shared you
> *have* side effects. So the functional style would be flawed.
No, COW will take care about that. The sharing should in general not be
noticable for the library user, the objects should normally behave like
genuine value objects.
Cheers
Paavo
On 27/08/2014 09:47, Paavo Helde wrote:
>
> Should these operations be const member functions returning a new Array
> (functional style) or non-const void member functions modifying the
> object? The actual content is shared and reference-counted behind the
> scenes so just copying an Array object is pretty cheap, so this question
> is more about style, not performance, thus I am mostly leaving out
> optimization issues here.
>
> Advantages:
> - Conscise usage
> - No side effects (pure functional style)
Maybe I'm missing something, but if the actual content is shared you
*have* side effects. So the functional style would be flawed.
S.
On Thu, 28 Aug 2014 09:19:39 +0200
Martijn Lievaart wrote:
> On Wed, 27 Aug 2014 23:26:23 +0200, Melzzzzz wrote:
>
> > On Wed, 27 Aug 2014 02:47:57 -0500 Paavo Helde
> > wrote:
> >
> (snip)
> >>
> >> Should these operations be const member functions returning a new
> >> Array (functional style) or non-const void member functions
> >> modifying the object? The actual content is shared and
> >> reference-counted behind the scenes so just copying an Array
> >> object is pretty cheap, so this question is more about style, not
> >> performance, thus I am mostly leaving out optimization issues here.
> >
> (snip)
> >
> > I have some experience with Haskell and I can tell that pure
> > functional style is performance killer...
>
> ... unless the actual content is shared and reference counted behind
> the scenes.
For linked lists and trees that is true. Only updated nodes are copied,
rest are shared. But array update requires copying whole array,
that is why in Haskell mutable arrays (imperative style) are used
whenever one wants performance.
>
> M4
--
press any key to continue or any other to quit...
On Wed, 27 Aug 2014 23:26:23 +0200, Melzzzzz wrote:
> On Wed, 27 Aug 2014 02:47:57 -0500 Paavo Helde
> wrote:
>
(snip)
>>
>> Should these operations be const member functions returning a new Array
>> (functional style) or non-const void member functions modifying the
>> object? The actual content is shared and reference-counted behind the
>> scenes so just copying an Array object is pretty cheap, so this
>> question is more about style, not performance, thus I am mostly leaving
>> out optimization issues here.
>
(snip)
>
> I have some experience with Haskell and I can tell that pure functional
> style is performance killer...
.... unless the actual content is shared and reference counted behind the
scenes.
M4
On Wed, 27 Aug 2014 02:47:57 -0500
Paavo Helde wrote:
> This is about a class interface design. Let's say I have an Array
> class which is able to encapsulate a N-dimensional array of a dynamic
> element type and has runtime operations for resizing the dimensions,
> converting the element type, etc.
>
> Should these operations be const member functions returning a new
> Array (functional style) or non-const void member functions modifying
> the object? The actual content is shared and reference-counted behind
> the scenes so just copying an Array object is pretty cheap, so this
> question is more about style, not performance, thus I am mostly
> leaving out optimization issues here.
Hm.
>
> *Functional style*
>
> class Array {
> public:
> Array Reshape(size_t width, size_t height, size_t depth) const;
> template Array Convert() const;
> // ...
> };
> Array PrepareSomeArray();
> void ConsumeArray(Array a);
>
> // usage:
> ConsumeArray( PrepareSomeArray().Reshape(10,20,30).Convert());
>
> Advantages:
> - Conscise usage
> - No side effects (pure functional style)
- you have to make copies of Array on both Reshape and Convert
operations which is more expensive than just mutating operation.
If not, users would be really surprised ;)
Array a = PrepareSomeArray();
Array b = a.Reshape(10,20,30);
// ... work with a
And you *have* side effect, which is maintaining reference count....
>
> Drawbacks:
> - Deviates from the std::vector usage (e.g. std::vector::resize()).
> - The caller may forgot to assign the result to a new object and
> assume the modifications happen in place
- Lot of copies.
>
> *Imperative style*
>
> class Array {
> public:
> void Reshape(size_t width, size_t height, size_t depth);
> template void Convert();
> // ...
> };
> Array PrepareSomeArray();
> void ConsumeArray(Array a);
>
> // usage example:
> Array a = PrepareSomeArray();
> a.Reshape(10,20,30);
> a.Convert();
> ConsumeArray(a);
>
> Advantages:
> - Follows std::vector design
>
> Drawbacks:
> - More verbose usage
> - Code is less robust because of side effects
> - Worse optimization possibilities because unneeded named objects
> tend to remain lying around.
>
> So, any comments or insights? Which style should I aim for? This
> interface is intended for external library users, but at the moment
> we don't have any yet, so cannot ask directly. Which style would
> *you* prefer when given the task to accomplish something by such a
> library?
>
> Maybe I should provide both styles? They would probably need
> different member function names, are there any naming conventions for
> these different styles?
I have some experience with Haskell and I can tell that pure functional
style is performance killer. I always end up writing imperative
style if I want performance.
Go for imperative style and make your implementation simpler: get
rid of reference counting as it is not good for thread sharing.
Synchronization problems and ....
After all OOP is about objects which change states.
>
> TIA
> Paavo
>
--
press any key to continue or any other to quit...
Jorgen Grahn wrote in
news:slrnlvscn4.1ks.grahn+nntp@frailea.sa.invalid:
> On Wed, 2014-08-27, Paavo Helde wrote:
> ...
>> So, any comments or insights? Which style should I aim for? This
>> interface is intended for external library users, but at the moment we
>> don't have any yet, so cannot ask directly.
>
> Another non-answer: don't expect the library to turn out really well
> if there are no users.
Right. That's what I'm a bit worried about. At the moment it is a
proprietary library and the intended users are mostly other departments
who want to include our data analysis library in their products.
Currently they are mostly content of using it in a separate executable
and communicating over disk files or HTTP.
We have some hopes to open-source it at some time-point, which would
certainly add more users. But this decision is not mine to make.
> Can you postpone the interface decisions, working on something else in
> the meantime? Or trick the users into arriving earlier?
This is a chicken-and-egg problem. This interface has been postponed at
least from year 2009.
Cheers
Paavo