16

我在看OCaml 的 functorsC++在我看来,它与/ C#/中所谓的通用对象非常相似Java。如果您暂时忽略 Java 的类型擦除,并忽略 C++ 模板的实现细节(我对语言特性感兴趣),函子与泛型完全一致。如果我理解正确,仿函数会根据您提供的类型为您提供一组新的函数,例如

List<MyClass>.GetType() != List<MyOtherClass>.GetType()

但是你可以粗略地重写 OCaml 的

#module Set =
   functor (Elt: ORDERED_TYPE) ->
     struct
       type element = Elt.t
       type set = element list
       let empty = []
       let rec add x s =
         match s with
           [] -> [x]
         | hd::tl ->
            match Elt.compare x hd with
              Equal   -> s         (* x is already in s *)
            | Less    -> x :: s    (* x is smaller than all elements of s *)
            | Greater -> hd :: add x tl
       let rec member x s =
         match s with
           [] -> false
         | hd::tl ->
             match Elt.compare x hd with
               Equal   -> true     (* x belongs to s *)
             | Less    -> false    (* x is smaller than all elements of s *)
             | Greater -> member x tl
     end;;

进入C#

class Set<T> where T : ISortable
{
    private List<T> l = new List<T>();
    static public Set<T> empty = new Set<T>();
    public bool IsMember(T x) {return l.IndexOf(x) > -1;}
    public void Add(T x) {l.Add(x);}
}

当然有一点不同,因为仿函数会影响 a Module(这只是一堆类型函数和值定义,类似于C#'s 命名空间)。

但仅此而已吗?函子仅仅是应用于命名空间的泛型吗?或者我缺少的仿函数和泛型之间是否有任何显着差异。

即使仿函数只是命名空间的泛型,这种方法的显着优势是什么?类也可以用作使用嵌套类的临时命名空间。

4

4 回答 4

6

但仅此而已吗?函子仅仅是应用于命名空间的泛型吗?

是的,我认为可以将仿函数视为“具有泛型的命名空间”,并且它本身在 C++ 中非常受欢迎,其中唯一的选择是使用具有所有静态成员的类,这很快就会变得非常难看。与 C++ 模板相比,一个巨大的优势是模块参数的显式签名(这是我相信 C++0x 概念可能变成的,但是oops)。

此外,模块与命名空间有很大不同(考虑多个结构签名、抽象和私有类型)。

即使仿函数只是命名空间的泛型,这种方法的显着优势是什么?类也可以用作使用嵌套类的临时命名空间。

不确定它是否符合重要条件,但可以打开命名空间,而类的使用是明确限定的。

总而言之 - 我认为函子本身并没有明显的“显着优势”,它只是代码模块化的不同方法 - ML 风格 - 它非常适合核心语言。不确定将模块系统与语言进行比较是否有意义。

PS C++ 模板和 C# 泛型也有很大不同,因此将它们作为一个整体进行比较感觉有点奇怪。

于 2009-09-25T10:10:34.770 回答
5

如果我理解正确,函子会根据您提供的类型为您提供一组新的函数

更一般地,函子将模块映射到模块。您的Set示例将遵循ORDERED_TYPE签名的模块映射到实现集合的模块。ORDERED_TYPE签名需要一个类型和一个比较函数。因此,您的 C# 不等效,因为它仅通过类型参数化集合,而不是通过比较函数。因此,您的 C# 只能为每个元素类型实现一个集合类型,而仿函数可以为每个元素模块实现多个集合模块,例如按升序和降序排列。

即使仿函数只是命名空间的泛型,这种方法的显着优势是什么?

高阶模块系统的一个主要优点是能够逐步细化接口。在 OOP 中,一切都是公共的或私有的(或者有时是受保护的或内部的等)。使用模块,您可以随意地逐步细化模块签名,让更多的公共访问更接近模块的内部,并随着您远离代码的那部分而抽象出越来越多的模块签名。我觉得这是一个相当大的好处。

与 OOP 相比,高阶模块系统大放异彩的两个例子是参数化数据结构实现相互之间以及构建可扩展的图形库。请参阅Chris Okasaki 的博士论文中的“结构抽象”部分,以获取在其他数据结构上参数化的数据结构示例,例如将队列转换为可连接列表的函子。请参阅 OCaml 出色的 ocamlgraph 库和论文Designing a Generic Graph Library using ML Functors,了解使用函子的可扩展和可重用图算法的示例。

类也可以用作使用嵌套类的临时命名空间。

在 C# 中,您不能参数化类而不是其他类。在 C++ 中,您可以做一些事情,例如从通过模板传入的类继承。

此外,您可以使用 curry 函子。

于 2009-10-18T22:35:24.117 回答
2

SML 中的函子是生成的,因此程序中某一点上的函子应用程序产生的抽象类型与另一点上同一应用程序(即相同函子、相同参数)产生的抽象类型不同。

例如,在:

structure IntMap1 = MakeMap(Int)
(* ... some other file in some other persons code: *)
structure IntMap2 = MakeMap(Int)

您不能将 IntMap1 中的函数生成的映射与 IntMap2 中的函数一起使用,因为 IntMap1.t 是与 IntMap2.t 不同的抽象类型。

实际上,这意味着如果您的库具有生成 IntMap.t 的函数,那么您还必须提供 IntMap 结构作为库的一部分,并且如果您的库的用户想要使用他自己的(或其他库)IntMap,那么他有将值从您的 IntMap 转换为他的 IntMap - 即使它们在结构上已经是等效的。

另一种方法是使您的库本身成为函子,并要求库的用户通过他们选择的 IntMap 应用该函子。这也需要图书馆的用户做比理想更多的工作。尤其是当您的库不仅使用 IntMap,还使用其他类型的 Map、各种 Set 等时。

使用泛型 OTOH,编写一个生成 Map 的库非常容易,并且该值可以与其他采用 Map 的库函数一起使用。

于 2011-12-11T15:13:16.727 回答
1

我刚刚找到了一个可以帮助您解决问题的来源 - 因为 OCaml 对于函子有不同的含义:

http://books.google.de/books?id=lfTv3iU0p8sC&pg=PA160&lpg=PA160&dq=ocaml+functor&source=bl&ots=vu0sdIB3ja&sig=OhGGcBdaIUR-3-UU05W1VoXQPKc&hl=de&ei=u2e8SqqCNI7R-Qa43IHSCw&_res=X&resnum=9# onepage&q=ocaml%20functor&f=false

仍然 - 如果将同一个词用于不同的概念,我会感到困惑。


我不知道 OCaml 是否有不同的含义 - 但通常 Functor 是一个“函数对象”(参见此处:http ://en.wikipedia.org/wiki/Function_object )。这与泛型完全不同(参见此处:http ://en.wikipedia.org/wiki/Generic_programming )

函数对象是可以用作函数的对象。泛型是一种参数化对象的方法。泛型与继承(专门化对象)有点正交。泛型引入了类型安全并减少了强制转换的需要。函子是改进的函数指针。

于 2009-09-25T06:42:59.863 回答