繁体   English   中英

仿函数和“仿制药”之间有什么区别

[英]What's the difference between functors and “generics”

我正在看OCaml的仿函数 它看起来与C++ / 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);}
}

当然,由于仿函数影响了一个Module (它只是一堆类型的函数和值定义,类似于C#的命名空间),所以有一点不同。

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

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

但它只是它吗? 仿函数仅仅是泛型应用于命名空间吗?

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

模块与命名空间完全不同(考虑多个结构签名,抽象和私有类型)。

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

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

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

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

如果我理解正确,仿函数会从您提供的类型中为您提供一组新功能

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

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

高阶模块系统的一个主要优点是能够逐步改进接口。 在OOP中,一切都是公共的或私人的(或有时受到保护或内部等)。 使用模块,您可以逐步优化模块签名,使更多的公共访问更接近模块的内部,并在您从代码的这一部分进一步抽象时越来越多地抽象出来。 我发现这是一个相当大的好处。

与OOP相比,高阶模块系统发光的两个例子是相互参数化数据结构实现并构建可扩展图库。 有关在其他数据结构上参数化的数据结构的示例,请参阅Chris Okasaki博士论文中的 “结构抽象”部分,例如将队列转换为可连接列表的仿函数。 有关使用仿函数的可扩展和可重用图算法的示例,请参阅OCaml优秀的ocamlgraph库和使用ML Functors 设计通用图库的文章。

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

在C#中,您无法对其他类的参数化进行参数化。 在C ++中,您可以执行一些操作,例如从通过模板传入的类继承。

此外,你可以咖喱仿函数。

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的库函数一起使用。

我刚刚找到了一个可以帮助你解决问题的来源 - 因为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&sa=X&oi=book_result&ct=result&resnum=9#v= onepage&q = ocaml的%20functor&F =假

仍然 - 如果同一个词被用于不同的概念,我会觉得很困惑。


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

函数对象是可以用作函数的对象。 泛型是一种参数化对象的方法。 泛型是一种与遗传正交的(它专门用于对象)。 仿制药引入了类型安全性并减少了对铸造的需求。 函子是一个改进的函数指针。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM