chiark / gitweb /
doc/intro.tex: Begin a (rather extensive) comparison with C++.
authorMark Wooding <mdw@distorted.org.uk>
Wed, 2 Aug 2017 09:59:28 +0000 (10:59 +0100)
committerMark Wooding <mdw@distorted.org.uk>
Fri, 8 Jun 2018 18:58:40 +0000 (19:58 +0100)
I think this doesn't fit well here, but these things need saying
somewhere.

doc/intro.tex
doc/sod.sty

index 64df8a00d25a7ef6d8bef11892b9c5bc640d461c..f4b3168d1e5f59a01120d8cffe254741a0545c28 100644 (file)
@@ -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 <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.} %
+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 <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}
 
index f1ba880106204594a690bb295801c39991408cdf..5538bd78f2709e554d9639c2b45b25ea864d6dae 100644 (file)
 \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