标准 ML 中具有多个输入的函子

Functors with multiple inputs in Standard ML

高级问题:如何在 SML 中使用带有多个参数的 functor

我看过 this, this, this and this(PDF)。它们似乎都在 structurefunctor 定义语法方面发生冲突,并且 none 显示除一元 functor.

之外的任何内容

细节:我正在尝试用标准 ML 编写一个 Web 服务器(你可以看到努力 here),并决定将其划分为 BUFFERPARSERTCPSERVER 块。 BUFFERPARSER 都是直截了当的 structureTCPSERVER 的想法是它处理 listening/accepting 逻辑,但允许用户通过传递其他两个来指定适当的 buffering/parsing 策略。我得到的是 [= =30=]

signature TCPSERVER =
sig
    type SockAction
    type Request
    val serve : int -> (Request -> (INetSock.inet,Socket.active Socket.stream) Socket.sock -> SockAction) -> 'u
end

functor Server (Buf : BUFFER) (Par : PARSER) : TCPSERVER =
struct
  type Request = Par.Request
  datatype SockAction = CLOSE | LEAVE_OPEN
  local
  ...
   [eliding more definitions, including calls to Par.* and Buf.* functions]
  ...
  fun serve port serverFn =
      let val s = INetSock.TCP.socket()
      in 
        Socket.Ctl.setREUSEADDR (s, true);
        Socket.bind(s, INetSock.any port);
        Socket.listen(s, 5);
        print "Entering accept loop...\n";
        acceptLoop s [] serverFn
      end
  end
end

以上似乎被smlnj接受...

- use "server.sml" ;
[opening server.sml]
type Response =
  {body:string, headers:(string * string) list, httpVersion:string,
   responseType:string}
val fst = fn : 'a * 'b -> 'a
val snd = fn : 'a * 'b -> 'b
val a_ = fn : 'a * 'b * 'c -> 'a
val b_ = fn : 'a * 'b * 'c -> 'b
val c_ = fn : 'a * 'b * 'c -> 'c
val curry = fn : ('a * 'b -> 'c) -> 'a -> 'b -> 'c
signature TCPSERVER =
  sig
    type SockAction
    type Request
    val serve : int
                -> (Request
                    -> (INetSock.inet,Socket.active Socket.stream) Socket.sock
                       -> SockAction)
                   -> 'a
  end
functor HTTPServer(Buf: sig
                          type Buffer
                          val readInto : Buffer
                                         -> ('a,Socket.active Socket.stream) 
                                              Socket.sock
                                            -> BufferStatus
                          val new : int -> Buffer
                          val toSlice : Buffer -> Word8ArraySlice.slice
                          val printBuffer : Buffer -> unit
                        end) :
                  sig functor <functor> : <fctsig> end
val it = () : unit

...但被 mlton.

拒绝
~/projects/serve-sml $ mlton server.mlb
Error: server.sml 23.1. # (line with "functor Server...")
  Syntax error: replacing  FUNCTOR with  FUN.
Error: server.sml 24.1.
  Syntax error: replacing  STRUCT with  ASTERISK.
Error: server.sml 87.1.
  Syntax error found at END.
Error: server.sml 88.0.
  Parse error.
...

此外,我不完全确定如何在评估后使用该定义。即使在 smlnj 中,明显的失败:

- HTTPServer(DefaultBuffer, DefaultParser) ;
stdIn:1.2-1.12 Error: unbound variable or constructor: HTTPServer
stdIn:2.7-3.1 Error: unbound variable or constructor: DefaultParser
stdIn:1.13-2.5 Error: unbound variable or constructor: DefaultBuffer
- 

谁能告诉我我做错了什么?或者给我指点一份好的文档?

我认为多参数仿函数的 StandardML 语法是:

signature PARSER = sig
  val parse : unit -> unit
end

signature BUFFER = sig
  val read : unit -> unit
end

functor Server (structure buffer : BUFFER
            structure parser : PARSER) = struct
end

我想问题是 SML-NJ 支持高阶仿函数而 MLton 不支持。

您的 Server 仿函数通过 currying 处理多个参数。这在普通 SML 中不起作用,因为它没有高阶仿函数(SML/NJ 作为非标准扩展支持)。您需要通过引入辅助结构来使用非柯里化形式,就像您在核心语言中使用元组或记录一样:

functor Server(X : sig structure Buffer : BUFFER; structure Parser : PARSER end) =
  ...X.Buffer...X.Parser...

structure MyServer =
  Server(struct structure Buffer = MyBuffer; structure Parser = MyParser end)

显然,这非常笨拙和冗长,所以至少 SML 对上面的内容有一些语法糖,允许您隐式地保留辅助结构:

functor Server(structure Buffer : BUFFER; structure Parser : PARSER) =
  ...Buffer...Parser...

structure MyServer =
  Server(structure Buffer = MyBuffer; structure Parser = MyParser)

但这在当前的 SML 中已经很短了。

理解标准 ML 由两种语言组成很有用——值的核心语言(普通函数、数字、布尔值及其类型等)和模块语言,由签名、结构和仿函数组成.

仿函数类似于核心函数,它们总是接受一个参数和return一个模块级值。仿函数的参数类型由签名指定,而当 "calling" 仿函数时,参数的实际值将是实现该签名的结构。仿函数 return 是一个结构,其类型再次由签名确定。这是基本骨架:

signature ARG = sig end
signature RESULT = sig end
functor FUNCTOR(A : ARG) : RESULT

现在,正如 Andreas Rossberg 所提到和举例说明的那样,该标准提供了一些语法糖来表达函子的参数类型。但是,当函子需要多个结构作为输入时,我倾向于使用上述框架:

signature SERVER_ARGS =
sig
  structure ARG_0 = sig end
  structure ARG_1 = sig end
  structure ARG_2 = sig end
  structure ARG_3 = sig end
end

signature SERVER = sig end

functor ServerFn(ARGS : SERVER_ARGS) : SERVER =
struct
end

现在,调用仿函数时,语法有多种选择:

(* Using an anonymous structure *)
ServerFn(struct
  structure ARG_0 = struct end
  structure ARG_1 = struct end
  structure ARG_2 = struct end
  structure ARG_3 = struct end
end)

(* Using a named structure *)
structure ServerArgs =
struct
  structure ARG_0 = struct end
  structure ARG_1 = struct end
  structure ARG_2 = struct end
  structure ARG_3 = struct end
end

ServerFn(ServerArgs)

(* Using an anonynous structure, with syntactic sugar *)
ServerFn(
  structure ARG_0 = struct end
  structure ARG_1 = struct end
  structure ARG_2 = struct end
  structure ARG_3 = struct end
)

仿函数的结果是一个结构,只能在源代码中的结构位置找到,即,您可以使用 structure 关键字为其命名,或者将其作为参数传递到其他函子:

structure Server = ServerFn(ServerArgs)
structure Quux = OtherFunctor(ServerFn(ServerArgs))

structure 关键字是核心语言中 val 关键字的模块级等价物。一种在模块级别绑定 "variables" 的方法。同样,signature 关键字是核心语言中 type 关键字的模块级等价物——一种为 sig ... end 表示的匿名签名引入别名的有用方法。

这就是您最后一个示例失败的原因,因为 SML 顶层试图将 HTTPServer(DefaultBuffer, DefaultParser); 解释为核心级函数调用,而不是模块级 function/functor 调用。