如何在OCaml / Reason中创建可选模块签名类型

时间:2017-10-29 23:59:02

标签: oop ocaml reason

我正在尝试使用Reason中的模块来遵循构建器设计模式。 我有以下类型:

type userBuilderType = {
  mutable name: string,
};

以及签名类型:

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

我将UserBuilderType签名类型作为仿函数传递给BuilderPattern

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

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

然后我将适当的值作为模块传递,执行以下操作:

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

但是,为了使这个构建器设计模式真正成为构建器设计模式,签名类型将需要是可选的。我正在努力如何做到这一点。例如,如果我将签名类型编辑为空:

module type UserBuilderType = {};

编译器会抱怨:Unbound value Builder.name。关于如何使签名类型可选的任何建议都非常受欢迎。我一如既往地感谢你。

可以看到完整代码here

1 个答案:

答案 0 :(得分:3)

首先,通常您无法使用某种语言机制来实现设计模式,因为设计模式不能直接在语言类型系统和语法中表达。设计模式描述了解决软件开发中反复出现的问题的特定方法。很快,由于语言提供了直接表达设计模式的机制,因此不再将其视为设计模式。因此,某种语言中的设计模式成为另一种语言的机制。例如,汇编语言中的循环是一种设计模式,但在大多数现代语言中,它是一种语法结构。设计模式的存在通常表明缺乏特定语言或编程范例的表达性。但是,无论你的语言有多么富有表现力,总会有抽象,无法使用语言机制直接实现。

您还应该了解GoF设计模式是在考虑OOP语言的情况下编写的,具有当时OOP语言的特殊性和局限性。因此,在OCaml / Reason或任何其他具有参数多态性和一流功能的语言中,它们并不总是适用甚至不需要。

特别是,Builder模式试图解决的问题是缺少一流的构造函数和参数多态。由于我们在Reason中都有,因此我们通常不会为设计复杂的类型层次结构而烦恼。 OOP的另一个限制是缺少代数数据类型,它们是实现复杂复合数据结构的理想语言机制,例如抽象语法树(表达式解析树)等。

尽管如此,您仍然可以在Reason中使用Builder模式,但很可能您实际上并不需要它,因为该语言提供了更好,更可表达的解决问题的机制。让我们使用维基百科的SportsCarBuilder代码,作为我们的工作示例,

/// <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();
    }
}

我们将提供从C#到Reason的一对一翻译,以显示Reason中C#机制的直接对应关系。注意,我们不会构建一个惯用的Reason代码,人们不太可能在Reason中遵循Builder Pattern。

Car类定义构建产品的接口。我们将在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;
};

我们决定将汽车类型抽象化(让实现者选择一个特定的实现,无论是记录,对象,还是汽车SQL数据库的关键。

我们现在将为汽车制造商定义一个相应的界面:

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;
}

现在让我们实现一个具体的构建器。由于我们决定将汽车类型抽象化,我们需要使用汽车类型对我们的混凝土构造进行参数化。在OCaml / Reason中,当您需要使用类型进行参数化时,通常使用仿函数。

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)
    };
};

最后,让我们实现一位导演。

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

我将让您将用户代码实现为练习。提示,您需要从Car接口的具体实现开始。您可以在Try Reason查看和播放代码(包括OCaml和Javascript版本)。