8
$\begingroup$

Intro

I was watching "UnRisk-Q Instrument Builder" talk (https://www.youtube.com/watch?v=Xx3goIBpOdE) and those guys use builder pattern to build complex objects like this:

bondBuilder = BondBuilder[]
bond = bondBuilder @ Nominale[1.] @ StartDate[{2010,10,10}] @ Build[]

and later bond is immutable and you can invoke methods on it: bond @ SomeMethod[]

The reason it works is them using JLink and @ behaves weirdly there and associates to the left.

Question/Problem

How to implement a builder pattern that:

  • looks good & concise in notebook and .m files? You could use custom operator instead of @ or some ~infix~ notation, but that looks heavy.
  • allows partial builder reuse like:

    octBuilder = BondBuilder[] @ StartData[{2010, 10, 10}]
    bond1 = octBuilder @ Nominale[1.] @ Build[]
    bond2 = octBuilder @ Nominale[2.] @ Build[]
    
  • has enough flexibility to add error checking to Build[]

I obviously do not want to mess a lot with @ and try to redefine its associativity. I do not want to copy the look but the idea.

$\endgroup$
1

1 Answer 1

5
$\begingroup$

While this won't win the beauty contest, something like this can be done in a reasonably simple way using the Object-Oriented Mathematica extension described here.

To keep this post self-contained, I will include the code to install the actual version of the OO extension I will use here (be aware that it will also install the ProjectInstaller package of mine):

Import["https://raw.github.com/lshifr/ProjectInstaller/master/BootstrapInstall.m"]
Needs["ProjectInstaller`"]
ProjectInstall[URL["https://gist.github.com/4266126/download"]]

This has to be done only once. Alternatively, you can go directly to the gist using the above URL, copy OO.m from there and place into $UserBaseDirectory / Applications, or any other place where Mathematica can find it.

Now, it can be loaded as

Needs["OO`"]

Here is the code for the toy classes Bond and BondBuilder:

DeclareType[Bond][
  getNominale[] := $content[[1]]
  ,
  getStartDate[] := $content[[2]]
]

and

DeclareType[BondBuilder][
  Nominale[x_?NumericQ] :=
    Module[{copy  = $content},
      copy[[1]] = x;
      $content = copy;
      $self
    ]
  ,
  StartDate[date : {__Integer}] :=
    Module[{copy  = $content},
      copy[[2]] = date;
      $content = copy;
      $self
  ]
  ,
  Build[] := With[{data = $content}, Bond[data]]
]

admittedly, the syntax for setting class fields is currently rather ugly. The first field holds the nominal, and the second holds the date. The system variable $content is assumed to keep a list of the two.

As you can see, I have incorporated the argument checks, just to show how this is done - pretty much in the same way as usually done with patterns in Mathematica.

Here is how we create an empty builder:

builder = BondBuilder[{Null, Null}]

(* « BondBuilder[]» *)

Since I did not bother with left associativity in OO.m, the chained method application will look ugly, since we would need parentheses. Alternatively, one could use Fold and Compose, which is what I will do here:

bond = Fold[Compose, builder, {Nominale[1.], StartDate[{2010, 10, 10}], Build[]}]

(* « Bond[]»  *)

This is a complete bond, which we can query:

bond@getNominale[]

(* 1. *)

bond@getStartDate[]

(* {2010, 10, 10} *)

We can also construct a partial builder:

octBuilder = BondBuilder[{Null, Null}]@StartDate[{2010, 10, 10}];

so that

bond1 = (octBuilder@Nominale[1.])@Build[];
bond2 = (octBuilder@Nominale[2.])@Build[];

and now

bond1@getNominale[]

(* 1. *)

bond2@getNominale[]

(* 2. *)

bond1@getStartDate[]

(* {2010, 10, 10} *)

bond2@getStartDate[]

(* {2010, 10, 10} *)

EDIT

As a light-weight alternative (which uses similar implementation ideas though), here is a builder which won't depend on any framework:

ClearAll[BondBuilder,Bond];
BondBuilder[] :=
  Module[{builder, props = {}},
    builder@property_[value_] :=
      (
        builder /: property[builder] = value;
        If[! MemberQ[props, property], AppendTo[props, property]];
        builder
      );
    builder@Build[] := Bond[#[#[builder]] & /@ props];
    builder
  ];

Here is how it can be used:

builder = BondBuilder[]

(* builder$16211 *)

builder@Nominale[1.]

(* builder$16211 *)

builder@StartDate[{2010, 10, 10}]

(* builder$16211 *)

builder@Build[]

(* Bond[{Nominale[1.], StartDate[{2010, 10, 10}]}] *)

you get an immutable expression representing your bond. You should also be able to use the partial builder in the way you outlined. The associativity problem for chained method application remains, however, and you will either have to use several statements, or use parentheses, or something like the Nest - Compose construct.

$\endgroup$
2
  • $\begingroup$ This is really nice, but unfortunately it looks too heavy weight. Both literally (too many symbols to achieve this) and conceptually (I would like to implement something like builders without OO framework). Thank you though. $\endgroup$
    – mikea
    Commented Sep 6, 2013 at 21:24
  • 1
    $\begingroup$ @mikea I have added a simpler alternative - see my edit. The code is bare-bones, just to illustrate the idea, and one might want to polish it. $\endgroup$ Commented Sep 6, 2013 at 21:46

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.