X-Git-Url: https://git.distorted.org.uk/~mdw/sod/blobdiff_plain/caa6f4b99a79f95d15a6cc1e5b8dd27ea48b4d03..638cd32270bb6aa0596a594f590d2f093be5fd7a:/doc/intro.tex

diff --git a/doc/intro.tex b/doc/intro.tex
index d509925..67a220f 100644
--- a/doc/intro.tex
+++ b/doc/intro.tex
@@ -29,9 +29,9 @@ Sod is an object system for the C programming language.
 
 The software distribution for Sod contains two main parts:
 \begin{itemize}
-\item a translator, or preprocessor, similar in spirit to \manpage{lex}{1} or
-  \manpage{yacc}{1}, which reads input files containing class definitions,
-  and writes C source code and header files; and
+\item a translator, or preprocessor, similar in spirit to \man{lex}{1} or
+  \man{yacc}{1}, which reads input files containing class definitions, and
+  writes C source code and header files; and
 \item a very small runtime library, containing the built-in base classes and
   some simple utility macros and functions for working with instances and
   classes.
@@ -65,6 +65,192 @@ 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 @|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 <iostream>                                          \\+
+                                                                %
+  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 <iostream>                                          \\+
+                                                                %
+  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.  And you still end up with two copies of `B'.} %
+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.  Messing with virtual base classes has
+eliminated the problem of duplicating @|B|'s state, but has done nothing to
+help avoid duplicating @|B|'s \emph{behaviour} -- which is a shame, because
+duplicating one without the other is going to end badly.
+
+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 <iostream>                                          \\+
+                                                                %
+  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 <stdio.h>                                           \\-
+  \#include <sod.h>                                             \\
+  \#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 <stdio.h>                                           \\-
+  \#include <sod.h>                                             \\
+  \#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}
 
