我在看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#
的命名空间),所以会有一点点不同。
但它只是吗?仿函数仅仅是泛型应用于命名空间吗?或者,我缺少的仿函数和泛型之间是否有任何显着的差异。
即使仿函数只是用于命名空间的泛型,这种方法的显着优势是什么?类也可以用作使用嵌套类的临时命名空间。
答案 0 :(得分:6)
但它只是吗?仅仅是仿函数 泛型应用于名称空间?
是的,我认为可以将仿函数视为“具有泛型的命名空间”,并且在C ++中它本身就是非常欢迎,其中唯一的选择是使用包含所有静态成员的类很快就很难看。与C ++模板相比,一个巨大的优势是模块参数上的显式签名(这是我认为C ++ 0x概念可能成为的,但是oops)。
模块与命名空间完全不同(考虑多个结构签名,抽象和私有类型)。
即使是仿函数也是如此 generics-for-namespace,是什么 显着的优势 进场?类也可以用作 使用嵌套的临时命名空间 类。
不确定它是否符合重要条件,但可以打开名称空间,同时显式限定类使用。
总而言之 - 我认为单独的仿函数没有明显的“显着优势”,它只是代码模块化的不同方法 - ML风格 - 并且它非常适合于 核心语言。不确定将模块系统与语言进行比较是否有意义。
PS C ++模板和C#泛型也是完全不同的,因此整体比较它们并不奇怪。
答案 1 :(得分:4)
如果我理解正确,仿函数会从您提供的类型中为您提供一组新功能
更一般地说,仿函数将模块映射到模块。您的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 ++中,您可以执行一些操作,例如从通过模板传入的类继承。
另外,你可以讨论仿函数。
答案 2 :(得分: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的库函数一起使用。
答案 3 :(得分:1)
我刚刚找到了一个可以帮助解决问题的来源 - 因为OCaml对于仿函数有不同的含义:
仍然 - 如果同一个词被用于不同的概念,我会感到困惑。
我不知道OCaml是否有不同的含义 - 但通常Functor是一个“Function对象”(参见这里:http://en.wikipedia.org/wiki/Function_object)。这与泛型完全不同(请参阅此处:http://en.wikipedia.org/wiki/Generic_programming)
函数对象是可以用作函数的对象。泛型是一种参数化对象的方法。泛型是一种与遗传正交的(它专门用于对象)。仿制药引入了类型安全性并减少了对铸造的需求。函数是一个改进的函数指针。