Functors with multiple inputs in Standard ML

2.1k views Asked by At

High level question: How do I use functors with multiple arguments in SML?

I've looked at this, this, this and this(PDF). All of them seem to conflict in terms of structure or functor definition syntax, and none of them show anything other than a unary functor.

Specifics: I'm trying to write a web server in Standard ML (you can see the effort here), and have decided to partition it into BUFFER, PARSER and TCPSERVER chunks. The BUFFER and PARSER are both just straightforward structures. The idea with the TCPSERVER is that it handles listening/accepting logic, but allows the user to specify an appropriate buffering/parsing strategy by passing the other two in. What I've got is something like

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

The above seems to be accepted by 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

... but rejected by 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.
...

Additionally, I'm not entirely sure how to use the definition once it's evaluated. Even in smlnj, the obvious fails:

- 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
- 

Can anyone tell me what I'm doing wrong? Or even point me to a good piece of documentation?

3

There are 3 answers

0
Andreas Rossberg On BEST ANSWER

Your Server functor does multiple arguments via currying. That does not work in plain SML, because it does not have higher-order functors (which SML/NJ supports as a non-standard extension). You need to use uncurried form, by introducing an auxiliary structure, like you would use a tuple or record in the core language:

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)

Obviously, this is pretty clumsy and verbose, so at least SML has some syntactic sugar for the above, allowing you to keep the auxiliary structure implicit:

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

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

But that is as short as it gets in current SML.

0
gruenewa On

I think the StandardML syntax for multi-argument functors is:

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

I guess the issue is that SML-NJ supports higher-order functors while MLton does not.

0
Ionuț G. Stan On

It's useful to understand that Standard ML is composed of two languages — the core language of values (ordinary functions, numbers, booleans, their types, etc.) and the language of modules, comprised of signatures, structures and functors.

Functors are similar to core functions, they always accept a single argument and return a module-level value. A functor's argument type is specified by a signature, while the actual value of the argument, when "calling" the functor, will be a structure implementing that signature. A functor returns a structure, whose type is again determined by a signature. This is the basic skeleton:

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

Now, as mentioned and examplified by Andreas Rossberg, the standard provides some syntactic sugar for expressing a functor's param type. However, I tend to favor the above skeleton when a functor requires more than a few structures as input:

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

Now, when calling a functor, there are several choices as to the syntax:

(* 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
)

A functor's result, being a structure, may only be found in a structure position in source code, i.e., you either give it a name using the structure keyword, or you pass it along as an argument to some other functor:

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

The structure keyword is the module-level equivalent of the val keyword in the core language. A way to bind "variables" at the module level. In the same vein, the signature keyword is the module-level equivalent of the type keyword in the core language — a helpful way to introduce aliases for anonymous signatures denoted by sig ... end.

This is why your last example fails, because the SML top-level tries to interpret HTTPServer(DefaultBuffer, DefaultParser); as a core-level function call, not as a module-level function/functor call.