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.e1STACK.t
,但这会在某种程度上将我的通用模块与特定的实现联系起来,这是我不想做的
问题:我是否可以定义一些不同的内容,以便在保持模块类型通用性的同时仍然可以使用模式匹配?我相信您已经回答了自己的问题。ocaml中的模块类型是一个不能回头查看的接口。否则,就没有意义了。在公开实现细节的同时,不能保持接口的通用性。您唯一可以使用的是通过接口公开的内容 我对您的问题的回答是肯定的,您可以对堆栈的定义做些什么,这会使堆栈的类型更复杂一些,从而使其匹配不同于单个值的模式,例如
(val,val)
。但是,您已经有了一个很好的堆栈定义,添加更多类型fluff可能是个坏主意
有关您的定义的一些建议:
cons=>push
,head=>peek
,tail=>pop
。我还将添加一个函数val pop:'a t->'a*'a t
,以便将头
和尾
组合成一个函数,以及镜像cons
。您当前的命名方案似乎暗示有一个列表支持您的堆栈,这是实现的一个心理漏洞:Deq
、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