NuclearSquid
NuclearSquid

Reputation: 25

How to get polymorphic monad operators in OCaml?

I'm trying to learn how to compose monads using functions like bind or map, and so far my code works but it's really verbose. Here's a tiny script I just wrote to illustrate what I mean, it casts a string into an int option and double the number if there is one.

open Base
open Stdio

let is_number str = String.for_all ~f:(Char.is_digit) str

let str_to_num_opt (str: string) : int option =
    if is_number str then Some (Int.of_string str)
    else None

let double n = n * 2

let () =
    let maybe_num = Option.map (str_to_num_opt "e123") ~f:double in
    match maybe_num with
    | Some n -> printf "Number found in string : %d\n" n
    | None   -> printf "No number found in string.\n"

The definition of maybe_num is quite verbose, and it's going to be exponentially worse the longer the composition chain is, so I tried using the >>| operator, but due to it being attached to the Option module, I can't directly use it as an infix function, instead calling it as Option.(>>|) (which is basically the same as using Option.map but I lose the named argument).

What I did was add open Option at the top of the file, then rewrite my () function as so :

let () =
    let maybe_num =
        str_to_num_opt "123"
        >>| double
    in match maybe_num with
    | Some n -> printf "Number found in string : %d\n" n
    | None   -> printf "No number found in string.\n"

The code is now a lot cleaner, but I can only use this trick for one module per file, since if I add open List (for instance) after open Option at the top of the file, it's going to shadow the definition of >>| and the operator will only work on lists, thus breaking my code.

What I was hopping for, was the two definitions of >>| coexisting together at the same time, and have the compiler / interpreter choose the one with the correct signature when running the code (similar to a trait being implemented for different types in Rust), but I couldn't make it work. Is it even possible ?

Upvotes: 1

Views: 176

Answers (2)

jthulhu
jthulhu

Reputation: 8678

An alternative solution would be to define bindings for each module you frequently use. So, for instance, you could use let* for Option.bind, let+ for Result.bind, ... This has the advantage of being very short and lightweight to use, while being explicit (because you can recognize the monad through the last character). You could even put all such definitions into their own module that you would open at the beginning of each file, so that you don't have to redefine the bindings each time.

Upvotes: 0

Chris Vine
Chris Vine

Reputation: 727

In OCaml, to do what you want you can have a local opening of modules within a let block in the form let open List in ... (for other forms of local opening see the manual at https://v2.ocaml.org/manual/moduleexamples.html#s%3Amodule%3Astructures), and you can shadow variables, so that for example you could define operator >>| for some code by reference to List.map and subsequently redefine it for other code in the same file by reference to Option.map.

Likewise you could have a definition let ( let* ) = Option.bind and subsequently have a shadowing definition let ( let* ) = Result.bind for other code.

However, OCaml does not implement ad hoc polymorphism (aka function overloading), in the sense that the compiler will not automatically pick the correct version of operater >>| for you. This may or may not be available in the future via modular implicits.

Upvotes: 1

Related Questions