如何在 OCaml/Reason 中创建可选模块签名类型
How to create an optional module signature type in 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.
首先,通常你不能使用语言的某种机制来实现设计模式,因为设计模式不能直接在语言类型系统和语法中表达。设计模式描述了一种用于解决软件开发中反复出现的问题的特定方法。一旦语言提供了直接表达设计模式的机制,就不再将其视为设计模式。因此,在一种语言中是设计模式的东西在另一种语言中变成了一种机制。例如,汇编语言中的循环是一种设计模式,尽管在大多数现代语言中它是一种句法结构。设计模式的存在通常表明特定语言或编程范式缺乏表现力。但是,无论您的语言多么富有表现力,总有一些抽象无法直接使用语言机制实现。
您还应该了解,GoF 设计模式是在考虑到当时 OOP 语言的特性和局限性的情况下使用 OOP 范式编写的。因此,在 OCaml/Reason 或具有参数多态性和 first-class 函数的任何其他语言中,它们并不总是适用甚至不需要。
特别是,Builder 模式试图解决的问题是缺少 first-class 构造函数和参数多态性。因为我们在 Reason 中都有,所以我们通常不会为设计复杂的类型层次结构而烦恼。 OOP 的另一个限制是缺少代数数据类型,代数数据类型是实现复杂复合数据结构(如抽象语法树(表达式解析树)等)的理想语言机制。
综上所述,您仍然可以在 Reason 中使用构建器模式,但很可能您实际上并不需要它,因为该语言提供了更好、更易表达的机制来解决您的问题。让我们使用来自维基百科的 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 中的构建器模式。
Car
class 定义构建产品的接口。我们将在 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 版本)
我正在尝试使用 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.
首先,通常你不能使用语言的某种机制来实现设计模式,因为设计模式不能直接在语言类型系统和语法中表达。设计模式描述了一种用于解决软件开发中反复出现的问题的特定方法。一旦语言提供了直接表达设计模式的机制,就不再将其视为设计模式。因此,在一种语言中是设计模式的东西在另一种语言中变成了一种机制。例如,汇编语言中的循环是一种设计模式,尽管在大多数现代语言中它是一种句法结构。设计模式的存在通常表明特定语言或编程范式缺乏表现力。但是,无论您的语言多么富有表现力,总有一些抽象无法直接使用语言机制实现。
您还应该了解,GoF 设计模式是在考虑到当时 OOP 语言的特性和局限性的情况下使用 OOP 范式编写的。因此,在 OCaml/Reason 或具有参数多态性和 first-class 函数的任何其他语言中,它们并不总是适用甚至不需要。
特别是,Builder 模式试图解决的问题是缺少 first-class 构造函数和参数多态性。因为我们在 Reason 中都有,所以我们通常不会为设计复杂的类型层次结构而烦恼。 OOP 的另一个限制是缺少代数数据类型,代数数据类型是实现复杂复合数据结构(如抽象语法树(表达式解析树)等)的理想语言机制。
综上所述,您仍然可以在 Reason 中使用构建器模式,但很可能您实际上并不需要它,因为该语言提供了更好、更易表达的机制来解决您的问题。让我们使用来自维基百科的 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 中的构建器模式。
Car
class 定义构建产品的接口。我们将在 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.