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