Module 我是否可以避免提交到模块类型中的特定类型,并仍然获得模式匹配?

Module 我是否可以避免提交到模块类型中的特定类型,并仍然获得模式匹配?,module,pattern-matching,ocaml,Module,Pattern Matching,Ocaml,我有两种模块类型: module type ORDERED = sig type t val eq : t * t -> bool val lt : t * t -> bool val leq : t * t -> bool end module type STACK = sig exception Empty type 'a t val

我有两种模块类型:

module type ORDERED =
    sig
        type t
        val eq  : t * t -> bool
        val lt  : t * t -> bool
        val leq : t * t -> bool
    end
module type STACK =
    sig
        exception Empty 
        type 'a t
        val empty   : 'a t
        val isEmpty : 'a t -> bool  
        val cons        : 'a * 'a t -> 'a t
        val head        : 'a t -> 'a
        val tail        : 'a t -> 'a t
        val length  : 'a t -> int
    end
我想写一个函子,将顺序关系从基本的
ORDERED
类型“提升”到该类型的
STACKs
。例如,可以这样说,如果两堆元素的所有单个元素都相等,那么这两堆元素将相等。如果堆栈s1和s2的第一个元素e1和e2也是s.t.e1 现在,如果不承诺在模块类型中显式定义类型,我将不得不编写如下内容(或者我不会?):

这是一种非常笨拙的方式,模式匹配非常有效。另一种方法是使用显式构造函数强制定义类型
STACK.t
,但这会在某种程度上将我的通用模块与特定的实现联系起来,这是我不想做的


问题:我是否可以定义一些不同的内容,以便在保持模块类型通用性的同时仍然可以使用模式匹配?

我相信您已经回答了自己的问题。ocaml中的模块类型是一个不能回头查看的接口。否则,就没有意义了。在公开实现细节的同时,不能保持接口的通用性。您唯一可以使用的是通过接口公开的内容

我对您的问题的回答是肯定的,您可以对堆栈的定义做些什么,这会使堆栈的类型更复杂一些,从而使其匹配不同于单个值的模式,例如
(val,val)
。但是,您已经有了一个很好的堆栈定义,添加更多类型fluff可能是个坏主意

有关您的定义的一些建议:

  • 重命名以下函数:
    cons=>push
    head=>peek
    tail=>pop
    。我还将添加一个函数
    val pop:'a t->'a*'a t
    ,以便将
    组合成一个函数,以及镜像
    cons
    。您当前的命名方案似乎暗示有一个列表支持您的堆栈,这是实现的一个心理漏洞:D
  • 为什么
    eq
    lt
    leq
    将一对作为第一个参数?在将
    eq
    的类型限制为
    val eq:'a t*'a t->'a t
    时,您强制使用接口的程序员保留等式谓词的一侧,直到获得另一侧,然后再最终应用函数。除非您有很好的理由,否则我将使用函数的默认curried形式,因为它为用户提供了更多的自由度(
    val eq:'a t->'a t->'a t
    )。自由在于,它们可以部分应用
    eq
    并传递函数,而不是将值和函数一起传递

  • 作为其他访问函数的替代或补充,该模块可以提供一个视图函数,该函数返回用于模式匹配的变量类型

    type ('a, 's) stack_view = Nil | Cons of 'a * 's
    
    module type STACK =
    sig
      val view : 'a t -> ('a , 'a t) stack_view
      ...
    end
    
    module StackLift (O : ORDERED) (S : STACK) : ORDERED =
    struct
      let rec eq (x, y) =
        match S.view x, S.view y with
            Cons (x, xs), Cons (y, ys) -> O.eq (x, y) && eq (xs, ys)
          | Nil, Nil -> true
          | _ -> false
      ...
    end
    

    任何具有
    函数的堆栈也可以具有
    视图
    函数,而不管底层数据结构如何。

    感谢您的建议。你说我可以做些什么使堆栈匹配不同的模式,但你没有详细说明。你有什么具体的想法吗?我想听一听。关于您的建议:(1)签名使用列表命名法,因为在Chris Okasaki之后,我将堆栈视为序列的实例(例如,其中包括队列),并且,通过在所有这些抽象中对函数具有一致的命名约定,不同的实现可以以最小的麻烦相互替换;(2) 同意,明白了,谢谢!“通过在所有这些抽象中为函数提供一致的命名约定……”。如果要确保实现是可替换的,则必须通过语言内子类型约束来强制实现。否则你的类型很容易分化,你不会注意到。类似()中的
    let()=let module S=(Stack:Sequence)之类的东西将默默地强制执行兼容性。@gasche我的命名约定没有那么正式,只是为了确保我自己的理智有一些一致性。但这是有道理的。现在,这是最好的方法吗?必须编写这样的伪语句,以便编译器检查不一致性,这感觉非常不自然。@Surikator如果您希望尽可能做到非侵入性(例如,如果您在事实之后添加了
    序列
    ),这就是为什么我只建议使用这种方法的原因。更标准的方法是在
    模块类型堆栈
    的定义中简单地
    包含序列
    ,而不是相关的函数(我想
    cons,nil,head,tail
    ),这很有意义。这就是我要找的。谢谢!我同意你的看法,你订购的
    有点奇怪。还有一件事,如果你有
    STACK
    一个函子,那么
    STACK
    本身可能有一个
    eq
    lt
    ,。。功能,可直接作为
    有序
    模块使用;创建比较函数不需要第三个模块或辅助数据类型。只要模块中的函数子集与用于函子的模块匹配,就可以在该函子中使用。这有点离题,但我宁愿使用
    类型order=Lt | Eq | Gt
    比较:'t->t->order
    。在内部使用它甚至是一个好主意,因为它允许更多的代码分解,这三个独立且通常是冗余的lt/eq/gt函数。@Gasche,实现
    compare:t->t->int
    不是更有效率吗?@nlucaroni:certainl
    type ('a, 's) stack_view = Nil | Cons of 'a * 's
    
    module type STACK =
    sig
      val view : 'a t -> ('a , 'a t) stack_view
      ...
    end
    
    module StackLift (O : ORDERED) (S : STACK) : ORDERED =
    struct
      let rec eq (x, y) =
        match S.view x, S.view y with
            Cons (x, xs), Cons (y, ys) -> O.eq (x, y) && eq (xs, ys)
          | Nil, Nil -> true
          | _ -> false
      ...
    end