0

I am trying to follow the builder design pattern using modules in Reason. I have the following type:

type userBuilderType = {
  mutable name: string,
};

As well as signature type:

module type UserBuilderType = {
  let name: string;
};

I am passing the UserBuilderType signature type as a functor to the BuilderPattern:

module BuilderPattern = fun(Builder: UserBuilderType) => {
  let builder = {
    name: "",
  };

  let setName = builder.name = Builder.name;
  let getName () => builder.name;
};

I am then passing the appropriate value as a module doing the following:

module SetOfMixedPairs = BuilderPattern({
  let name = "asd";
});

However, in order for this builder design pattern, to truly be a builder design pattern, the signature type will need to be optional. I am struggling as how to do so. If I were for instance, to edit the signature type to be empty:

module type UserBuilderType = {};

The compiler will complain: Unbound value Builder.name. Any suggestions as to how to make the signature type optional, are more than welcome. My thanks as always.

Full code can be seen here.

Charlie-Greenman
  • 1,469
  • 1
  • 16
  • 36
  • What do you mean by "the signature type will need to be optional"? Your example with an empty signature doesn't make any sense. How do you actually want to use this, and what would you expect to come out of it? Also, you seem to be confusing functions taking a single unit argument with just an expression again on `setName`. – glennsl Oct 30 '17 at 06:14
  • By optional signature type, I mean that I should not have to apply signature type, and still be able to use passed in value to SetOfMixedPairs as a setter/getter. If I am indeed confusing functions with single unit arguments, my apologies. Perhaps that is where I am in err once again. My thanks as always. – Charlie-Greenman Oct 30 '17 at 09:46
  • 1
    I don't think the function confusion is the issue here, but it would certainly be easier to help if the code you post is actually valid and presents the problem clearly. Anyway, wouldn't just exposing a `module DefaultBuilderPattern = BuilderPattern({ let name = "default"; })` work? – glennsl Oct 30 '17 at 10:08
  • @glennsl I will most definitely try momentarily. In addition, I am extremely appreciative of all the help and by all means whatever I can do to make myself as clear as possible. Any constructive criticism as how I can make the above clearer so I can do the same now and later on, would be greatly appreciated. Thank you! – Charlie-Greenman Oct 30 '17 at 11:00
  • I'm trying to understand what are you trying to implement. Are you talking about this pattern: https://en.wikipedia.org/wiki/Builder_pattern ? If yes, then, is the `userBuilderType` represents the type of values that are build by a concrete visitor, and the `name` is just a component of a compound that you're trying to build? – ivg Oct 30 '17 at 12:34
  • @ivg that is correct. – Charlie-Greenman Oct 30 '17 at 13:33
  • This question has been down voted. If you would be kind enough to mention ways I can improve this question, by all means, feel free to do so. Thank you. – Charlie-Greenman Oct 31 '17 at 14:52
  • 1
    probably some reason haters :) – ivg Nov 02 '17 at 18:06

1 Answers1

3

First of all, usually you can't implement a design pattern using some mechanism of a language, as design patterns are not expressible directly in the language type system and syntax. Design patterns describe a particular methodology for solving recurring problems in software development. As soon, as a language provides a mechanism to express a design pattern directly, this is no longer considered a design pattern. Thus something that is a design pattern in one language, becomes a mechanism in another language. For example, a loop in assembly language is a design pattern, though in most modern languages it's a syntactic construct. A presence of design patterns usually indicates a lack of expressivity of a particular language or programming paradigm. Though, no matter how expressive your language, there always be abstractions, that can't be implemented directly using the language mechanisms.

You should also understand that GoF design patterns were written with the OOP paradigm in mind with peculiarities and limitations of OOP languages of that time. So they are not always applicable or even needed in OCaml/Reason or any other languages with parametric polymorphism and first-class functions.

In particular, the problem that the Builder pattern is trying to solve is an absence of first-class constructors and parametric polymorphism. Since we have both in Reason, we are usually not bothered with designing complex hierarchies of types. Another limitation of OOP is an absence of algebraic data types, that are ideal language mechanism for implementing complex compound data structures such as abstract syntax trees (expression parsing trees) and so on.

With all this said, you can still use the Builder pattern in Reason, but most likely you don't actually need it, as the language provides much better and more expressible mechanisms for solving your problem. Let's use the SportsCarBuilder code from Wikipedia, as our working example,

/// <summary>
/// Represents a product created by the builder
/// </summary>
public class Car
{
    public string Make { get; }
    public string Model { get; }
    public int NumDoors { get; }
    public string Colour { get; }

    public Car(string make, string model, string colour, int numDoors)
    {
        Make = make;
        Model = model;
        Colour = colour;
        NumDoors = numDoors;
    }
}

/// <summary>
/// The builder abstraction
/// </summary>
public interface ICarBuilder
{
    string Colour { get; set; }
    int NumDoors { get; set; }

    Car GetResult();
}

/// <summary>
/// Concrete builder implementation
/// </summary>
public class FerrariBuilder : ICarBuilder
{
    public string Colour { get; set; }
    public int NumDoors { get; set; }

    public Car GetResult()
    {
        return NumDoors == 2 ? new Car("Ferrari", "488 Spider", Colour, NumDoors) : null;        
    }
}

/// <summary>
/// The director
/// </summary>
public class SportsCarBuildDirector
{
    private ICarBuilder _builder;
    public SportsCarBuildDirector(ICarBuilder builder) 
    {
        _builder = builder;
    }

    public void Construct()
    {
        _builder.Colour = "Red";
        _builder.NumDoors = 2;
    }
}

public class Client
{
    public void DoSomethingWithCars()
    {
        var builder = new FerrariBuilder();
        var director = new SportsCarBuildDirector(builder);

        director.Construct();
        Car myRaceCar = builder.GetResult();
    }
}

We will provide a one-to-one translation from C# to Reason, to show the direct counterparts of C# mechanisms in Reason. Note, we will not build an idiomatic Reason code, people will unlikely follow the Builder Pattern in Reason.

The Car class defines an interface of a build product. We will represent it as a module type in Reason:

module type Car = {
  type t;
  let make : string;
  let model : string;
  let numDoors : int;
  let colour: string;

  let create : (~make:string, ~model:string, ~numDoors:int, ~colour:string) => t;
};

We decided to make the car type abstract (letting an implementor to choose a particular implementation, whether it would be a record, an object, or maybe an key to a SQL database of cars.

We will now define a corresponding interface for the car builder:

module type CarBuilder = {
   type t;
   type car;
   let setColour : (t,string) => unit;
   let getColour : t => string;
   let setNumDoors : (t,int) => unit;
   let getNumDoors : t => int;

   let getResult : t => car;
}

Now let's implement a concrete builder. Since we decided to make the car type abstract, we need to parametrize our concrete builder with the car type. In OCaml/Reason, when you need something to parametrize with a type, you usually use functors.

module FerariBuilder = (Car: Car) => {
  type t = {
    mutable numDoors: int,
    mutable colour: string
  };
  exception BadFerrari;
  let setColour = (builder, colour) => builder.colour = colour;
  let getColour = (builder) => builder.colour;
  let setNumDoors = (builder, n) => builder.numDoors = n;
  let getNumDoors = (builder) => builder.numDoors;
  let getResult = (builder) =>
    if (builder.numDoors == 2) {
      Car.create(~make="Ferrari", ~model="488 Spider", 
                 ~numDoors=2, ~colour=builder.colour)
    } else {
      raise(BadFerrari)
    };
};

And finally, let's implement a director.

module Director = (Car: Car, Builder: CarBuilder with type car = Car.t) => {
  let construct = (builder) => {
    Builder.setColour(builder, "red");
    Builder.setNumDoors(builder, 2)
  };
};

I will let you implementing the user code as an exercise. Hint, you need to start with a concrete implementation of the Car interface. You may look and play with the code (including the OCaml and Javascript version) at Try Reason.

ivg
  • 34,431
  • 2
  • 35
  • 63