From: Mark Wooding Date: Wed, 2 Aug 2017 09:59:28 +0000 (+0100) Subject: doc/intro.tex: Begin a (rather extensive) comparison with C++. X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~mdw/git/sod/commitdiff_plain/ca8f5d55bfc2b792edeb00e48d970b5b6b6c2c9b doc/intro.tex: Begin a (rather extensive) comparison with C++. I think this doesn't fit well here, but these things need saying somewhere. --- diff --git a/doc/intro.tex b/doc/intro.tex index 64df8a0..f4b3168 100644 --- a/doc/intro.tex +++ b/doc/intro.tex @@ -65,6 +65,189 @@ but Sod's ideology is different from that of most object systems. growing new `safety' features. \end{itemize} +\subsection{Comparison with other object systems} + +Sod's object system is significantly different in flavour\footnote{% + The pun was unintentional, but I'm happy with it.} % +from most popular object-ish languages. Indeed, it bears far more similarity +to Flavors, CLOS, and Dylan than to \Cplusplus, \Csharp, or Java (and still +less to Go or Rust). + +Of the popular languages, \Cplusplus's object system is probably closest to +Sod. Both are statically compiled, statically typed, implement single +dispatch, and have relatively simple runtime metaprogramming facilities. +\Cplusplus\ has a rich compile-time template metalanguage; Sod instead allows +compile-time metaprogramming in Common Lisp, which is probably less +convenient for simple cases but rather more pleasant for doing difficult +things. Significantly, Sod has the @|SodObject| class, of which all other +classes\footnote{% + Unless you construct a new root class of your own, which you can totally + do, but it's hard work.} % +are subclasses; \Cplusplus\ has no such root class. + +Both systems provide multiple inheritance, but go about it very differently. +The most important difference is that \Cplusplus\ provides only \emph{static + delegation}: if you have a class @|B| which defines some (virtual) member +function @|f|, and a derived class of @|D| which wants to \emph{extend} the +behaviour of @|f| on instances of @|D|, then you must explicitly call @|B::f| +at the appropriate point: +\begin{prog} + \#include \\+ + % + class B \{ \\ + public: \\ \ind + virtual void f() \{ std::cout <{}< "B@\\n"; \} \\ + virtual @~B() \{ \} \-\\ + \}; \\+ + % + class D: public B \{ \\ + public: \\ \ind + void f() \{ B::f(); std::cout <{}< "D too@\\n"; \} \-\\ + \}; +\end{prog} + +This works adequately when only single inheritance is involved. But if we +now introduce multiple inheritance, we see the problem. +\begin{prog} + \#include \\+ + % + class B \{ \\ + public: \\ \ind + virtual void f() \{ std::cout <{}< "B@\\n"; \} \\ + virtual @~B() \{ \} \-\\ + \}; \\+ + % + class X: virtual public B \{ \\ + public: \\ \ind + void f() \{ B::f(); std::cout <{}< "X after@\\n"; \} \-\\ + \}; \\+ + % + class Y: virtual public B \{ \\ + public: \\ \ind + void f() \{ std::cout <{}< "Y before@\\n"; B::f(); \} \-\\ + \}; \\+ + % + class D: public X, public Y \{ \\ + public: \\ \ind + void f() \{ X::f(); Y::f(); \} // \comment{oh, dear} \-\\ + \}; +\end{prog} +The above prints +\begin{prog} + B \\ + X after \\ + Y before \\ + B +\end{prog} +which is unlikely to be what was wanted: `B' prints twice, and the `before' +and `after' actions are both in the middle.\footnote{% + Of course, one could have arranged to call @|Y::f| before @|X::f| -- but + the important point is that one would have needed to \emph{know} that this + was necessary.} % +The problem is that correctly composing behaviour from a collection of +superclasses requires knowledge of all of the superclasses involved and how +they're supposed to work together. + +The obvious workaround is to separate the functionality -- here, printing the +messages -- from the plumbing, which arranges to do everything in the right +order. You'd end up with a @|_f| member function in each class which wanted +print something, and then every class would have a virtual @|f| which calls +the various @|_f| functions in the right order, like this: +\begin{prog} + \#include \\+ + % + class B \{ \\ + protected: \\ \ind + void _f() \{ std::cout <{}< "B@\\n"; \} \-\\ + public: \\ \ind + virtual void f() \{ _f(); \} \\ + virtual ~B() \{ \} \-\\ + \}; \\+ + % + class X: virtual public B \{ \\ + protected: \\ \ind + void _f() \{ std::cout <{}< "X after@\\n"; \} \-\\ + public: \\ \ind + void f() \{ B::_f(); _f(); \} \-\\ + \}; \\+ + % + class Y: virtual public B \{ \\ + protected: \\ \ind + void _f() \{ std::cout <{}< "Y before@\\n"; \} \-\\ + public: \\ \ind + void f() \{ _f(); B::_f(); \} \-\\ + \}; \\+ + % + class D: public X, public Y \{ \\ + public: \\ \ind + void f() \{ Y::_f(); B::_f(); X::_f(); \} \-\\ + \}; +\end{prog} +This is clearly much more cumbersome. Most disastrously, it spreads +knowledge about how the various classes' contributions to the behaviour of +@|f| fit together throughout the class graph. Also, even this approach is +only suitable for especially simple cases. Suppose @|Y| needed to add +behaviour before \emph{and} after @|B| -- maybe @|Y| is taking out a lock, +and then releasing it. Then this approach won't work any more; indeed, it's +hard to see any way to make this work without spreading knowledge about +@|Y|'s lock everywhere. + +Compare Sod's approach. +\begin{prog} + code c: includes \{ \\ + \#include \\- + \#include \\ + \#include "foo.h" \\ + \} \\+ + % + class B: SodObject \{ \\ \ind + void f() \{ puts("B"); \} \-\\ + \} \\+ + % + class X: B \{ \\ \ind + void b.f() \{ CALL_NEXT_METHOD; puts("X after"); \} \-\\ + \} \\ + % + class Y: B \{ \\ \ind + void b.f() \{ puts("Y before"); CALL_NEXT_METHOD; \} \-\\ + \} \\+ + % + class D: X, Y \{ \} +\end{prog} +This prints +\begin{prog} + Y before \\ + B \\ + X after +\end{prog} +as (I think) you'd hope. @|CALL_NEXT_METHOD| here does the job of figuring +out what to do next, according to some rather complicated rules +(described in full in \xref{sec:concepts.methods.combination}). + +Indeed, there's an even better way to write this particular case with Sod, +because Sod has dedicated \emph{method rôles}. +\begin{prog} + code c: includes \{ \\ + \#include \\- + \#include \\ + \#include "foo.h" \\ + \} \\+ + % + class B: SodObject \{ \\ \ind + void f() \{ puts("B"); \} \-\\ + \} \\+ + % + class X: B \{ \\ \ind + [role = after] void b.f() \{ puts("X after"); \} \-\\ + \} \\ + % + class Y: B \{ \\ \ind + [role = before] void b.f() \{ puts("Y before"); \} \-\\ + \} \\+ + % + class D: X, Y \{ \} +\end{prog} + %%%-------------------------------------------------------------------------- \section{About this manual} diff --git a/doc/sod.sty b/doc/sod.sty index f1ba880..5538bd7 100644 --- a/doc/sod.sty +++ b/doc/sod.sty @@ -136,6 +136,7 @@ \def\ind{\quad\=\+\kill} \def\@progcr{\futurelet\@tempa\@progcr@i} {\def\:{\gdef\@progcr@sp}\: {\@progcr}} +\atdef~{\textasciitilde} \def\@progcr@i{% \ifx\@tempa\@sptoken\let\next@\@progcr@sp\else \if1\ifx\@tempa[1\else