我有一个关于模块的简单问题,并让它们适应签名和仿函数。如果我们有机会,我们应该让模块适合仿函数,还是应该让仿函数适合模块?我知道这取决于你可以修改什么,但如果你正在编写模块,签名和仿函数会怎样。我认为这只是一个小例子。假设我有两个模块,可以是'签名'明智的相同,除了一个功能。
module One =(*this module has a immutable data container*)
struct
...
(* container->string->container *)
let add_str_container cont str = ...
...
end
和
module Two =(*this module has a mutable data container*)
struct
...
(* container->string->unit *)
let add_str_container cont str = ...
...
end
现在我可以通过创建一个新函数并隐藏一个模块的add_str_container返回单位而另一个模块返回容器这一事实来解决这个难题。
module One =(*this module has a immutable data container*)
struct
...
(* container->string->container *)
let add_str_container cont str = ...(* now this is not exported in the signature *)
(* container->string->container *)
let add_str_aux cont str = add_str_container cont str
...
end
module Two =(*this module has a mutable data container*)
struct
...
(* container->string->unit *)
let add_str_container cont str = ...(* now this is not exported in the signature *)
(* container->string->container *)
let add_str_aux cont str = add_str_container cont str; cont
...
end
add_str_aux的添加和add_str_container的省略很好地解决了类型问题,并且签名和仿函数中的所有类型都是正确且简单的。我的问题基本上是 - 当可变容器通常在更新时返回单元并且不可变容器返回对更新容器的新引用时,如何将包含不可变和可变容器的模块添加到唯一签名?
答案 0 :(得分:4)
不是你问题的答案,但正确答案是:不要这样做。
您似乎在假设使用相同的接口对可变和不可变的数据结构进行建模是有意义的。但我会质疑这个假设。这些差异比几种回报类型的差异更为基础。试图将他们变成同一个签名的做法弊大于利。事实上,我甚至建议使用不同的命名约定来强调差异,而不是模糊它。它使程序更具可读性。
Scala集合尝试统一可变和不可变的接口,我听到了很多批评这种方法。这当然使它们变得非常复杂。
答案 1 :(得分:2)
虽然这确实是一个坏主意,但它仍然可以表达它,你只需要找到一个涵盖两种实现的抽象,至少在语法点上。例如,
==0
因此,我们可以提供此接口的命令式和持久性实现:
module type Container = sig
type t
type elt
val add : t -> elt -> t
end
因此,我们有两个实现满足相同的签名,因此,可能很容易认为它们适合相同的抽象。但是,这里有一个陷阱,虽然它们在语法上确实适合相同的抽象,但它们具有不同的语义。让我再详细说明一下这个问题。实际上,类型 module Persistent : Container = struct
type t = int list
type elt = int
let add xs x = x :: xs
end
module Imperative : Container = struct
type t = int list ref
type elt = int
let add xs x =
xs := x :: !xs;
xs
end
并不要求返回的值不是输入值的别名,因此在一般情况下,这可能发生。但是,有一个约定,如果一个函数接受一个值,并返回一个相同类型的值,那么返回的值不会与输入值混淆(至少它是不可观察的)。这使得程序的推理变得更加容易。但同样,这只是一种惯例,不能通过类型系统或语言的任何其他机制来保证。这就是为什么我们可以将两个不同的实现放入同一个签名中,这是因为我们在违反约定时应该小心。
有些情况下,这样的勾选可以带来利润。假设您有一个通用算法,它只需要一个容器,它可以放置数据。算法本身不会依赖于一个事实,即值没有别名。这种算法的示例可以是图搜索,其由容器参数化。它将始终使用最后返回的值,并丢弃中间容器,因此它对于持久容器和命令容器都可以正常工作。并且没有理由,为什么这样的算法应该仅由命令式(或者,仅持久性)容器参数化。
统一持久性和命令式数据结构的库的一个特定示例是OCaml Graph库,它提供了命令式和持久性图形适合的接口。因此,这种算法可以在两种情况下互换。
总结一下,这是可能的,但它会违反惯例,所以你应该非常小心。你不能用你的类型系统捕获它,所以,至少,你应该考虑仔细选择名称,并记录你的假设。