【问题标题】:What's the difference between functors and "generics"函子和“泛型”有什么区别
【发布时间】:2010-12-01 08:04:47
【问题描述】:

我正在查看OCaml's functors。在我看来,它与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# generics oop functional-programming ocaml


    【解决方案1】:

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

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

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

    即使函子只是 generics-for-namespace,是什么 的显着优势 方法?类也可以用作 使用嵌套的临时命名空间 类。

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

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

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

    【讨论】:

    • C++和C#在类型系统方面有什么不同?我看不出,在底线中,您如何拥有一种新的 List 类型,无论它是由 VM 支持还是在编译时自动生成。请启发我。
    • 不客气。模板 类 X { T t;公共:int hello() { return t.hello(); } };类你好 { public: int hello() { return 1; } }; int main() { X x;返回 x.hello(); }
    • 也许我有点密集,但我没有得到它。在您的示例中,您有 Hello 类和 X 类型的类,就像在 C# 中一样。我验证它按预期打印 1 codepad.org/vZUPwFgs 在您给出的示例中类型系统方面的区别是什么(当然,您使用了只能在 C++ 中使用的非引用类型,但除此之外我不'看不出区别)。
    • 请注意,模板类参数在 C++ 中没有任何约束。你能在 C# 中实现这一点吗?模板有两个不同的阶段——定义和实例化。这有很多后果:模板类必须在实例化时完全可用,因此适度模板化的代码需要大量的头文件 - 导致编译时间非常长,模板中的错误被检测到较晚,编译器可以轻松执行更多的内联和优化,链接器有更多的工作做以消除重复的模板实例等
    【解决方案2】:

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

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

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

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

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

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

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

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

    【讨论】:

      【解决方案3】:

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

      【讨论】:

        【解决方案4】:

        我刚刚找到了一个可以帮助您解决问题的资源 - 因为 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=false

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


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

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

        【讨论】:

        • Functor 在 OCaml 中确实意味着不同的东西(这比 C++ 的“函子”更接近“通用”)。
        猜你喜欢
        • 2019-08-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-04-24
        • 2010-10-15
        • 2023-03-17
        • 2015-05-30
        相关资源
        最近更新 更多