www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - D Ranges

reply "Chris" <wendlec tcd.ie> writes:
A short report on component programming and ranges.

A lot of my code deals with transforming and reformatting input, 
e.g. text is split into sentences and words for grammatical 
parsing (part of speech) and phonetic transcriptions. I'm using D 
ranges and component programming and I'm quite happy with 
"one-liners" like

foreach 
(bySentence().byWord().byWhateverFormat().byReformatAgain()) {
    // Whatever
}

The code is much easier to maintain, neater and more efficient 
within each component.

Sometimes, however, I wonder how I should design my ranges. It is 
hard to decide whether to use them as pure pipes or semi-output 
ranges. "Semi" because they're not sinks as defined by put() but 
still they can hold data (an array of reformatted strings for 
example) that could be accessed by using Range.data. I'm not sure 
as regards "best practice" and whether or not I'm wasting 
resources by storing data internally. On the other hand, it might 
be handy to have access to the data stored internally. Does 
anyone have a rough guide to D ranges? Like Case 1: Use XYZ, Case 
2: Use ZYX etc.
(I've read this tutorial http://ddili.org/ders/d.en/ranges.html, 
and I'd like to thank Ali for that! It helped me a lot.)

Another issue I've come across is how to integrate CP and ranges 
into an OO framework. I figure that ranges are good work horses, 
but it makes sense to keep the overall logic in an OO fashion. Or 
could it be that D's structs and ranges will replace OOP as we no 
it (a class-free system).
Sep 13 2013
next sibling parent "bearophile" <bearophileHUGS lycos.com> writes:
Chris:

 A short report on component programming and ranges.

 A lot of my code deals with transforming and reformatting 
 input, e.g. text is split into sentences and words for 
 grammatical parsing (part of speech) and phonetic 
 transcriptions. I'm using D ranges and component programming 
 and I'm quite happy with "one-liners" like

 foreach 
 (bySentence().byWord().byWhateverFormat().byReformatAgain()) {
    // Whatever
 }

In most cases today you are free to omit those (): foreach (bySentence.byWord.byWhateverFormat.byReformatAgain) { Bye, bearophile
Sep 13 2013
prev sibling next sibling parent "Dicebot" <public dicebot.lv> writes:
On Friday, 13 September 2013 at 13:31:18 UTC, bearophile wrote:
 In most cases today you are free to omit those ():

 foreach (bySentence.byWord.byWhateverFormat.byReformatAgain) {

 Bye,
 bearophile

...but you shouldn't if you care about readability (leave at least the last pair in the line) :P
Sep 13 2013
prev sibling next sibling parent "Chris" <wendlec tcd.ie> writes:
On Friday, 13 September 2013 at 13:42:07 UTC, Dicebot wrote:
 On Friday, 13 September 2013 at 13:31:18 UTC, bearophile wrote:
 In most cases today you are free to omit those ():

 foreach (bySentence.byWord.byWhateverFormat.byReformatAgain) {

 Bye,
 bearophile

...but you shouldn't if you care about readability (leave at least the last pair in the line) :P

It also helps other people (and me) to realize that it actually _does_ something and doesn't just return a value. Often (not always of course), if you omit the brackets it returns a value without doing anything. But that's just a personal convention I might abandon further down the road.
Sep 13 2013
prev sibling next sibling parent "deadalnix" <deadalnix gmail.com> writes:
On Friday, 13 September 2013 at 13:42:07 UTC, Dicebot wrote:
 On Friday, 13 September 2013 at 13:31:18 UTC, bearophile wrote:
 In most cases today you are free to omit those ():

 foreach (bySentence.byWord.byWhateverFormat.byReformatAgain) {

 Bye,
 bearophile

...but you shouldn't if you care about readability (leave at least the last pair in the line) :P

That is my gideline as well. Keep at least the last one.
Sep 13 2013
prev sibling next sibling parent "Brad Anderson" <eco gnuk.net> writes:
On Friday, 13 September 2013 at 14:39:29 UTC, Chris wrote:
 On Friday, 13 September 2013 at 13:42:07 UTC, Dicebot wrote:
 On Friday, 13 September 2013 at 13:31:18 UTC, bearophile wrote:
 In most cases today you are free to omit those ():

 foreach (bySentence.byWord.byWhateverFormat.byReformatAgain) {

 Bye,
 bearophile

...but you shouldn't if you care about readability (leave at least the last pair in the line) :P

It also helps other people (and me) to realize that it actually _does_ something and doesn't just return a value. Often (not always of course), if you omit the brackets it returns a value without doing anything. But that's just a personal convention I might abandon further down the road.

Since most ranges in std.algorithm are lazy they are usually not doing anything but setting a few members and returning a new range, often without having even touched the input range. Thinking about it I think I may start using () to denote eager versus lazy ranges in my UFCS chains.
Sep 13 2013
prev sibling next sibling parent "Chris" <wendlec tcd.ie> writes:
On Friday, 13 September 2013 at 17:35:21 UTC, Brad Anderson wrote:
 On Friday, 13 September 2013 at 14:39:29 UTC, Chris wrote:
 On Friday, 13 September 2013 at 13:42:07 UTC, Dicebot wrote:
 On Friday, 13 September 2013 at 13:31:18 UTC, bearophile 
 wrote:
 In most cases today you are free to omit those ():

 foreach (bySentence.byWord.byWhateverFormat.byReformatAgain) 
 {

 Bye,
 bearophile

...but you shouldn't if you care about readability (leave at least the last pair in the line) :P

It also helps other people (and me) to realize that it actually _does_ something and doesn't just return a value. Often (not always of course), if you omit the brackets it returns a value without doing anything. But that's just a personal convention I might abandon further down the road.

Since most ranges in std.algorithm are lazy they are usually not doing anything but setting a few members and returning a new range, often without having even touched the input range. Thinking about it I think I may start using () to denote eager versus lazy ranges in my UFCS chains.

This reminds me, I have to check whether my ranges are lazy enough (like myself).
Sep 13 2013
prev sibling next sibling parent reply "Jesse Phillips" <Jesse.K.Phillips+D gmail.com> writes:
On Friday, 13 September 2013 at 11:31:24 UTC, Chris wrote:
 Sometimes, however, I wonder how I should design my ranges. It 
 is hard to decide whether to use them as pure pipes or 
 semi-output ranges. "Semi" because they're not sinks as defined 
 by put() but still they can hold data (an array of reformatted 
 strings for example) that could be accessed by using 
 Range.data. I'm not sure as regards "best practice" and whether 
 or not I'm wasting resources by storing data internally. On the 
 other hand, it might be handy to have access to the data stored 
 internally. Does anyone have a rough guide to D ranges? Like 
 Case 1: Use XYZ, Case 2: Use ZYX etc.
 (I've read this tutorial 
 http://ddili.org/ders/d.en/ranges.html, and I'd like to thank 
 Ali for that! It helped me a lot.)

There isn't a guide in the manner you desire. In my experience I generally don't hold any more data than the value returned by front (to prevent recalculation). Right now I don't recall what situations I've needed to store the intermediary data, but I know how you feel about having a input rang which is like an output range simply returning a range to provide the chaining ability. I also generally don't find a need to specify output ranges. Output ranges are the end of the line so they kill component programming
 Another issue I've come across is how to integrate CP and 
 ranges into an OO framework. I figure that ranges are good work 
 horses, but it makes sense to keep the overall logic in an OO 
 fashion. Or could it be that D's structs and ranges will 
 replace OOP as we no it (a class-free system).

I spent much of my initial time programming in Java, so I have a good grasp of the constructs behind OOP (and a little bit of the prototyping style known to be in Javascript). I also think I have a pretty good grasp on designing for OOP, though I do have a lot I could learn. I hate OOP. I believe it has a place in software design, I just haven't found it yet. Templates cause a lot of problem, though I need to look into using the trick using /final/. Trying to do inheritance when your interface defines that it takes a range or returns a range. Though generics in C# work quite nicely. The other issue I have is that OOP is very resistant to change and testing. It is hard enough to get all the data needed for your test cases, then throw in designing mock objects and blah blah, it quickly becomes a mess. And even if you skip all the testing aspects, if you want to make changes there is a giant structure you're making changes for. Yes had I done a better job designing my structure up front to allow for such change I wouldn't be in this mess... Anyway, my recommendation isn't to build up a class structure just because that is what you would do in another language. Figure out if the usability provided by inheritance is what you want, if not struct with helper functions seems to be simplest in development and maintenance. Destroy.
Sep 13 2013
parent Jacob Carlborg <doob me.com> writes:
On 2013-09-14 08:07, H. S. Teoh wrote:

 OTOH, I find myself switching to classes just to get the reference
 semantics in other cases, even if I never actually do any inheritance.

I agree, it seems I most times want a reference type (not talking ranges here but in general). -- /Jacob Carlborg
Sep 14 2013
prev sibling next sibling parent "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Saturday, September 14, 2013 04:10:09 Jesse Phillips wrote:
 Anyway, my recommendation isn't to build up a class structure
 just because that is what you would do in another language.
 Figure out if the usability provided by inheritance is what you
 want, if not struct with helper functions seems to be simplest in
 development and maintenance. Destroy.

I find that I use classes very rarely in D. Once in a while, I need polymorphism, and in that situation, I use classes, but at least in the types of programs that I've usually been writing, polymorphism has rarely made much sense. Structs deal with most everything just fine. Classes definitely have their uses, but IMHO, they should not be the first tool to pull out of your toolbox when writing a D program. Just use them when you actually need them. - Jonathan M Davis
Sep 13 2013
prev sibling next sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Fri, Sep 13, 2013 at 10:49:23PM -0400, Jonathan M Davis wrote:
 On Saturday, September 14, 2013 04:10:09 Jesse Phillips wrote:
 Anyway, my recommendation isn't to build up a class structure
 just because that is what you would do in another language.
 Figure out if the usability provided by inheritance is what you
 want, if not struct with helper functions seems to be simplest in
 development and maintenance. Destroy.

I find that I use classes very rarely in D. Once in a while, I need polymorphism, and in that situation, I use classes, but at least in the types of programs that I've usually been writing, polymorphism has rarely made much sense. Structs deal with most everything just fine. Classes definitely have their uses, but IMHO, they should not be the first tool to pull out of your toolbox when writing a D program. Just use them when you actually need them.

I've found myself hovering between structs and classes when writing D code. I almost always use structs for ranges, just because value types incur less overhead, which does add up when you use a lot of UFCS chaining. OTOH, I find myself switching to classes just to get the reference semantics in other cases, even if I never actually do any inheritance. Trying to do reference semantics with structs, while certainly possible, is just too error-prone IME. Just a few days ago, I encountered what looked like a nasty functionality bug in my program, only to eventually discover that it was caused by a missing 'ref' in a function's struct parameter, so updates to the struct didn't persist as the code assumed it would. I found myself seriously considering using classes instead, just for the default ref semantics. While D's decision to make structs value-only and classes ref-only is certainly clever, it also introduces some tricky gotchas for the unwary. T -- May you live all the days of your life. -- Jonathan Swift
Sep 13 2013
prev sibling next sibling parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Friday, September 13, 2013 23:07:03 H. S. Teoh wrote:
 OTOH, I find myself switching to classes just to get the reference
 semantics in other cases, even if I never actually do any inheritance.
 Trying to do reference semantics with structs, while certainly possible,
 is just too error-prone IME. Just a few days ago, I encountered what
 looked like a nasty functionality bug in my program, only to eventually
 discover that it was caused by a missing 'ref' in a function's struct
 parameter, so updates to the struct didn't persist as the code assumed
 it would. I found myself seriously considering using classes instead,
 just for the default ref semantics.

In general, if you want to have structs with reference semantics, it's probably better to just give them reference semantics by making it so that any of their members which are value types are on the heap or by putting all of the struct's guts on the heap. At some point, it becomes debatable as to whether that's better than using a class, but it does have less overhead. There's also RefCounted, which does incur the bookkeeping overhead of the refcounting, but it does make it so that the memory is freed as soon as you don't need the object anymore. - Jonathan M Davis
Sep 13 2013
next sibling parent reply Dmitry Olshansky <dmitry.olsh gmail.com> writes:
15-Sep-2013 12:39, monarch_dodra пишет:
 On Saturday, 14 September 2013 at 06:18:02 UTC, Jonathan M Davis wrote:
 I have a few issues with ref counted.

 First, if you *ever* place one in an array or an AA, then you will leak.
 It is NOT designed for simply incorporating reference, but for
 deterministic finalization.

Another problem with built-in AAs...
 Second, it has an elaborate postblit and opAssign. This is not a big
 issue in itself, but their sole existence does cause dmd to generate
 code that is sub-optimal. It also means it can't take the "optimal"
 route in a lot of algorithms (array has to emplace each element
 individually, for example).

Seriously we could do a better job then that.. For instance blit the whole data, then call postblits on that range. Exception safety would be trickier to achieve. Even better one day I now expect compiler to be able to know that e.g. RefCounted is registered as ARC-capable type then the compiler should elide call to ref-counting.
 Finally, a RefCounted will implicitly cast to its payload. I think this
 is *horrible*. Before you know it, the Payload will have "jettisoned"
 its wrapper, and you'll be operating on your value-type payload "raw".
 For example:
 void foo(T t);
 RefCounted!T myRecCounted;
 foo(myRefCounted); //Passes. Oops!

Well it does a copy. Which is IMO perfectly fine. If you want to prevent that (at least in theory) you should simply mark postblit of T as disabled.
 The workaround would be to either require explicit "get" to go from ref
 counted to payload (breaking existing code), or to entirelly wrap the
 RefCounted as a member inside the struct (requires boilerplate code).

There is no need to prevent that in general case. Only RAII kind of things implemented with RefCounted would have problem copying-out the payload but these should have post-blit disabled for their "value" and consequently would not compile.
 --------

 Overall, if I need a reference semantic struct, I find it much simpler
 for it to just hold a GC pointer to a payload.

Ehm.. Isn't finalized class is just that? Plus bit of overhead for monitor.
 Though to be honest, as H.S. Teoh, I seriously wonder why I even bother,
 when I could just use a final class. With proper "private constructors +
 non-member "make" function", you can make your choice outright
 transparent to the final user too, meaning you can "fast prototype" with
 classes, and later change to structs if you think it is worth it.

This is true. And in fact I would encourage every D programmer to never expose constructors and always provide factory functions. These days constructors are constrained by a set of arcane limitations thusly suck beyond measure in every way possible. But you need them to say initialize immutable members, hence _use_ - but don't _expose_ them. -- Dmitry Olshansky
Sep 15 2013
parent Dmitry Olshansky <dmitry.olsh gmail.com> writes:
15-Sep-2013 17:02, monarch_dodra пишет:
 It depends how you look at it. If the original intent was to have a type
 with reference semantics, because your value type was so damn big (eg,
 PRNG), then that would *not* be OK (and the original point I was making
 that RefCounted is a bad contender for "just" reference semantics).

If user don't have the access to the wrapped internal type there should be no problem. E.g. the wrapped type is private and only usable inside the PRNG's module. Then having public alias for RefCounted!PRNG touted as THE PRNG should suffice. (Truly wicked folks can crack open it with a carefully applied piece of template deduction but each to their own goals). If you are afraid of these dark mages of templates you can do something like: struct RefPRNG{ RefCounted!PRNG payload; mixin ForwardCallsTo!"payload"; } And let them suck it down. IMHO there is no reason to go that far.
 If you are *trully* using it for deterministic finalization, then yeah,
 it's OK.

See above I don't see the problem. -- Dmitry Olshansky
Sep 15 2013
prev sibling parent Paulo Pinto <pjmlp progtools.org> writes:
Am 15.09.2013 10:52, schrieb Jonathan M Davis:
...

 Yeah. RefCounted could/should definitely be improved, but the concept is
 essentially the same as smart_ptr in C++11. In many ways, smart pointers are
 far superior to using the GC, and I really think that we should do a better
 job of supporting them and promoting them. There are obviously plenty of cases
 where the GC is better or even necessary, but in many cases - particularly
 with objects - smart pointers are arguably a much better way to go.

 - Jonathan M Davis

Actually, it is much easier to write exception safe code in languages with GC. Or in case of ref-counting if the compiler is aware of them. The problems with exception safe code in C++ with memory leaks are related to its C compatibility rules and having to cope to optional exceptions. After watching the Going Native 2013 videos, I really wonder how much of a push C++ will win back from younger developers, outside the game industry and device drivers. Now back to C++ coding. :) -- Paulo
Sep 15 2013
prev sibling next sibling parent "monarch_dodra" <monarchdodra gmail.com> writes:
On Saturday, 14 September 2013 at 06:18:02 UTC, Jonathan M Davis 
wrote:
 On Friday, September 13, 2013 23:07:03 H. S. Teoh wrote:
 OTOH, I find myself switching to classes just to get the 
 reference
 semantics in other cases, even if I never actually do any 
 inheritance.
 Trying to do reference semantics with structs, while certainly 
 possible,
 is just too error-prone IME. Just a few days ago, I 
 encountered what
 looked like a nasty functionality bug in my program, only to 
 eventually
 discover that it was caused by a missing 'ref' in a function's 
 struct
 parameter, so updates to the struct didn't persist as the code 
 assumed
 it would. I found myself seriously considering using classes 
 instead,
 just for the default ref semantics.

In general, if you want to have structs with reference semantics, it's probably better to just give them reference semantics by making it so that any of their members which are value types are on the heap or by putting all of the struct's guts on the heap. At some point, it becomes debatable as to whether that's better than using a class, but it does have less overhead. There's also RefCounted, which does incur the bookkeeping overhead of the refcounting, but it does make it so that the memory is freed as soon as you don't need the object anymore. - Jonathan M Davis

I have a few issues with ref counted. First, if you *ever* place one in an array or an AA, then you will leak. It is NOT designed for simply incorporating reference, but for deterministic finalization. Second, it has an elaborate postblit and opAssign. This is not a big issue in itself, but their sole existence does cause dmd to generate code that is sub-optimal. It also means it can't take the "optimal" route in a lot of algorithms (array has to emplace each element individually, for example). Finally, a RefCounted will implicitly cast to its payload. I think this is *horrible*. Before you know it, the Payload will have "jettisoned" its wrapper, and you'll be operating on your value-type payload "raw". For example: void foo(T t); RefCounted!T myRecCounted; foo(myRefCounted); //Passes. Oops! The workaround would be to either require explicit "get" to go from ref counted to payload (breaking existing code), or to entirelly wrap the RefCounted as a member inside the struct (requires boilerplate code). -------- Overall, if I need a reference semantic struct, I find it much simpler for it to just hold a GC pointer to a payload. Though to be honest, as H.S. Teoh, I seriously wonder why I even bother, when I could just use a final class. With proper "private constructors + non-member "make" function", you can make your choice outright transparent to the final user too, meaning you can "fast prototype" with classes, and later change to structs if you think it is worth it.
Sep 15 2013
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Sunday, September 15, 2013 10:39:40 monarch_dodra wrote:
 I have a few issues with ref counted.
 
 First, if you *ever* place one in an array or an AA, then you
 will leak. It is NOT designed for simply incorporating reference,
 but for deterministic finalization.
 
 Second, it has an elaborate postblit and opAssign. This is not a
 big issue in itself, but their sole existence does cause dmd to
 generate code that is sub-optimal. It also means it can't take
 the "optimal" route in a lot of algorithms (array has to emplace
 each element individually, for example).
 
 Finally, a RefCounted will implicitly cast to its payload. I
 think this is *horrible*. Before you know it, the Payload will
 have "jettisoned" its wrapper, and you'll be operating on your
 value-type payload "raw". For example:
 void foo(T t);
 RefCounted!T myRecCounted;
 foo(myRefCounted); //Passes. Oops!
 
 The workaround would be to either require explicit "get" to go
 from ref counted to payload (breaking existing code), or to
 entirelly wrap the RefCounted as a member inside the struct
 (requires boilerplate code).

Yeah. RefCounted could/should definitely be improved, but the concept is essentially the same as smart_ptr in C++11. In many ways, smart pointers are far superior to using the GC, and I really think that we should do a better job of supporting them and promoting them. There are obviously plenty of cases where the GC is better or even necessary, but in many cases - particularly with objects - smart pointers are arguably a much better way to go. - Jonathan M Davis
Sep 15 2013
prev sibling next sibling parent "monarch_dodra" <monarchdodra gmail.com> writes:
On Sunday, 15 September 2013 at 12:17:34 UTC, Dmitry Olshansky 
wrote:
 15-Sep-2013 12:39, monarch_dodra пишет:
 On Saturday, 14 September 2013 at 06:18:02 UTC, Jonathan M 
 Davis wrote:
 I have a few issues with ref counted.

 First, if you *ever* place one in an array or an AA, then you 
 will leak.
 It is NOT designed for simply incorporating reference, but for
 deterministic finalization.

Another problem with built-in AAs...
 Second, it has an elaborate postblit and opAssign. This is not 
 a big
 issue in itself, but their sole existence does cause dmd to 
 generate
 code that is sub-optimal. It also means it can't take the 
 "optimal"
 route in a lot of algorithms (array has to emplace each element
 individually, for example).

Seriously we could do a better job then that.. For instance blit the whole data, then call postblits on that range. Exception safety would be trickier to achieve. Even better one day I now expect compiler to be able to know that e.g. RefCounted is registered as ARC-capable type then the compiler should elide call to ref-counting.

Well, if a struct has no elaborate opAssign, then "=" *is* blit+postblit. And it doesn't get much more efficient than that. If not, you have to pull out the memcpy, and things just go downhill from there.
 Finally, a RefCounted will implicitly cast to its payload. I 
 think this
 is *horrible*. Before you know it, the Payload will have 
 "jettisoned"
 its wrapper, and you'll be operating on your value-type 
 payload "raw".
 For example:
 void foo(T t);
 RefCounted!T myRecCounted;
 foo(myRefCounted); //Passes. Oops!

Well it does a copy. Which is IMO perfectly fine. If you want to prevent that (at least in theory) you should simply mark postblit of T as disabled.

It depends how you look at it. If the original intent was to have a type with reference semantics, because your value type was so damn big (eg, PRNG), then that would *not* be OK (and the original point I was making that RefCounted is a bad contender for "just" reference semantics). If you are *trully* using it for deterministic finalization, then yeah, it's OK.
Sep 15 2013
prev sibling next sibling parent "Chris" <wendlec tcd.ie> writes:
On Saturday, 14 September 2013 at 02:10:13 UTC, Jesse Phillips 
wrote:
 There isn't a guide in the manner you desire.

 In my experience I generally don't hold any more data than the 
 value returned by front (to prevent recalculation). Right now I 
 don't recall what situations I've needed to store the 
 intermediary data,

 but I know how you feel about having a input rang which is like 
 an output range simply returning a range to provide the 
 chaining ability.

Yes, this occurs sometimes. But I think it's due to my lack of experience with ranges, that's why I was asking, if there was a rough guide. I don't think I really need to store intermediate data in my components. I'm not sure, however, if I should slowly migrate from OOP to structs. A of now I use them in two scenarios: 1. input / output ranges (flexible and interchangeable "workhorses") 2. storage of user defined data types, e.g. you could have a struct like this for an entry in a dictionary: struct LexEntry { string lemma = "digital"; string transcription = "Some IPA symbols"; string[] definition = ["1. bla bla", "2. bla bla"]; } At the moment, I use a lot of singletons in my program, because a lot of the data and data processing is handled by designated classes that do not to be reinstantiated. They just sit there waiting for input. In fact, without even noticing it, I designed parts of my program in an OO fashion that, in a way, makes OOP superfluous. So I'm beginning to wonder whether classes are really necessary. If I need features like sublcassing, I could just right another input range and add it to the chain. I'm still hesitant because a. if it's not broke, don't fix it and b. there might be some usage scenario when I'll need OO design and that I cannot yet foresee. But I agree that OO design is hard to unittest, it's not easy to single out a component and test it, because it's like a neurological network.
Sep 16 2013
prev sibling parent "Chris" <wendlec tcd.ie> writes:
On Monday, 16 September 2013 at 13:05:56 UTC, Chris wrote:
 On Saturday, 14 September 2013 at 02:10:13 UTC, Jesse Phillips 
 wrote:
 There isn't a guide in the manner you desire.

 In my experience I generally don't hold any more data than the 
 value returned by front (to prevent recalculation). Right now 
 I don't recall what situations I've needed to store the 
 intermediary data,

 but I know how you feel about having a input rang which is 
 like an output range simply returning a range to provide the 
 chaining ability.

Yes, this occurs sometimes. But I think it's due to my lack of experience with ranges, that's why I was asking, if there was a rough guide. I don't think I really need to store intermediate data in my components. I'm not sure, however, if I should slowly migrate from OOP to structs. A of now I use them in two scenarios: 1. input / output ranges (flexible and interchangeable "workhorses") 2. storage of user defined data types, e.g. you could have a struct like this for an entry in a dictionary: struct LexEntry { string lemma = "digital"; string transcription = "Some IPA symbols"; string[] definition = ["1. bla bla", "2. bla bla"]; } At the moment, I use a lot of singletons in my program, because a lot of the data and data processing is handled by designated classes that do not to be reinstantiated. They just sit there waiting for input. In fact, without even noticing it, I designed parts of my program in an OO fashion that, in a way, makes OOP superfluous. So I'm beginning to wonder whether classes are really necessary. If I need features like sublcassing, I could just right another input range and add it to the chain. I'm still hesitant because a. if it's not broke, don't fix it and b. there might be some usage scenario when I'll need OO design and that I cannot yet foresee. But I agree that OO design is hard to unittest, it's not easy to single out a component and test it, because it's like a neurological network.

that do not _have_ to be reinstantiated I could just right <= write (sorry, it's the Monday bug in my head!)
Sep 16 2013