在设计模块时如何决定是在类型级还是模块级参数化?

时间:2016-08-29 07:32:28

标签: types parameters ocaml sml modularity

我正在努力深入理解ML式模块:我认为 概念很重要,我喜欢他们鼓励的那种想法。我只是 现在发现参数类型和参数之间可能产生的张力 参数模块。我正在寻找工具来思考这件事 帮助我在构建程序时做出明智的设计决策。

拳头我会尽力描述我的问题。然后我会提供一个 我正在研究的一个学习项目的具体例子。最后,我会的 重新审视一般性问题,以便将其引入某一点。

(我很抱歉,我还不太了解这个问题,而且更简洁。)

总的来说,我发现的紧张是:功能最多 当我们为它们提供参数时,灵活,并且对最广泛的重用开放 类型签名(适当时)。但是,模块最灵活,最开放 当我们密封内部函数的参数化时,最广泛的重用 模块,而是参数化给定类型的整个模块。

在比较模块时可以找到这种差异的现成例子 实现LIST签名与实现的签名 ORD_SET。模块List:LIST提供了许多有用的功能, 在任何类型上参数化。一旦我们定义或加载了List模块,我们就可以 随时应用它提供的任何功能来构造,操纵或 检查任何类型的列表。例如,如果我们正在处理两个字符串和 整数,我们可以使用同一个模块来构造和操作 两种类型的值:

val strList = List.@ (["a","b"], ["c","d"])
val intList = List.@ ([1,2,3,4], [5,6,7,8])

另一方面,如果我们想处理有序集合,问题就不同了: 有序集要求订购关系保持其所有元素, 并且没有单一的具体功能compare : 'a * 'a -> order 为每种类型产生这种关系。因此,我们需要一个不同的 满足我们希望放入的每种类型的ORD_SET签名的模块 有序集。因此,为了构造或操纵有序的字符串集 和整数,我们必须为每种类型实现不同的模块[1]:

structure IntOrdSet = BinarySetFn ( type ord_key = int
                                    val compare = Int.compare )
structure StrOrdSet = BinarySetFn ( type ord_key = string
                                    val compare = String.compare )

然后我们必须在适当的模块中使用拟合函数 希望按给定类型操作:

val strSet = StrOrdSet.fromList ["a","b","c"]
val intSet = IntOrdSet.fromList [1,2,3,4,5,6]

这里有一个非常简单的权衡:LIST模块提供功能 你喜欢任何类型的范围,但他们不能利用任何关系 在任何特定类型的值之间保持; ORD_SET个模块提供 必须约束于仿函数中提供的类型的函数 参数,但通过相同的参数化,他们能够 纳入有关内部结构和关系的具体信息 他们的目标类型。

很容易想象我们想要设计替代家庭的情况 列表模块,使用仿函数参数化类型和其他值 提供具有更复杂结构的类似列表的数据类型:例如,指定 有序列表的数据类型,或使用自平衡二进制表示列表 搜索树木。

创建模块时,我认为它很容易识别 能够提供多态函数以及何时需要参数化 在某些类型上。对我来说,似乎更难的是弄清楚哪种方式 在下游工作时你应该依赖的模块。

总的来说,我的问题是:当我设计一个系统时 各种相关的模块,我怎样才能弄清楚是否围绕模块进行设计 提供使用仿函数生成的多态函数或模块 在类型和值上参数化?

我希望通过以下示例来说明这种困境及其重要性, 取自我正在进行的玩具项目。

我有functor PostFix (ST:STACK) : CALCULATOR_SYNTAX。这需要一个 堆栈数据结构的实现并生成一个读取的解析器 具体的后缀("反向抛光")表示为抽象语法(将... 由计算器模块下游评估),反之亦然。现在,我曾经 使用提供多态堆栈类型的标准堆栈接口 要对其进行操作的功能数量:

signature STACK =
sig
    type 'a stack
    exception EmptyStack

    val empty : 'a stack
    val isEmpty : 'a stack -> bool

    val push : ('a * 'a stack) -> 'a stack
    val pop  : 'a stack -> 'a stack
    val top  : 'a stack -> 'a
    val popTop : 'a stack -> 'a stack * 'a
end

这很好用,并且给了我一些灵活性,因为我可以使用基于列表的堆栈 或基于矢量的堆栈,或其他什么。但是,说我想添加一个简单的日志记录 函数到堆栈模块,以便每次推送元素,或 从堆栈弹出,它打印出堆栈的当前状态。现在我会 需要fun toString : 'a -> string表示堆栈收集的类型,以及 据我所知,这不能合并到STACK模块中。现在我 需要将类型密封到模块中,并在类型上参数化模块 收集在堆栈中的toString函数将让我生成一个 收集类型的可打印表示。所以我需要像

这样的东西
functor StackFn (type t
                 val toString: t -> string ) =
struct
   ...
end

这将生成与STACK签名匹配的模块,因为它 没有提供多态类型。因此,我必须更改所需的签名 对于PostFix仿函数。如果我有很多其他模块,我必须改变 所有这些都是。这可能不方便,但真正的问题是我 我不能再使用我的简单的基于列表或基于矢量的STACK模块了 当我想要记录时,PostFix仿函数。现在,似乎,我必须回去 并将这些模块重写为密封类型。

所以回到,继续,并(仁慈地)完成我的问题:

  1. 是否有某种方法可以指定由其生成的模块的签名 StackFn以便他们最终成为"特殊情况"是STACK
  2. 或者,是否有一种为PostFix模块编写签名的方法 这将允许StackFn生成的模块和那些模块 满足STACK
  3. 一般来说,有没有办法思考它们之间的关系 哪些模块可以帮助我将来捕捉/预测这种事情?
  4. (如果您已经读过这篇文章了。非常感谢!)

1 个答案:

答案 0 :(得分:4)

正如您所发现的,参数多态性与SML和OCaml中的仿函数/模块之间存在张力。这主要是由于模块的“两种语言”性质以及缺乏特殊的多态性。 1MLmodular implicits都为此问题提供了不同的解决方案。第一种是将两种参数化统一起来,后者允许在需要时激发一些特殊的多态性。

回到实际考虑。使用仿函数,对于给定数据结构的单态化是相当容易的(但是冗长/恼人)。这是一个例子(在OCaml中)。有了这个,您仍然可以编写通用实现并在以后专门化它们(通过提供打印功能)。

module type POLYSTACK = sig
  type 'a stack
  exception EmptyStack

  val empty : 'a stack
  val isEmpty : 'a stack -> bool

  val push : ('a * 'a stack) -> 'a stack
  val pop  : 'a stack -> 'a stack
  val top  : 'a stack -> 'a
  val popTop : 'a stack -> 'a stack * 'a
  val toString : ('a -> string) -> 'a stack -> string
end

module type STACK = sig
  type elt
  type t
  exception EmptyStack

  val empty : t
  val isEmpty : t -> bool

  val push : (elt * t) -> t
  val pop  : t -> t
  val top  : t -> elt
  val popTop : t -> t * elt
  val toString : t -> string
end

module type PRINTABLE = sig
  type t
  val toString : t -> string
end

module Make (E : PRINTABLE) (S : POLYSTACK)
  : STACK with type elt = E.t and type t = E.t S.stack
= struct
  type elt = E.t
  type t = E.t S.stack
  include S
  let toString = S.toString E.toString
end

module AddLogging (S : STACK)
  : STACK with type elt = S.elt and type t = S.t
= struct
  include S
  let push (x, s) =
    let s' = S.push (x, s) in print_string (toString s') ; s'
end