Object-oriented design patterns with Wolfram Language

Anton Antonov

Accendo Data, LLC

Join the Conversation #WolframTechConf

Abstract

In this talk we are going to discuss a particular style of Wolfram Language programming that allows the application of the Object-oriented paradigm (to a point). The approach does not require the use of preliminary implementations (packages or extra code). Using the Object-Oriented Paradigm (OOP) is achieved by following a specific programming style and conventions with the standard functional programming constructs of Wolfram Language.

Design Patterns are obtained from analysis of successful software products. One can say that the OOP came to maturity with Design Patterns. Design Patterns help overcome limitations of programming languages, give higher level abstractions for program design, and provide design transformation guidance. Because of extensive documentation and examples, Design Patterns help knowledge transfer and communication between developers.

Using Design Patterns we apply the OOP "culture" into Wolfram Language. The proposed Wolfram Language approach of implementing Design Patterns with OOP origin is going to be demonstrated with the programming of the patterns named Template Method, Strategy, Composite, Decorator, Interpreter, Observer, and others.

We are going to draw parallels with Object-oriented approaches in other functional programming languages, like CLOS and R. Relations and analogies with some mathematical theories are going to be pointed out.

Concrete real life examples are going to be briefly discussed. (Dynamic, NDSolve, NIntegrate, an air pollution model, a recommendation system.)

Talk plan

Why use pattern languages?

Knowledge transfer

Ability to start and finish a project (in time or at all)

Concrete on the particular challenges

Philosophical origins of pattern languages

Need for pattern solutions

How do we come up with pattern languages

Why use the object-oriented paradigm?

Functional programming is great and hence sufficient!

We can respond to that.

How do apply OOP Design Patterns in Mathematica?

Principle implementations

Inheritance

Template method

Strategy

Decorator

Brief concrete examples throughout

Concrete examples

Plotting GitHub commit data

NIntegrate algorithms tracing

Questions for the audience

How many program in Mathematica (not using it just as a calculator)?

How many are familiar with Object-Oriented Programming?

How many have appplied or heard of Desing Patterns?

How many know (at least somewhat) LISP and Scheme?

Patterns Venn Diagram

pVennGr=Import["/Users/antonov/MathFiles/Presentations/WTC-2015/Design patterns in Mathematica/VennDiagramForPatternsInGeneral.png"];
Show[pVennGr,ImageSize->1000]
AutoCollapse[]

Concrete examples first

GitHub data plots

Started with this Mathematica StackExchange discussion: “Import and Plot Git Commit History”.

Get GitHub data of several projects and plot it (using the Composite design pattern).

Import["https://raw.githubusercontent.com/antononcube/MathematicaForPrediction/master/Misc/GitHubDataObjects.m"]

ghs=Map[MakeGitHubDataObject["hadley",#]&,{"plyr","dplyr","adv-r","ggplot2","devtools","rvest"}];
ghComposite=GitHubDataComposite[Unique[],ghs]

ghComposite["Plot3D"["ProjectPlanes"->True,"GlobalTimeOrder"->False,BoxRatios->{1,2,2}]]

NIntegrate

Design patterns can be seen algorithms Initialization tracing

Several questions/discussions in Mathematica StackExchange and Wolfram Community have asked for finding the automatic selection of algorithms by NIntegrate for given integrands.

Here is code that answers these questions and demonstrates the NIntegrate usage of the patterns Strategy, Composite, and Decorator. (Well and Interpreter.) NIntegrate algorithms initialization tracing.nb

symbNames=Names["NIntegrate`*"];
symbNames=Append[Pick[symbNames,StringMatchQ[symbNames,(__~~"Rule")|(__~~"Global"|"Local"|"MonteCarlo"|"Principal"|"Levin"|"Osc"~~___)]],"NIntegrate`AutomaticStrategy"];
symbs=ToExpression[#]&/@symbNames;
uvs=UpValues/@symbs;
Unprotect/@symbs;
uvsNew=MapThread[
With[{s=#2},
UpValues[s]=
ReplaceAll[#1,
HoldPattern[Block[vars_,CompoundExpression[b___]]]:>
Block[{res=Block[vars,CompoundExpression[b]]},Print["UpValue call for: ",Style[s,Blue],Style[" ::\n",Blue],res];res]
]
]&,{uvs,symbs,symbNames}];

NIntegrate[1/(Sqrt[x]),{x,0,1},Method->{"EvenOddSubdivision"}]

NIntegrate[1/(Sqrt[x]),{x,0,1},Method->{"SymbolicPiecewiseSubdivision"}]

Quit

Application to MatrixFunction calculations

The NIntegrate framework in powerful and general. Here is an example of using NIntegrate to integrate matrix function formulas.

Consider the following definition: MatrixFunction[f, A] evaluates, for a given square matrix A and a given scalar-valued function f(x), the contour integral

f(A)=(1/(2π i))(∮_γ) f(z)(((z I -A))^(-1))dz

where γ is a closed contour enclosing the eigenvalues of A, and where f(z) is analytic within.

Here are definitions of integration strategies for NIntegrate that use that formula to compute MatrixFunction[f,A] and MatrixFunction[f,A].

Why use pattern languages?

To design bravely

Leveraging the existing documented experience using different solutions, and

knowing their consequences.

As Rene Descardes says:

“I might be not smarter than the others but I have a method.”

(paraphrased)

We can be ignorant in a very smart way. This is similar to putting a relational database in a third and higher normal forms.

To get everyone on board

Useful for hands on experience with prototypes,

As entry points to proposed solutions.

For knowledge transfer

Because patterns are well documented it is easier to ensure knowledge transfer of projects to new maintainers and developers.

What is in contrast with design patterns?

Anti-patterns

There are books and articles with/about software and organizational Anti-patters.

Big balls of mud

See the interesting and insightful “Big Ball of Mud” by Brian Foote and Joseph Yoder. (Related Wikipedia entry.)

“A Big Ball of Mud is a haphazardly structured, sprawling, sloppy, duct-tape-and-baling-wire, spaghetti-code jungle. [...]”

Why object-oriented programming?

Question

Why use the Object-Oriented Programming (OOP) paradigm? The Functional Programming (FP) paradigm is pretty powerful and sufficient.

Answers

General

Agreed. (Of course!)

But people tend to thing with objects.

OOP models the world by encapsulating behavior into objects. FP is more about the dichotomy of data and algorithms over data.

Examples would be nice...

It is better to program with Design Patterns in the following “real world” scenario:

prototype a solution in Mathematica, and

then program it in a production level language (Java, C++).

The ideal of OOP is complete reuse. The ideal of FP is refining until you get a language that fits the problem.

Mathematical analogies to justify OOP (next)

Why object-oriented programming? (cont.)

Question

Why use the Object-Oriented Programming (OOP) paradigm? The Functional Programming (FP) paradigm is pretty powerful and sufficient.

Answers

Mathematical analogies to justify OOP

Signal approximation

FP: Standard Fourier analysis.

OOP: Wavelets are like approximation done locally by each "small wave".

Integration

FP: Riemann integral

OOP: Lebesgue integral

Algebras

FP: Free algebra

OOP: We restrict the free algebra to with polynomial identifies and we get (say) the ring of reals.

Why object-oriented programming design patterns?

In summary so far:

Apply the OOP culture into Mathematica

Design and prototype in Mathematica, implement in C/C++/Java.

It helps the communication between team members and people who inherit the code.

This is more of a different approach to programming with Mathematica that helps transition from prototypes in Mathematica to production code in Java/C++.

Using Design Patterns we apply the OOP "culture" into the functional programming language of Mathematica.

We are not trying to use or emulate OOP at object level in order to leverage OOP know-how and obtain OOP designs. We apply Design patterns instead.

We wanna concentrate on what we program, not how.

A standard claim by any programming methodology.

Briefly what are software design patterns

Definitions

The software architecture definition

“A design pattern describes the problem, a solution to the problem consisting of a general arrangement of objects and classes, when to apply the solution, and the consequences of applying the solution.”

From Alexander’s book “The Timeless Way of Building”

Each pattern is a three-part rule, which expresses a relation between a certain context, a problem, and a solution.

As an element in the world, each pattern is a relationship between a certain context, a certain system of forces which occurs repeatedly in that context, and a certain spatial configuration which allows these forces to resolve themselves.

As an element of language, a pattern is an instruction, which shows how this spatial configuration can be used, over and over again, to resolve the given system of forces, wherever the context makes it relevant.

The pattern is, in short, at the same time a thing, which happens in the world, and the rule which tells us how to create that thing, and when we must create it. It is both a process and a thing; both a description of a thing which is alive, and a description of the process which will generate that thing.

He also speaks a lot about the quality without a name.

The broad classification in OOP

Creational

Structural

Behavioral

Transformation flow

Design patterns history

In 1975 the architect Christopher Alexander et al., [7], introduced a pattern language for architectural solutions. Alexander’s book “The Timeless Way of Building”, [6], is the more fundamental one -- some sort of philosophical prequel of [7] and [8].

The computer scientists Kent Beck and Ward Cunningham started applying these ideas in software architecture in 1987 and presented results in the conference Object-Oriented Programming, Systems, Languages, and Applications (OOPSLA) -- see [9].

In 1995 the book by Gamma et al. “Design Patterns: Elements of Reusable Object-Oriented Software” provided a catalog of well researched and documented design patterns and that brought design patterns and pattern languages great popularity and acceptance. In 2005 ACM SIGPLAN awarded that year’s Programming Language Achievements Award to the authors. The authors are of often references ad the “Gang of Four” (GoF).

patternsHistory=Dataset[Association/@{
{"Persons"->{"Christopher Alexander","Schlomo Angel","Sara Ishikawa", "Murray Silverstein","Danny Angel"},"Date"->"1975","EventType"->"book release","EventName"->"'The Oregon experiment'\nbook by Alexander et al."},{"Persons"->{"Christopher Alexander","Sara Ishikawa", "Murray Silverstein"},"Date"->"1977","EventType"->"book release","EventName"->"'A Pattern Language - Towns, Buildings, Construction'\nbook by Alexander et al.","note"->"generative grammar"},
{"Persons"->{"Christopher Alexander"},"Date"->"1979","EventType"->"book release","EventName"->"'The Timeless Way of Building'\nbook by Alexander","note"->"concepts, the quality without a name"},
{"Persons"->{"Kent Beck","Ward Cunningham"},"Date"->"1987","EventType"->"articles","EventName"->"OOPSLA articles by Beck and Cunningham\nin Orlando, FL"},{"Persons"->{"GoF"},"Date"->"1994","EventType"->"book release","EventName"->"'Design Patterns: Elements of Reusable Object-Oriented software'\nbook by GoF"},
{"Persons"->{"many"},"Date"->"1994","EventType"->"conference started","EventName"->"'Pattern Languages of Programming' conference started\nAllerton Park near Monticello, IL"},
{"Persons"->"ACM SIGPLAN","Date"->"2005","EventType"->"award","EventName"->"SIGPLAN Programming Language Achivements Award to GoF"}}];
tlData=Transpose[{Normal[patternsHistory[All, "EventName"]] , Map[DateObject[DateList[{#, {"Year"}}]] &, Normal[ patternsHistory[All, "Date"]]]}];
tlData=Association[Rule@@@tlData];
TimelinePlot[tlData]
AutoCollapse[]

.

General on the proposed approach

Approach

It is a style not a package.

We have to address the use of commonality and variability between function codes in programming.

In OOP we do that by inheritance and modification.

Instead of considering objects as data capsules we consider objects as hubs of functionalities.

Different approaches

In Wolfram Language we do that by currying (second order functions), signature overloading (pattern matching, etc.).

There are other approaches to of using software (design) patterns in functional languages. Here we are we show how to use OOP Design Patterns into a functional language.

Preliminary concepts

Here is a list of notions I assume the audience is familiar with:

Closures

Dynamic and lexical scoping

Inheritance & Delegation

DownValues, UpValues, and Tags

Closure state

A standard trick to use internal states.

Module[{counter=0}, NewID[]:=counter++;ResetID[]:=counter=0;]

Table[NewID[],{5}]
ResetID[]

?counter*

NewID[]

ResetID[]

?NewID

?ResetID

Inheritance

Consider the following example. We have three classes C0, C1, and C2. The class C1 inherits C0, C2 inherits C1. We write this as C0←C1←C2 (Using UML?).

Clear[C0,C1,C2]

C0[d_]["Data"[]]:=d;
C0[{a_,b_,c_,___}]["f1"[]]:=a;
C0[{a_,b_,c_,___}]["f2"[]]:=a+b;
C0[{a_,b_,c_,___}]["f3"[]]:=a+b+c;

C1[d_][s_]:=C0[d][s]
C1[{a_,b_,c_}]["f1"[]]:=a*b;

C2[d_][s_]:=C1[d][s]
C2[{a_,b_,c_}]["f2"[]]:=a^b;

obj=C2[{a,b,c}];
(* Invokes C1's definition *)
obj["f1"[]]
(* Invokes C2's definition *) 
obj["f2"[]]
(* Invokes C0's definition *) 
obj["f3"[]]

Let us look into more detail of what is hap penning. We have made the “object”

C2[{123,z2, z3}]

There are sub-value rules for C2:

SubValues[C2]

Here is one more example:

C0[{1343,1,3}]["f1"[]]

C2[{1343,1,3}]["f1"[]]

Static inheritance

Achieved by direct manipulation of sub-values.

Clear[C0,C1,C2]

C0[d_]["Data"[]]:=d;
C0[{a_,b_,c_,___}]["f1"[]]:=a;
C0[{a_,b_,c_,___}]["f2"[]]:=a+b;
C0[{a_,b_,c_,___}]["f3"[]]:=a+b+c;

(* static inheritance C0->C1*)
SubValues[C1]=ReplaceRepeated[SubValues[C0],C0->C1];

(*pos is position of the overridden method *)
pos=Position[First/@SubValues[C1],"f1"[___],∞][[1,1]];
SubValues[C1]=Drop[SubValues[C1],{pos}];
C1[{a_,b_,c_,___}]["f1"[]]:=a*b;

(* static inheritance C1->C2 *)
SubValues[C2]=ReplaceRepeated[SubValues[C1], C1-> C2];

(*pos is position of the overridden method *)
pos=Position[First/@SubValues[C2],"f2"[___],∞][[1,1]];
SubValues[C2]=Drop[SubValues[C2],{pos}];
C2[{a_,b_,c_,___}]["f2"[]]:=a^b;

obj=C2[{a,b,c}];
obj["f1"[]] (* C1 defintinion *)
obj["f2"[]] (* C2 defintinion *)
obj["f3"[]] (* C0 defintinion *)

It is instructional to see the definitions of the symbols defined above.

?C0

Strategy

Generally Strategy’s structure is not needed in Mathematica since function signatures are handled with a very powerful mechanism -- its application is trivial. Conceptually Strategy is applied all the time, e.g. second order functions or writing functions with arguments being other functions.

UML

tmUML=Import["/Users/antonov/MathFiles/Presentations/WTC-2015/Design patterns in Mathematica/UML Diagrams/Strategy-UML-Diagram.png"];
Show[tmUML,ImageSize->700]
AutoCollapse[]

Uses in Mathematica

NIntegrate‘s main algorithms -- the integration strategies, like, “GlobalAdaptive” and “LocalAdaptive” -- play the role of Context in Strategy. The integration rules to play roles of the ConcreteStrategy classes.

Concrete example

The plotting functionality of the main GitHub data class in [5] GitHubData given by the method “Plot” is changed using through the Strategy design pattern. The data member “PlotFunction” can be set to one of the functions GHDDateListPlot, GHDBarPlot, or GHDGraphics3D. In this way different plots are derived for the representation of the ingested GitHub data.

Template Method

Intent

Here is the “intent” of Template Method, [GoF94]:

Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm’s structure.

UML

templateMethpdUML=Import["/Users/antonov/MathFiles/Presentations/WTC-2015/Design patterns in Mathematica/UML Diagrams/Template-Method-UML-Diagram.pdf"];
Show[templateMethpdUML,ImageSize->700]
AutoCollapse[]

Template Method (cont.)

Implementation

Clear[AbstractClass,ConcreteOne,ConcreteTwo];

CLASSHEAD=AbstractClass;
AbstractClass[d_]["Data"[]]:=d;
AbstractClass[d_]["PrimitiveOperation1"[]]:=d[[1]];
AbstractClass[d_]["PrimitiveOperation2"[]]:=d[[2]];
AbstractClass[d_]["TemplateMethod"[]]:=CLASSHEAD[d]["PrimitiveOperation1"[]]+CLASSHEAD[d]["PrimitiveOperation2"[]]

ConcreteOne[d_][s_]:=Block[{CLASSHEAD=ConcreteOne},AbstractClass[d][s]]
ConcreteOne[d_]["PrimitiveOperation1"[]]:=d[[1]];
ConcreteOne[d_]["PrimitiveOperation2"[]]:=d[[1]]*d[[2]];

ConcreteTwo[d_][s_]:=Block[{CLASSHEAD=ConcreteTwo},AbstractClass[d][s]]
ConcreteTwo[d_]["PrimitiveOperation1"[]]:=d[[1]];
ConcreteTwo[d_]["PrimitiveOperation2"[]]:=d[[3]]^d[[2]];

t=AbstractClass[{a,b,c}]
t["Data"[]]
t["TemplateMethod"[]]

c1=ConcreteOne[{a,b,c}]
c1["Data"[]]
c1["TemplateMethod"[]]

c2=ConcreteTwo[{a,b,c}]
c2["Data"[]]
c2["TemplateMethod"[]]

It is instructive to see the trace of the invocations.

This call uses a method defined in the abstract class:

c1["Data"[]]//Trace//ColumnForm

This trace shows the call for the abstract algorithm (template method) for a concrete class object that invokes the primitive operation defined in that concrete class.

c1["TemplateMethod"[]]//Trace

Composite

Intent

“Compose objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly.” [GoF94]

Typical example is desktop icons selection dragging.

UML

compositeGr=Import["https://upload.wikimedia.org/wikipedia/commons/thumb/5/5a/Composite_UML_class_diagram_(fixed).svg/960px-Composite_UML_class_diagram_(fixed).svg.png"];
Show[compositeGr,ImageSize->700]

Composite (cont.)

Implementation

Clear[Component,LeafOne,LeafTwo,Composite];

CLASSHEAD=Component;
Component[d_,sm_]["Submethods"[]]:=sm;
Component[{a_,b_,c_},sm_]["Add"[method_]]:=CLASSHEAD[{a,b,c},Append[sm,method]];
Component[{a_,b_,c_},sm_]["Remove"[method_]]:=CLASSHEAD[{a,b,c},Select[sm,#1=!=method&]];Component[{a_,b_,c_},sm_]["GetChild"[i_Integer]]:=sm[[i]];
Component[d_,sm_]["Operation"[]]:=Print["error"]

LeafOne[d_,sm_][s_]:=Block[{CLASSHEAD=LeafOne},Component[d,sm][s]]
LeafOne[{a_,b_,c_},sm_]["Operation"[]]:=a;

LeafTwo[d_,sm_][s_]:=Block[{CLASSHEAD=LeafTwo},Component[d,sm][s]]
LeafTwo[{a_,b_,c_},sm_]["Operation"[]]:=a^b^c;

Composite[d_,sm_][s_]:=Block[{CLASSHEAD=Composite},Component[d,sm][s]]
Composite[{a_,b_,c_},sm_]["Operation"[]]:=Map[#1["Operation"[]]&,sm];

Component[{a,b,c},{{1},{2}}]["Submethods"[]]
Component[{a,b,c},{{1},{2}}]["Operation"[]]

leaf1=LeafOne[{a,b,c},{{3},{4}}]
leaf1["Submethods"[]]
leaf1["Operation"[]]

leaf2=LeafTwo[{a,b,c},{{5},{6}}]
leaf2["Submethods"[]]
leaf2["Operation"[]]

comp=Composite[{a,b,c},{}]["Add"[leaf1]]
comp=comp["Add"[leaf2]]
comp["Operation"[]]

comp1=Composite[{a,b,c},{}]["Add"[leaf1]]
comp1=comp1["Add"[comp]]
comp1["Operation"[]]

Decorator

Intent

"Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to sub-classing for extending functionality." [GoF94]

Special case of Composite. Typical example -- scroll-bars.

UML

decoratorGr=Import["https://upload.wikimedia.org/wikipedia/commons/thumb/e/e9/Decorator_UML_class_diagram.svg/960px-Decorator_UML_class_diagram.svg.png"];
Show[decoratorGr,ImageSize->700]

Decorator (cont.)

Implementation

Clear[Component,ConcreteComponent,ConcreateDecoratorA,ConcreateDecoratorA,Decorator];

COMPONENTHEAD=Component;
Component[d_]["Operation"[]]:=Print["error"]

Decorator[d_,component_][s_]:=Block[{COMPONENTHEAD=Decorator},Component[d][s]]
Decorator[d_,component_]["Operation"[]]:=(Print["Decorator:",Head[component]];component["Operation"[]]);

ConcreteComponent[d_][s_]:=Block[{COMPONENTHEAD=ConcreteComponent},Component[d][s]]
ConcreteComponent[d_]["Operation"[]]:=d;
ConcreteComponent[d_List]["Operation"[]]:=Plus@@d;

ConcreateDecoratorA[d_,component_][s_]:=Block[{COMPONENTHEAD=ConcreateDecoratorA},Decorator[d,component][s]]
ConcreateDecoratorA[d_,component_]["Operation"[]]:=Decorator[d,component]["Operation"[]]^d[[1]];

ConcreateDecoratorB[d_,component_][s_]:=Block[{COMPONENTHEAD=ConcreateDecoratorA},Decorator[d,component][s]]
ConcreateDecoratorB[d_,component_]["Operation"[]]:=1/Decorator[d,component]["Operation"[]];

Experiments

cc=ConcreteComponent[{a,b,c}];
cc["Operation"[]]

cdA=ConcreateDecoratorA[{h},cc];
cdA["Operation"[]]

cdB=ConcreateDecoratorB[{},cc];
cdB["Operation"[]]

cdAB=ConcreateDecoratorB[cdA[[1]],cdA];
cdAB["Operation"[]]

cdAABA=ConcreateDecoratorA[{f},ConcreateDecoratorA[{g},ConcreateDecoratorB[{},ConcreateDecoratorA[{h},cc]]]];
cdAABA["Operation"[]]

Observer

Also known as Model-View-Controller (MVC).

Used heavily in Mathematica -- Dynamic, Manipulate, etc.

Interpreter

Intent

"Given a language, define a representation for its grammar along with an interpreter that uses the representation to interpret sentences in the language." [GoF94]

UML

interpreterUML=Import["/Users/antonov/MathFiles/Presentations/WTC-2015/Design patterns in Mathematica/UML Diagrams/Interpreter-UML-Diagram.png"];
Show[interpreterUML,ImageSize->700]
AutoCollapse[]

Interpreter (cont.)

BNF

BooleanExp::=VariableExp|Constant|OrExp|AndExp|NotExp|'(' BooleanExp')'
AndExp::=BooleanExp'and' BooleanExp
OrExp::=BooleanExp'or' BooleanExp
NotExp::='not' BooleanExp
Constant::='true'|'false'
VariableExp::='A'|'B'|...|'X'|'Y'|'Z'

Implementation

Clear[BooleanExp,VariableExp,ConstantExp,AndExp,OrExp,NotExp]

SELFHEAD=BooleanExp;
BooleanExp[d___]["Data"[]]:=d;
BooleanExp[d___]["Evaluate"[context_]]:=Null;
BooleanExp[d___]["Replace"[var_,epxr_]]:=Null;
BooleanExp[d___]["Copy"[]]:=Null;

VariableExp[d___][s_]:=Block[{SELFHEAD=VariableExp},BooleanExp[d][s]]
VariableExp[{var_String,rest___}]["Name"[]]:=var;VariableExp[{var_String,rest___}]["Evaluate"[context_]]:=context["Lookup"[var]];

ConstantExp[d___][s_]:=Block[{SELFHEAD=ConstantExp},BooleanExp[d][s]]
ConstantExp[{val_,rest___}]["Evaluate"[context_]]:=If[val==="true",True,False];

AndExp[d___][s_]:=Block[{SELFHEAD=AndExp},BooleanExp[d][s]]
AndExp[{operand1_,operand2_,rest___}]["Evaluate"[context_]]:=operand1["Evaluate"[context]]&&operand2["Evaluate"[context]];

OrExp[d___][s_]:=Block[{SELFHEAD=OrExp},BooleanExp[d][s]]
OrExp[{operand1_,operand2_,rest___}]["Evaluate"[context_]]:=operand1["Evaluate"[context]]||operand2["Evaluate"[context]];

NotExp[d___][s_]:=Block[{SELFHEAD=NotExp},BooleanExp[d][s]]
NotExp[{operand1_,rest___}]["Evaluate"[context_]]:=Not[operand1["Evaluate"[context]]];

Clear[IContext]

IContext[d_]["Assign"[var_String,val_]]:=(IContext[d][var[]]=val);
IContext[d_]["Assign"[var_VariableExp,val_]]:=Module[{name=var["Name"[]]},IContext[d][name[]]=val];IContext[d_]["Lookup"[var_String]]:=IContext[d][var[]]

Experiments

(true and x) or (y and (not x))

tr=ConstantExp[{"true"}];
fl=ConstantExp[{"false"}];
x=VariableExp[{"X"}];
y=VariableExp[{"Y"}];
orexp=OrExp[{AndExp[{tr,x}],AndExp[{y,NotExp[{x}]}]}];

Clear[cont]
cont=IContext["first"];
cont["Assign"[x,False]]
cont["Assign"[y,False]]

orexp["Evaluate"[cont]]

texp=OrExp[{y,NotExp[{x}]}];

texp["Evaluate"[cont]]

Interpreter (cont. 2)

General comments

The pattern requires a certain Domain Specific Language (DSL) design knowledge.

NIntegrate options DSL was developed using Interpreter.

Applicability [GoF94]

Use the Interpreter pattern when there is a language to interpret, and you can represent statements in the language as abstract syntax trees. The Interpreter pattern works best when
	•	the grammar is simple. For complex grammars, the class hierarchy for the grammar becomes large and unmanageable. Tools such as parser generators are a better alternative in such cases. They can interpret expressions without building abstract syntax trees, which can save space and possibly time.	
	•	efficiency is not a critical concern. The most efficient interpreters are usually not implemented by interpreting parse trees directly but by first translating them into another form. For example, regular expressions are often transformed into state machines. But even then, the translator can be implemented by the Interpreter pattern, so the pattern is still applicable.

Extension

I am a big fan of the Interpreter pattern. I developed a package FunctionalParsers.m that addresses its limitations -- it makes it easy to create and maintain complex grammars by automatically generating parsers from BNF forms.

By the way, that package uses a very celebrated (lauded) design pattern known “Monad programming” supported in Haskell, F#. (Can be applied in any functional language.)

For example, see these blog posts: "Natural language processing with functional parsers", "Simple time conversational engine" and this Mathematica.StackExchange discussion, “CSS Selectors for Symbolic XML”.

This design pattern can be extended beyond the programming. I combine it with applications of morphological analysis to derive algorithmic solutions for complicated modeling tasks that include multiple types of components and interactions or have nebulous formulations.

On parallelism

Parallel functionalities in Mathematica are obtained much more naturally with the standard constructs.
  - More details.
  - Reference

Examples

Air pollution demo

Strategy, Template Method, Decorator.

Sparse Matrix Recommendation system

Strategy, Template method,

Parallel Heap

Inheritance

NIntegrate

Uses Template Method, Strategy, Interpreter, Decorator, Chain of responsibility, Abstract Factory.

NDSolve

Uses Template Method, Strategy.

Dynamic, Manipulate

Observer.

How it is done in other functional languages?

CLOS

R

R has two object systems: S3 and S4.

The presented approach is kind of between  S4 (S3 is more of a convention than an OOP system). S4 essentially implements the CLOS OOP system.

References

[Gof94] Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides, Design Patterns: Elements of Reusable Object-Oriented Software, Addison-Wesley, 1994.

[MSE-GH] Mathematica StackExchange discussion, “Import and Plot Git Commit History”. URL: http://mathematica.stackexchange.com/questions/94706/import-and-plot-git-commit-history .

[GHP15] Anton Antonov, GitHubPlots, Mathematica package, MathematicaForPrediction at GitHub, 2015. URL: https://github.com/antononcube/MathematicaForPrediction/blob/master/Misc/GitHubPlots.m .

[GHO15] Anton Antonov, GitHubObjects, Mathematica package, MathematicaForPrediction at GitHub, 2015. URL: https://github.com/antononcube/MathematicaForPrediction/blob/master/Misc/GitHubDataObjects.m .

Conclusion and summary

Use design patterns in Mathematica !