Search code examples
ocamlreasonbucklescript

How to use Js.Option.map?


For this code:

  // val map : ('a -> 'b [@bs]) -> 'a option -> 'b option
  let optTest: Js.Option.t(int) = Js.Option.map(x => x, Js.Option.some(1));

I am getting following error:

  This expression should not be a function, the expected type is (. 'a) => 'b

where x => x is red. I am really confused, why the map isn't working? From its type signature it looks I am using it correctly, yet compiler says the first argument is not supposed to be a function?


Solution

  • Short answer - use Belt.Option.map instead:

    let optTest: option(int) = Belt.Option.map(Some(1), x => x);
    

    Long answer:

    The Js namespace is intended mostly for bindings to JavaScript's standard APIs. And while Js.Option for historical reasons was included in this namespace, the conventions used in the Js namespace is still that of very thin bindings.

    The type you see for the callback function in the documentation, 'a -> 'b [@bs], and the type you see in the error message, (. 'a) => 'b, are exactly the same type. But the former is in OCaml's syntax while the latter is in Reason's, and also sugared up to look less offensive. Either way, the problem is that you're passing it an ordinary function when it expects this weird other kind of function.

    The weird other kind of function is called an uncurried function. It's called that because "normal" functions in Reason are curried, while JavaScript functions are not. An uncurried function is therefore essentially just a native JavaScript function, which you sometimes need to deal with because you might receive one or need to pass one to a higher-order JavaScript function, like those in the Js namespace.

    So how do you create an uncurried function in Reason? Just add a ., like in the type:

    let optTest: option(int) = Js.Option.map((.x) => x, Some(1);
    

    Or if you want to do it without the sugar (which you don't, but for the sake of completeness):

    let optTest: option(int) = Js.Option.map([@bs] x => x, Some(1);
    

    Addendum:

    You might have noticed that I've replaced Js.Option.t and Js.Option.some in your example with option and Some. That's because those are the actual primitives. The option type is essentially defined as

    type option('a) =
      | Some('a)
      | None
    

    and is available everywhere.

    Js.Option.t('a) (and Belt.Option.t('a)) is just an alias. And Js.Option.some is just a convenience function which doesn't have any equivalent in Belt.Option. They're mostly just there for consistency, and you should normally use the actual type and variant constructors instead.