Ocaml 由值参数化的函数集

Ocaml 由值参数化的函数集,ocaml,Ocaml,我想构建一组由函数变量参数化的相关函数(函数封装了坐标变换)。即,对于某些变量: val transformation : float * float -> float * float 我希望在运行时基于此转换构建一组函数。现在,在“主流”命令式语言中,我通常会选择某种带有方法的对象。我知道这些在OCaml中也是可能的,但我也读过,在OCaml中,通常最好使用模块(我想这里是一个函子) 我应该用方法还是模块(通过函子)构建对象?如果是后者,我应该如何编码?我打算用object,因为我想我

我想构建一组由函数变量参数化的相关函数(函数封装了坐标变换)。即,对于某些变量:

val transformation : float * float -> float * float
我希望在运行时基于此转换构建一组函数。现在,在“主流”命令式语言中,我通常会选择某种带有方法的对象。我知道这些在OCaml中也是可能的,但我也读过,在OCaml中,通常最好使用模块(我想这里是一个函子)

我应该用方法还是模块(通过函子)构建对象?如果是后者,我应该如何编码?我打算用object,因为我想我会写它;至于模块,我很困惑,不确定我想要的是否可能。但是如果你能帮我的话,我很想学“更好的方法”。我想要像这样的东西:

val make_set_of_functions : (float * float -> float * float) -> 'magic
其中,
'magic
是一个模块(或对象)


蒂亚

OCaml提供了几种泛化和抽象机制。有时选择哪一个并不明显。在这种情况下,我通常建议使用可用的最轻量级的机制,换句话说,不要使用提供超出您需要的机制(除非您预期将来您将需要更多)

因此,您有以下选择:

  • 函子
  • 作用
  • 阶级
  • 反对
  • 函子 函子提供了最强大的泛化机制。他们使用模块类型指定抽象和模块作为实现。这当然要付出代价。许多人发现,函子在语法上很重

    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 razor,直接使用记录,而不需要模块封装,例如

    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 = canvas xform
     canvas#draw world
    
    用法

    物体 定义类时,实际上是在定义蓝图,蓝图描述了如何创建该类型的对象。但在OCaml中,不需要从基类派生对象,也不需要使用类创建对象。要将对象识别为类的实例,它必须提供在该类中指定的所有方法(当然要有匹配的方法类型)。换句话说,OCaml类型系统不是名义上的,而是结构上的——如果一个对象拥有所有的方法,并且所有类型的方法都包含在所需的类型中,那么该对象就是该类型的。所以,当您将某些东西表示为类时,实际上您预期有人将继承您的类,并更改实现方法。这对您的实现和方法的类型施加了额外的限制。因此,如果您不期望继承,最好只使用对象(但是,如果您不期望继承,那么为什么要使用类)。以下是语法示例:

    let canvas xform = object
       method draw : obj -> unit = 
           let x,y = xform obj.coords in  
           ...
     end
    
    是的,只要用
    let
    替换
    class
    ,您将创建一个值,而不是一个类。但这种替代将给你一个很大的优势,在你与类型检查争吵。下面是使用示例:

    class canvas xform = object
       method draw : obj -> unit = 
           let x,y = xform obj.coords in  
           ...
     end
    
     let canvas = canvas xform
     canvas#draw world
    
    总结
    实际上还有其他更为模糊的机制,比如使用第一类模块。但按照奥卡姆剃刀原则,我们将削减他们的张贴。到目前为止,在我展示的所有机制中,我建议您使用函子或记录。如果你认为,在某个时间点,你会抽象出坐标表示,那么我建议使用函子。如果你想保持它的简单和具体,那么就使用记录。除非您的函数是相互递归的,并且您希望您的用户连接到您的方法中

    非常感谢,真棒的回答!事实上,我完全忘记了记录,在这一点上,它们确实似乎是最简单的解决方案,因此,更加感谢您。但是,如果可以的话,我仍然想问你更多关于模块/函子的问题,因为有一件事我还不清楚,我不能完全理解:从技术上讲,是否可以基于(第一类)函数值构建参数化的模块,即在我编写的伪签名中放入一个模块作为
    'magic
    ?(可能通过一流模块?)或者不可能/单向:模块va