Talk:Trait
From APIDesign
(Comment provided by jtulach - via ArticleComments extension) |
(Comment provided by Ssp - via ArticleComments extension) |
||
(8 intermediate revisions not shown.) | |||
Line 93: | Line 93: | ||
I don't know why do you think this is not type safe? You cannot add anything which is not inherited from ll_item. If you have enabled RTTI, you can dynamic cast items from list. | I don't know why do you think this is not type safe? You cannot add anything which is not inherited from ll_item. If you have enabled RTTI, you can dynamic cast items from list. | ||
+ | <source lang="cpp"> | ||
class persons_list : public llist { | class persons_list : public llist { | ||
public: | public: | ||
Line 100: | Line 101: | ||
} | } | ||
}; | }; | ||
- | + | </source> | |
--jtulach 10:58, 13 September 2012 (CEST) | --jtulach 10:58, 13 September 2012 (CEST) | ||
+ | </div> | ||
+ | |||
+ | The question ''Why this is not type-safe?'' may reveal the progress [[OOP]] made during last twenty years. In the middle of nineties [[Java]] was claimed a safe language as it could not cause ''segmentation faults''. The [[C++]]'s '''dynamic_cast''' is a safety of similar kind. It is a runtime check. It is not bad thing, but as I am coding in [[Java]] most of the time, I was not calling for something as basic. | ||
+ | |||
+ | When I mentioned type safety, I meant compile type safety. The above dynamic cast does not guarantee that I cannot add ''animal_item'' into the ''llist'' and try to obtain ''person_item''. To achieve such kind of safety one needs a form of ''algebraic types'' (nicely illustrated in the [[OOP#Revolutionary_view|revolutionary essay]]). Both [[Scala]] and [[Java]] (since version 1.5) provide a way to marry ''algebraic types'' with [[OOP]] inheritance and thus provide compile time safety (as code snippets for the [[Trait|direct linked list]] show). | ||
+ | |||
+ | --[[User:JaroslavTulach|JaroslavTulach]] 07:42, 17 September 2012 (UTC) | ||
+ | == jtulach said ... == | ||
+ | |||
+ | <div class='commentBlock'> | ||
+ | |||
+ | <source lang="cpp"> | ||
+ | template<class ITEM> | ||
+ | class llist { | ||
+ | protected: | ||
+ | ITEM *first; | ||
+ | public: | ||
+ | void add(ITEM& p) | ||
+ | { | ||
+ | p.prev=NULL; | ||
+ | p.next=first; | ||
+ | first=&p; | ||
+ | } | ||
+ | void remove(ITEM& p) | ||
+ | { | ||
+ | ITEM *pr=p.prev; | ||
+ | ITEM *nx=p.next; | ||
+ | if(pr) pr->next=nx; | ||
+ | else first=nx; | ||
+ | if(nx) nx->prev=pr; | ||
+ | } | ||
+ | |||
+ | ITEM *get(int idx) | ||
+ | { | ||
+ | ITEM *i=first; | ||
+ | while((i)&&(idx-->0)) | ||
+ | i=i->next; | ||
+ | |||
+ | return i; | ||
+ | } | ||
+ | |||
+ | llist(void) { first=NULL; }; | ||
+ | ~llist() { }; | ||
+ | }; | ||
+ | |||
+ | // not inherited, requires to define next and prev properties | ||
+ | class person_item { | ||
+ | protected: | ||
+ | int age; | ||
+ | const char* name; | ||
+ | |||
+ | public: | ||
+ | person_item* next; | ||
+ | person_item* prev; | ||
+ | |||
+ | person_item (int age, const char* name) { this->age=age; this->name=name; } | ||
+ | }; | ||
+ | |||
+ | |||
+ | template<class ITEM> | ||
+ | class ll_item { | ||
+ | friend llist <ITEM>; | ||
+ | private: | ||
+ | ITEM* next; | ||
+ | ITEM* prev; | ||
+ | }; | ||
+ | |||
+ | // inherited from ll_item template which defines next and prev for me | ||
+ | class animal_item : public ll_item <animal_item>{ | ||
+ | protected: | ||
+ | const char* name; | ||
+ | |||
+ | public: | ||
+ | |||
+ | animal_item(const char* name) : ll_item <animal_item>() { this->name=name; } | ||
+ | }; | ||
+ | |||
+ | int main(int argc, char* argv[]) | ||
+ | { | ||
+ | person_item a(10, "Ben"); | ||
+ | person_item b(20, "Nora"); | ||
+ | person_item c(30, "John"); | ||
+ | |||
+ | animal_item x("Fifi"); | ||
+ | animal_item y("Bobika"); | ||
+ | animal_item z("Bill"); | ||
+ | |||
+ | llist <person_item> persons; | ||
+ | persons.add(b); | ||
+ | persons.add(c); | ||
+ | persons.add(a); | ||
+ | // persons.add(x); - compilation fails | ||
+ | |||
+ | llist <animal_item> animals; | ||
+ | animals.add(x); | ||
+ | animals.add(y); | ||
+ | animals.add(z); | ||
+ | } | ||
+ | </source> | ||
+ | |||
+ | --jtulach 14:04, 17 September 2012 (CEST) | ||
+ | </div> | ||
+ | |||
+ | Right. This is it. | ||
+ | |||
+ | I didn't know about the option used when defining ''person_item'' - e.g. to just expose the ''next'' and ''prev'' publicly. Not that I'd prefer that solution (as it breaks the encapsulation), but shows the power of [[C++]] ''always recompiled'' templates. [[Java]]'s generics are compiled only once and need to use the ''class animal_item : public ll_item <animal_item>'' approach. | ||
+ | |||
+ | Thanks. I'll publish the code somehow, sometimes on the [[blogs]]. | ||
+ | |||
+ | --[[User:JaroslavTulach|JaroslavTulach]] 08:34, 18 September 2012 (UTC) | ||
+ | == jtulach said ... == | ||
+ | |||
+ | <div class='commentBlock'> | ||
+ | With a small modification you can keep next and prev private but using ll_item template is better approach. | ||
+ | |||
+ | <source lang="cpp"> | ||
+ | class person_item { | ||
+ | private: | ||
+ | friend llist <person_item>; | ||
+ | person_item* next; | ||
+ | person_item* prev; | ||
+ | |||
+ | protected: | ||
+ | int age; | ||
+ | const char* name; | ||
+ | |||
+ | public: | ||
+ | person_item (int age, const char* name) { this->age=age; this->name=name; } | ||
+ | }; | ||
+ | </source> | ||
+ | |||
+ | |||
+ | --jtulach 10:49, 18 September 2012 (CEST) | ||
+ | </div> | ||
+ | == bob said ... == | ||
+ | |||
+ | <div class='commentBlock'> | ||
+ | trait Listable[T <: Listable[T]] | ||
+ | |||
+ | should be changed to | ||
+ | |||
+ | trait Listable[T] | ||
+ | |||
+ | The additional bound does not provide any benefit | ||
+ | |||
+ | --bob 07:20, 5 December 2012 (CET) | ||
+ | |||
+ | Thanks. Fixed in [http://source.apidesign.org/hg/apidesign/rev/87bcc647df63 87bcc647df63]. | ||
+ | |||
+ | --[[User:JaroslavTulach|JaroslavTulach]] 15:09, 5 December 2012 (UTC) | ||
+ | |||
+ | </div> | ||
+ | == Ssp said ... == | ||
+ | |||
+ | <div class='commentBlock'> | ||
+ | Hi, The Community BBQ will take place on Torridge Way between 4.00 6.00pm on Saturday 5th November. We won't be hanivg fireworks, but have timed the BBQ so that people can go on down to the Hoe later or watch that display from various excellent viewing points around Efford. I'll send you an email with the flier about this event and add you to our regular circulation about events. Thanks for your interest and hope you will join us on Saturday, do feel free to bring others along. Jacqi, Community Coordinator | ||
+ | |||
+ | --Ssp 18:47, 21 October 2013 (CEST) | ||
</div> | </div> |
Current revision
Comments on Trait <comments />
Contents |
Miles Elam said ...
I am primarily interested in properly typing the multiple class encapsulation case. E.g. having prev/next field in the item class and manipulating them in only by the list. Moreover I'd like to write this (and type this) in a generic way. Looks like it will be possible to do it in C++, but the solution will definitely not be like in Java/Scala - rather upside-down...
--JaroslavTulach 22:25, 5 September 2012 (UTC)
jtulach said ...
The article is more about STL overhead. I personally don't like and don't use STL. I think following C++ implementation is as fast as implementation in C.
class ll_item; class llist { public: void add(ll_item& p); void remove(ll_item& p); llist(void); ~llist(); }; class ll_item { friend llist; private: ll_item* next; ll_item* prev; }; class person_item : public ll_item { protected: int age; const char* name; public: person_item(int age, const char* name); }; class animal_item : public ll_item { protected: const char* name; public: animal_item(const char* name); }; int main(int argc, char* argv[]) { person_item a(10, "Ben"); person_item b(20, "Nora"); person_item c(30, "John"); animal_item x("Fifi"); animal_item y("Bobika"); animal_item z("Bill"); llist l; l.add(b); l.add(c); l.add(a); l.add(x); l.add(y); l.add(z); }
--jtulach 21:07, 11 September 2012 (CEST)
Right, now the question is how to generify this (as the example above does not feel type safe enough) - e.g. how to turn into a C++ template?
I don't want to have list of items, but rather list of persons and another list of animals. When I ask for an item from each list, I would expect I get a person in the former case and an animal in the latter. Right now I just get a ll_item. That is efficient, but not really typesafe.
I have an unfinished prototype with template and compared to Java or Scala it feels a bit upside down. Interesting clash of cultures.
--JaroslavTulach 08:26, 12 September 2012 (UTC)
jtulach said ...
I don't know why do you think this is not type safe? You cannot add anything which is not inherited from ll_item. If you have enabled RTTI, you can dynamic cast items from list.
class persons_list : public llist { public: person_item *get(int idx) { return dynamic_cast<person_item *>(llist::get(idx)); } };
--jtulach 10:58, 13 September 2012 (CEST)
The question Why this is not type-safe? may reveal the progress OOP made during last twenty years. In the middle of nineties Java was claimed a safe language as it could not cause segmentation faults. The C++'s dynamic_cast is a safety of similar kind. It is a runtime check. It is not bad thing, but as I am coding in Java most of the time, I was not calling for something as basic.
When I mentioned type safety, I meant compile type safety. The above dynamic cast does not guarantee that I cannot add animal_item into the llist and try to obtain person_item. To achieve such kind of safety one needs a form of algebraic types (nicely illustrated in the revolutionary essay). Both Scala and Java (since version 1.5) provide a way to marry algebraic types with OOP inheritance and thus provide compile time safety (as code snippets for the direct linked list show).
--JaroslavTulach 07:42, 17 September 2012 (UTC)
jtulach said ...
template<class ITEM> class llist { protected: ITEM *first; public: void add(ITEM& p) { p.prev=NULL; p.next=first; first=&p; } void remove(ITEM& p) { ITEM *pr=p.prev; ITEM *nx=p.next; if(pr) pr->next=nx; else first=nx; if(nx) nx->prev=pr; } ITEM *get(int idx) { ITEM *i=first; while((i)&&(idx-->0)) i=i->next; return i; } llist(void) { first=NULL; }; ~llist() { }; }; // not inherited, requires to define next and prev properties class person_item { protected: int age; const char* name; public: person_item* next; person_item* prev; person_item (int age, const char* name) { this->age=age; this->name=name; } }; template<class ITEM> class ll_item { friend llist <ITEM>; private: ITEM* next; ITEM* prev; }; // inherited from ll_item template which defines next and prev for me class animal_item : public ll_item <animal_item>{ protected: const char* name; public: animal_item(const char* name) : ll_item <animal_item>() { this->name=name; } }; int main(int argc, char* argv[]) { person_item a(10, "Ben"); person_item b(20, "Nora"); person_item c(30, "John"); animal_item x("Fifi"); animal_item y("Bobika"); animal_item z("Bill"); llist <person_item> persons; persons.add(b); persons.add(c); persons.add(a); // persons.add(x); - compilation fails llist <animal_item> animals; animals.add(x); animals.add(y); animals.add(z); }
--jtulach 14:04, 17 September 2012 (CEST)
Right. This is it.
I didn't know about the option used when defining person_item - e.g. to just expose the next and prev publicly. Not that I'd prefer that solution (as it breaks the encapsulation), but shows the power of C++ always recompiled templates. Java's generics are compiled only once and need to use the class animal_item : public ll_item <animal_item> approach.
Thanks. I'll publish the code somehow, sometimes on the blogs.
--JaroslavTulach 08:34, 18 September 2012 (UTC)
jtulach said ...
With a small modification you can keep next and prev private but using ll_item template is better approach.
class person_item { private: friend llist <person_item>; person_item* next; person_item* prev; protected: int age; const char* name; public: person_item (int age, const char* name) { this->age=age; this->name=name; } };
--jtulach 10:49, 18 September 2012 (CEST)
bob said ...
trait Listable[T <: Listable[T]]
should be changed to
trait Listable[T]
The additional bound does not provide any benefit
--bob 07:20, 5 December 2012 (CET)
Thanks. Fixed in 87bcc647df63.
--JaroslavTulach 15:09, 5 December 2012 (UTC)
Ssp said ...
Hi, The Community BBQ will take place on Torridge Way between 4.00 6.00pm on Saturday 5th November. We won't be hanivg fireworks, but have timed the BBQ so that people can go on down to the Hoe later or watch that display from various excellent viewing points around Efford. I'll send you an email with the flier about this event and add you to our regular circulation about events. Thanks for your interest and hope you will join us on Saturday, do feel free to bring others along. Jacqi, Community Coordinator
--Ssp 18:47, 21 October 2013 (CEST)
Yes, this is indeed possible in C++ and is, in fact, used extensively in the C++ standard library (aka STL). For a prime example, look no further than std::string or the various pluggable memory allocators. The example given in the article appears to these eyes as one of a C programmer trying to make C++ do things like C and failing. To be more precise, if one is accessing people objects by iterator, why would a raw pointer to a person need to be manipulated in this way? In addition, what happens if the object must be accessed in multiple ways, e.g., exists in both a normal list and a sorted list (or multiple sorted lists). The C method falls down as there is no single pair of *next and *prev but rather multiple.
Don't get me wrong, C definitely has its uses. Its relative simplicity for one. However, C++'s generic algorithms and data structures should not be discarded so lightly.
--Miles Elam 20:26, 4 September 2012 (CEST)