我想构建一组由函数变量参数化的相关函数(该函数封装了坐标变换)。即,对于某些变量:
val transformation : float * float -> float * float
我想在运行时基于此转换构建一组函数。现在,在“主流”命令式语言中,我通常会使用方法来寻找某种对象。我知道这些在OCaml中也是可能的,但是我也读过OCaml,通常最好使用模块(例如,这里是一个仿函数,我认为)。
我应该使用方法或模块(通过 functor )构建对象吗?如果是后者,我应该如何编码呢?我打算用对象,因为我想我知道如何写它;对于模块,我很困惑,不确定我想要的是否可能。但是如果你可以帮助我,我会喜欢学习“更好的方法”。我想要这样的东西:
val make_set_of_functions : (float * float -> float * float) -> 'magic
其中'magic
是模块(或对象)。
TIA!
答案 0 :(得分:6)
OCaml提供了几种泛化和抽象机制。有时候选择哪一个并不明显。在这种情况下,我通常建议使用最轻量级的机制,换句话说,不要使用比你需要的机制更多的机制(除非你预计将来你需要更多)。
所以,你有以下选择:
Functors提供了最强大的泛化机制。它们使用模块类型将抽象和模块指定为实现。当然,这需要付出代价。很多人发现,仿函数在语法上很重。
module type Coordinates = sig
val transform : (int * int) -> (int * int)
end
module Canvas(Coord : Coordinates) = struct
let draw obj =
let x,y = Coord.transform obj.coords in
...
end
以下是用法:
module Canvas = Canvas(Minimap)
Canvas.draw world
模块只是一个复合数据结构,可以包含函数,类型和其他模块。编译时,类型和其他短暂声明将被删除,因此模块表示实际上与记录的表示相同。仿函数实际上被编译为函数,由记录参数化。看起来,我们并没有尝试使用任何类型或模块来参数化我们的“函数集”,因此我们可以使用Occam剃刀,直接使用记录,无需模块封装,例如,
type canvas = {
draw : object -> unit;
...
}
let canvas xform = {
draw = (fun obj -> ... );
...
}
用法:
let canvas = canvas xform
canvas.draw world
类与模块一样,也将几个字段封装到一个实体中。它们可以包含方法(即函数)和实例变量。但是,它们不能包含类型。类和模块之间的主要区别在于类函数不是在编译时相互绑定,而是在运行时(所谓的后期绑定)相互绑定。这意味着,无论何时调用类方法,您实际上并没有调用具体函数,而是调用当前在与调用方法关联的插槽中编写的函数。基本上,这意味着,一个类是一组相互递归的函数,可以在运行时进行变异。换句话说,相互递归是开放的,即,您的类的用户可以通过继承更改任何方法的实现。基本上,对你来说,这意味着,你不能相信自己的方法,并且对象的类型不是固定的,而是开放的。所有这些一起使得很难对类进行推理,所以如果你不需要开放式递归,那么我建议远离类。为了保护类的概念,我想注意它们适用于实现AST访问者,解释器和其他使用本质上递归的数据结构的算法。 所以这是一个例子:
class canvas xform = object
method draw : obj -> unit =
let x,y = xform obj.coords in
...
end
用法
let canvas = new canvas xform
canvas#draw world
定义类时,实际上是在定义一个蓝图,它描述了如何创建该类型的对象。但是在OCaml中,对象不需要从基类派生,也不需要使用类创建。对于要被识别为类的实例的对象,它必须提供在此类中指定的所有方法(当然,具有匹配的方法类型)。换句话说,OCaml类型系统不是名义上的,而是结构 - 如果一个对象具有所有方法,并且所有类型的方法都包含在所需类型中,那么该对象就是该类型。所以,当你表达某个类的东西时,你实际上是在期待有人会继承你的类,并改变方法的实现。这对您的实现和方法类型提出了额外的限制。所以,如果你没有预料到继承,最好只使用一个对象(但是,如果你没有预期继承,那么为什么要使用类)。这是语法示例:
let canvas xform = object
method draw : obj -> unit =
let x,y = xform obj.coords in
...
end
是的,只需用class
替换let
,您就会创建一个值,而不是一个类。但是这种替换会让你在使用类型检查器的争吵中获得很大的优势。以下是用法示例:
let canvas = canvas xform
canvas#draw world
实际上还有其他机制,比如使用第一类模块更加模糊。但遵循奥卡姆剃刀原则,我们将削减他们的帖子。在我到目前为止所展示的所有机制中,我建议您使用仿函数或记录。如果您认为,在某个时间点,您将抽象坐标表示,那么我建议使用仿函数。如果你想保持简单和具体,那就使用记录。 Don't use classes除非您的函数是相互递归的,并且您希望用户挂钩您的方法。