interopffihigher-order-functionsrescript

Optional arguments for higher-order functions


I try to write a binding for socket.io.

I am having trouble with a function (next() in my example code at the bottom), that either takes no argument or a error object (Js.Exn.raiseError("ERROR!")).

I can't find a way to define a function signature that can take both types of argument as the first value.

I am not even sure, if what I am asking for is possible in rescript, any help to solve that problem in the proper rescript way, would be appreciated.

My current implementation looks like this:

type server
type socket
type next = (. unit) => unit 

@new @module("socket.io") external socketIO: unit => server = "Server"
@send external use: (server, (socket, next) => unit) => unit = "use"
@send external listen: (server, int) => unit = "listen"
@send external on: (server, @string [ #connection(socket => unit) ] ) => unit = "on"

let io = socketIO()

io->use((socket, next) => {
    Js.log("FIRST")
    next(.)
})

io->use((socket, next) => {
    Js.log("SECOND")
    next(.)
})

io->on(#connection(socket => 
    Js.log("CONNECT")
))

io->listen(3000)

Solution

  • It's not possible in general to have a function with a variable number of arguments, but it is possible to pass either undefined or a value, which in most cases will be equivalent.

    One way to do so is to simply use the option type. If we re-define next as

    type next = (. option<int>) => unit 
    

    we can use it like this

    io->use((_socket, next) => {
        next(. None)
        next(. Some(42))
    })
    

    which will generate the following JavaScript:

    io.use(function (_socket, next) {
          next(undefined);
          return next(42);
        });
    

    Another option could be to use optional arguments, but this doesn't seem to work with uncurrying, and recently there's been bugs with currying that the compiler author seems to have no interest in fixing, so it might not work there either, but it might be worth a shot:

    type next = (~error: int=?, unit) => unit
    ...
    io->use((_socket, next) => {
        next(())
        next(~error=42, ())
    })
    

    Lastly, there already exists some bindings for socket.io (bs-socket.io). These also don't handle this case unfortunately, but it might save you from re-inventing some wheels at least.