Reputation: 25
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
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
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