Index: Makefile.in =================================================================== diff -u -rc4235dca165a2bafa2214e6b2ece913ffd74b19d -r88e54fa5bb23eb546abf7f676afa03aa697ac813 --- Makefile.in (.../Makefile.in) (revision c4235dca165a2bafa2214e6b2ece913ffd74b19d) +++ Makefile.in (.../Makefile.in) (revision 88e54fa5bb23eb546abf7f676afa03aa697ac813) @@ -234,6 +234,7 @@ $(src_doc_dir)/example-scripts/rosetta-serialization.html \ $(src_doc_dir)/example-scripts/rosetta-singleton.html \ $(src_doc_dir)/example-scripts/rosetta-unknown-method.html \ + $(src_doc_dir)/example-scripts/ruby-mixins.html \ $(src_doc_dir)/example-scripts/tk-horse-race.html \ $(src_doc_dir)/example-scripts/tk-locomotive.html \ $(src_doc_dir)/example-scripts/tk-mini.html \ @@ -513,6 +514,7 @@ $(TCLSH) $(src_doc_dir_native)/example-scripts/rosetta-serialization.tcl -libdir $(PLATFORM_DIR) $(TESTFLAGS) $(TCLSH) $(src_doc_dir_native)/example-scripts/rosetta-singleton.tcl -libdir $(PLATFORM_DIR) $(TESTFLAGS) $(TCLSH) $(src_doc_dir_native)/example-scripts/rosetta-unknown-method.tcl -libdir $(PLATFORM_DIR) $(TESTFLAGS) + $(TCLSH) $(src_doc_dir_native)/example-scripts/ruby-mixins.tcl -libdir $(PLATFORM_DIR) $(TESTFLAGS) $(TCLSH) $(src_doc_dir_native)/example-scripts/traits-composite.tcl -libdir $(PLATFORM_DIR) $(TESTFLAGS) $(TCLSH) $(src_doc_dir_native)/example-scripts/traits-simple.tcl -libdir $(PLATFORM_DIR) $(TESTFLAGS) Index: TODO =================================================================== diff -u -r5ce68a42506fcc981cea2431afa1b09b476e667a -r88e54fa5bb23eb546abf7f676afa03aa697ac813 --- TODO (.../TODO) (revision 5ce68a42506fcc981cea2431afa1b09b476e667a) +++ TODO (.../TODO) (revision 88e54fa5bb23eb546abf7f676afa03aa697ac813) @@ -4002,6 +4002,9 @@ - start error messages with a lower case word for consistency and to follow closer to Tcl's conventions +Documentation: +- added design study ruby-mixins.tcl to example-docs and regression test + ======================================================================== TODO: - document "/obj/ info name" Index: doc/example-scripts/ruby-mixins.html =================================================================== diff -u --- doc/example-scripts/ruby-mixins.html (revision 0) +++ doc/example-scripts/ruby-mixins.html (revision 88e54fa5bb23eb546abf7f676afa03aa697ac813) @@ -0,0 +1,1133 @@ + + + + + +Listing of doc/example-scripts/ruby-mixins.tcl + + + + + +
+
+
+

Design study to show differences between NX/XOTcl style mixin +classes and Ruby’s mixin modules. The example shows that the +dynamic class structure of NX (and XOTcl) is able to support +Ruby-style mixins (called modules) and decorator style mixins +(named after the design pattern Decorator) at the same time.

+
+
+
package req nx
+

One important difference between mixin classes in NX and Ruby’s +mixins is the precedence order. While in NX, mixins are decorators +(the mixins have higher precedence than the intrinsic classes, +therefore a mixin class can overload the methods of the current +class), the mixins of Ruby have a lower precedence (they extend the +base behavior; although Ruby’s modules are not full classes, they +are folded into the intrinsic class hierarchy).

+

We define the method module, which behaves somewhat similar to +Ruby’s module command and adds the provided class to the +precedence order after the current class. The easiest way to achieve +this is via multiple inheritance (i.e. via the superclass +relationship).

+
+
+
nx::Class eval {
+  :protected method module {name:class} {
+    :configure -superclass [concat $name [:info superclass]]
+  }
+}
+

For illustration of the behavior of module we define a class +Enumerable somewhat inspired by the Ruby module with the same +name. We define here just the methods map, each, count, and +count_if.

+
+
+
nx::Class create Enumerable {
+  :property members:0..n
+
+  # The method 'each' applies the provided block on every element of
+  # 'members'
+  :public method each {var block} {
+    foreach member ${:members} {
+      uplevel [list set $var $member]
+      uplevel $block
+    }
+  }
+
+  # The method 'map' applies the provided block on every element of
+  # 'members' and returns a list, where every element is the mapped
+  # result of the source.
+  :public method map {var block} {
+    set result [list]
+    :each $var {
+      uplevel [list set $var [set $var]]
+      lappend result [uplevel $block]
+    }
+    return $result
+  }
+
+  # The method 'count' returns the number of elements.
+  :public method count {} {
+    return [llength ${:members}]
+  }
+
+  # The method 'count_if' returns the number of elements for which
+  # the provided expression is true.
+  :public method count_if {var expr} {
+    set result 0
+    :each $var {
+      incr result [expr $expr]
+    }
+    return $result
+  }
+}
+

Create a class Group, which is essentially a subclass of "Enumerable".

+
+
+
nx::Class create Group {
+  #
+  # Include the "module" Enumerable
+  #
+  :module Enumerable
+}
+

Define now a group g1 with the three provided members.

+
+
+
% Group create g1 -members {mini trix trax}
+::g1
+

Since the definition of Group includes the module Enumerable, +this class is listed in the precedence order after the class Group:

+
+
+
% g1 info precedence
+::Group ::Enumerable ::nx::Object
+

Certainly, we can call the methods of Enumerable as usual:

+
+
+
% g1 count
+3
+
+% g1 map x {list pre-$x-post}
+pre-mini-post pre-trix-post pre-trax-post
+
+% g1 count_if x {[string match tr*x $x] > 0}
+2
+

To show the difference between a module and a decorator mixin we +define a class named Mix with the single method count, which +wraps the result of the underlaying count method between the +alpha and omega.

+
+
+
nx::Class create Mix {
+  :public method count {} {
+    return [list alpha [next] omega]
+  }
+}
+

When the mixin class is added to g1, it is added to the front of +the precedence list. A decorator is able to modify the behavior of +all of the methods of the class, where it is mixed into.

+
+
+
% g1 mixin Mix
+::Mix
+
+% g1 info precedence
+::Mix ::Group ::Enumerable ::nx::Object
+
+% g1 count
+alpha 3 omega
+

For the time being, remove the mixin class again.

+
+
+
% g1 mixin ""
+% g1 info precedence
+::Group ::Enumerable ::nx::Object
+

An important difference between NX/XOTcl style mixins (decorators) +and Ruby style modules is that the decorator will have always a +higher precedence than the intrinsic classes, while the module is +folded into the precedence path.

+

Define a class ATeam that uses Enumerable in the style of a Ruby +module. The class might refine some of the provided methods. We +refined the method each, which is used as well by the other +methods. In general, by defining each one can define very +different kind of enumerators (for lists, databases, etc.).

+

Since Enumerable is a module, the definition of each in the +class ATeam has a higher precedence than the definition in the +class Enumerable. If Enumerable would be a decorator style mixin +class, it would not e possible to refine the definition in the class +ATeam, but maybe via another mixin class.

+
+
+
nx::Class create ATeam {
+  #
+  # Include the "module" Enumerable
+  #
+  :module Enumerable
+
+  #
+  # Overload "each"
+  #
+  :public method each {var block} {
+    foreach member ${:members} {
+      uplevel [list set $var $member-[string length $member]]
+      uplevel $block
+    }
+  }
+
+  #
+  # Use "map", which uses the "each" method defined in this class.
+  #
+  :public method foo {} {
+    return [:map x {string totitle $x}]
+  }
+}
+

Define now a team t1 with the three provided members.

+
+
+
% ATeam create t1 -members {arthur bill chuck}
+::t1
+

As above, the precedence of ATeam is higher than the precedence of +Enumerable. Therefore, the object t1 uses the method each specialized in +class ATeam:

+
+
+
% t1 info precedence
+::ATeam ::Enumerable ::nx::Object
+
+% t1 foo
+Arthur-6 Bill-4 Chuck-5
+

The class ATeam can be specialized further by a class SpecialForce:

+
+
+
nx::Class create SpecialForce -superclass ATeam {
+  # ...
+}
+

Define a special force s1 with the four provided members.

+
+
+
% SpecialForce create s1 -members {Donald Micky Daniel Gustav}
+::s1
+

As above, the precedence of Enumerable is lower then the +precedence of ATeam and Enumerable. Therefore ATeam can refine +the behavior of Enumerable, the class SpecialForce can refine +the behavior of ATeam.

+
+
+
% s1 info precedence
+::SpecialForce ::ATeam ::Enumerable ::nx::Object
+
+% s1 foo
+Donald-6 Micky-5 Daniel-6 Gustav-6
+

Let us look again on decorator style mixin classes. If we add a +per-class mixin to ATeam, the mixin class has highest precedence, +and decorates the instances of ATeam as well the instances of its +specializations (like e.g. SpecialForce).

+
+
+
% ATeam mixin Mix
+::Mix
+
+% s1 info precedence
+::Mix ::SpecialForce ::ATeam ::Enumerable ::nx::Object
+
+% s1 count
+alpha 4 omega
+

This example showed that NX/XOTcl dynamic class structure is able to +support Ruby-style mixins, and decorator style mixins at the same time.

+
+
+
+

+ + + Index: doc/example-scripts/ruby-mixins.tcl =================================================================== diff -u --- doc/example-scripts/ruby-mixins.tcl (revision 0) +++ doc/example-scripts/ruby-mixins.tcl (revision 88e54fa5bb23eb546abf7f676afa03aa697ac813) @@ -0,0 +1,214 @@ +# +# Design study to show differences between NX/XOTcl style mixin +# classes and Ruby's mixin modules. The example shows that the +# dynamic class structure of NX (and XOTcl) is able to support +# _Ruby-style mixins_ (called modules) and _decorator style mixins_ +# (named after the design pattern Decorator) at the same time. + +package req nx +package req nx::test +nx::Test parameter count 1 + +# +# One important difference between mixin classes in NX and Ruby's +# mixins is the precedence order. While in NX, mixins are decorators +# (the mixins have higher precedence than the intrinsic classes, +# therefore a mixin class can overload the methods of the current +# class), the mixins of Ruby have a lower precedence (they extend the +# _base behavior_; although Ruby's modules are not full classes, they +# are folded into the _intrinsic class hierarchy_). +# +# We define the method +module+, which behaves somewhat similar to +# Ruby's +module+ command and adds the provided class to the +# precedence order after the current class. The easiest way to achieve +# this is via multiple inheritance (i.e. via the +superclass+ +# relationship). +# +nx::Class eval { + :protected method module {name:class} { + :configure -superclass [concat $name [:info superclass]] + } +} + +# +# For illustration of the behavior of +module+ we define a class +# +Enumerable+ somewhat inspired by the Ruby module with the same +# name. We define here just the methods +map+, +each+, +count+, and +# +count_if+. +# +nx::Class create Enumerable { + :property members:0..n + + # The method 'each' applies the provided block on every element of + # 'members' + :public method each {var block} { + foreach member ${:members} { + uplevel [list set $var $member] + uplevel $block + } + } + + # The method 'map' applies the provided block on every element of + # 'members' and returns a list, where every element is the mapped + # result of the source. + :public method map {var block} { + set result [list] + :each $var { + uplevel [list set $var [set $var]] + lappend result [uplevel $block] + } + return $result + } + + # The method 'count' returns the number of elements. + :public method count {} { + return [llength ${:members}] + } + + # The method 'count_if' returns the number of elements for which + # the provided expression is true. + :public method count_if {var expr} { + set result 0 + :each $var { + incr result [expr $expr] + } + return $result + } +} + +# Create a class Group, which is essentially a subclass of "Enumerable". +nx::Class create Group { + # + # Include the "module" Enumerable + # + :module Enumerable +} + +# Define now a group +g1+ with the three provided members. +? {Group create g1 -members {mini trix trax}} ::g1 + +# Since the definition of +Group+ includes the module +Enumerable+, +# this class is listed in the precedence order after the class +Group+: + +? {g1 info precedence} "::Group ::Enumerable ::nx::Object" + +# Certainly, we can call the methods of +Enumerable+ as usual: +? {g1 count} 3 + +? {g1 map x {list pre-$x-post}} "pre-mini-post pre-trix-post pre-trax-post" + +? {g1 count_if x {[string match tr*x $x] > 0}} 2 + +# +# To show the difference between a +module+ and a +decorator mixin+ we +# define a class named +Mix+ with the single method +count+, which +# wraps the result of the underlaying +count+ method between the +# +alpha+ and +omega+. + +nx::Class create Mix { + :public method count {} { + return [list alpha [next] omega] + } +} + +# When the mixin class is added to +g1+, it is added to the front of +# the precedence list. A decorator is able to modify the behavior of +# all of the methods of the class, where it is mixed into. + +? {g1 mixin Mix} "::Mix" + +? {g1 info precedence} "::Mix ::Group ::Enumerable ::nx::Object" + +? {g1 count} {alpha 3 omega} + +# For the time being, remove the mixin class again. +? {g1 mixin ""} "" +? {g1 info precedence} "::Group ::Enumerable ::nx::Object" + +# +# An important difference between NX/XOTcl style mixins (decorators) +# and Ruby style modules is that the decorator will have always a +# higher precedence than the intrinsic classes, while the +module+ is +# folded into the precedence path. +# + +# Define a class +ATeam+ that uses +Enumerable+ in the style of a Ruby +# module. The class might refine some of the provided methods. We +# refined the method +each+, which is used as well by the other +# methods. In general, by defining +each+ one can define very +# different kind of enumerators (for lists, databases, etc.). +# +# Since +Enumerable+ is a module, the definition of +each+ in the +# class +ATeam+ has a higher precedence than the definition in the +# class +Enumerable+. If +Enumerable+ would be a decorator style mixin +# class, it would not e possible to refine the definition in the class +# +ATeam+, but maybe via another mixin class. +# +nx::Class create ATeam { + # + # Include the "module" Enumerable + # + :module Enumerable + + # + # Overload "each" + # + :public method each {var block} { + foreach member ${:members} { + uplevel [list set $var $member-[string length $member]] + uplevel $block + } + } + + # + # Use "map", which uses the "each" method defined in this class. + # + :public method foo {} { + return [:map x {string totitle $x}] + } +} + +# +# Define now a team +t1+ with the three provided members. +# +? {ATeam create t1 -members {arthur bill chuck}} ::t1 + +# As above, the precedence of +ATeam+ is higher than the precedence of +# +Enumerable+. Therefore, the object +t1+ uses the method +each+ specialized in +# class +ATeam+: +? {t1 info precedence} "::ATeam ::Enumerable ::nx::Object" + +? {t1 foo} "Arthur-6 Bill-4 Chuck-5" + +# +# The class +ATeam+ can be specialized further by a class +SpecialForce+: +# +nx::Class create SpecialForce -superclass ATeam { + # ... +} + +# Define a special force +s1+ with the four provided members. + +? {SpecialForce create s1 -members {Donald Micky Daniel Gustav}} ::s1 + +# As above, the precedence of +Enumerable+ is lower then the +# precedence of +ATeam+ and +Enumerable+. Therefore +ATeam+ can refine +# the behavior of +Enumerable+, the class +SpecialForce+ can refine +# the behavior of +ATeam+. +? {s1 info precedence} "::SpecialForce ::ATeam ::Enumerable ::nx::Object" + +? {s1 foo} "Donald-6 Micky-5 Daniel-6 Gustav-6" + +# Let us look again on decorator style mixin classes. If we add a +# per-class mixin to +ATeam+, the mixin class has highest precedence, +# and decorates the instances of +ATeam+ as well the instances of its +# specializations (like e.g. +SpecialForce+). + +? {ATeam mixin Mix} "::Mix" + +? {s1 info precedence} "::Mix ::SpecialForce ::ATeam ::Enumerable ::nx::Object" + +? {s1 count} {alpha 4 omega} + +# This example showed that NX/XOTcl dynamic class structure is able to +# support Ruby-style mixins, and decorator style mixins at the same time.