'如何正确使用高阶函子?'或者'如何与funsigs玩得很开心?'

时间:2016-09-04 04:49:45

标签: module ocaml sml smlnj ml

动机

对于我的生活,我无法弄清楚如何使用更高阶的仿函数 SML / NJ到任何实际目的。

根据 SML/NJ docs on the implementation's special features, 应该可以通过使用指定一个仿函数作为另一个仿函数的参数 funsig关键字。因此,给定签名

signature SIG = sig ... end

我们应该能够指定一个能够生成令人满意的模块的仿函数 SIG,应用于满足某些签名SIG'的结构时。如,

funsig Fn (S:SIG') = SIG

以这种方式声明Fn,我们应该(能够定义另一个 将 this 仿函数作为参数的仿函数。即,我们可以定义一个模块 它是在另一个参数化模块上参数化的,并且可能使用了 后者属于前者;因此:

functor Fn' (functor Fn:SIG) =
struct
...
structure S' = Fn (S:SIG')
...
end

理论上这一切看起来都不错,但我无法弄清楚如何实际使用 这种模式。

示例问题

以下是我尝试使用此模式的两个实例,仅用于查找 这是不切实际的:

首次尝试

对于我的第一次尝试,只是玩游戏,我试图制作一个仿真器 采用实现有序集的仿函数,并生成用于处理的模块 使用整数集(不是很有用,但它可以让你参数化集 在不同的集合实现中给定类型的)我可以定义 以下结构,他们将编译(使用新泽西州的标准ML v110.7 ):

structure IntOrdKey : ORD_KEY
= struct
    type ord_key = int
    val compare = Int.compare
end

funsig SET_FN (KEY:ORD_KEY) = ORD_SET

functor IntSetFn (functor SetFn:SET_FN) =
struct
    structure Set = SetFn (IntOrdKey)
end

但是当我真正尝试将IntSetFn应用于应该满足的算符时 SET_FN funsig,它只是没有解析:

- structure IntSet = IntSetFn (functor ListSetFn);
= ;
= ;;
stdIn:18.1-24.2 Error: syntax error: deleting  RPAREN SEMICOLON SEMICOLON
- structure IntSet = IntSetFn (functor BinarySetFn) ;
= ;
= ;
stdIn:19.1-26.2 Error: syntax error: deleting  RPAREN SEMICOLON SEMICOLON

第二次尝试

我的第二次尝试以两种方式失败。

我已经定义了一个嵌套模块的结构,它实现了多态和 单形堆栈(the source file,为了好奇)。至 实现单态堆栈,你做

- structure IntStack = Collect.Stack.Mono (type elem = int);
structure IntStack : MONO_STACK?
- IntStack.push(1, IntStack.empty);
val it = - : IntStack.t

等等。到目前为止似乎工作正常。现在,我想定义一个模块 参数化这个仿函数。所以我已经定义了一个funsig Collect.Stack.Mono仿函数(可以在我的回购中看到)。然后,继续 上面指出的模式,我试图定义以下测试模块:

(* load my little utility library *)
CM.autoload("../../../utils/sources.cm");

functor T (functor StackFn:MONO_STACK) =
struct
    structure S = StackFn (type elem = int)
    val x = S.push (1, S.empty)
end

但这不会编译!我收到类型错误:

Error: operator and operand don't agree [overload conflict]
  operator domain: S.elem * S.t
  operand:         [int ty] * S.t
  in expression:
    S.push (1,S.empty)

uncaught exception Error
  raised at: ../compiler/TopLevel/interact/evalloop.sml:66.19-66.27
             ../compiler/TopLevel/interact/evalloop.sml:44.55
             ../compiler/TopLevel/interact/evalloop.sml:292.17-292.20

然而,在仿函数T中,我似乎正在使用完全相同的实例化 在顶层完美运作的模式。我错过了什么?

不幸的是,那不是我意外的结束。现在,我删除该行 导致类型错误,离开,

functor T (functor StackFn:MONO_STACK) =
struct
    structure S = StackFn (type elem = int)
end

编译好:

[scanning ../../../utils/sources.cm]
val it = true : bool
[autoloading]
[autoloading done]
functor T(<param>: sig functor StackFn : <fctsig> end) :
         sig
           structure S : <sig>
         end
val it = () : unit

但我实际上无法实例化该模块!显然是路径访问语法 不支持高阶函子?

- structure Test = T (functor Collect.Stack.Mono);
stdIn:43.36-43.43 Error: syntax error: deleting  DOT ID DOT

我迷路了。

问题

我有三个相关的问题:

  1. 我在SML / NJ中是否存在高阶仿函数的基本原理 缺少,或者它只是一个不完整的,笨拙的实现的功能 语言?
  2. 如果是后者,我可以在哪里转向更优雅和实用的高阶 函子? (希望是一个SML,但如果有必要,我会深入OCaml。)
  3. 我是否应采取不同的方法来实现这些目标 一起避免高阶函子的效果是什么?
  4. 非常感谢任何答案,提示或后续问题!

1 个答案:

答案 0 :(得分:7)

关于您的首次尝试,应用IntSetFn仿函数的正确语法是:

structure IntSet = IntSetFn (functor SetFn = ListSetFn)

同样适用于您在第二次尝试中应用Test仿函数:

structure Test = T (functor StackFn = Collect.Stack.Mono)

这应该可以修复语法错误。

尝试在仿函数S中使用堆栈结构T时遇到的类型错误与您定义MONO_STACK funsig的方式有关:

funsig MONO_STACK (E:ELEM) = MONO_STACK

这只是说它返回一个 MONO_STACK结构,具有完全抽象的elem类型。它没有说它的elem类型与E.elem相同。根据这个,我可以通过像

这样的函子
functor F (E : ELEM) = struct type elem = unit ... end

到您的仿函数T。因此,在T内,类型系统不允许假定类型为S.elem = int,因此您会收到类型错误。

要解决此问题,您需要按如下方式优化MONO_STACK funsig:

funsig MONO_STACK (E:ELEM) = MONO_STACK where type elem = E.elem

这应该消除类型错误。

[编辑]

关于你的问题:

  1. 高阶仿函数在SML / NJ中的语法有点笨拙,因为它试图与纯SML保持100%兼容,它将仿函数的命名空间与结构的命名空间分开。如果情况并非如此,那么就不需要将funsigs作为单独的命名空间(以及其他语法巴洛克式),并且签名语言可以简单地扩展为包含仿函数类型。

  2. 莫斯科ML是另一种具有更高阶模块扩展的SML方言,可以更优雅地解决兼容性问题(并且更具表现力)。还有(现在大部分已经死了)ALice ML,还有另一个带有高阶函子的SML方言,只是简单地删除了尴尬的命名空间分离。 OCaml当然首先没有这个约束,因此它的高阶模块在语法上也更加规则。

  3. 方法似乎很好。