def f(x):
fn = get_fn(x)
if fn is None:
return
df = read_csv(fn)
if df is None:
return
x = df.mean()
return x

I guess the corresponding approach in Ocaml would be:

let f x =
get_fn x
>>|
(
fun fn -> read_csv fn
)
>>|
(
fun df -> calc_mean df
)

I encounter issues here when the last pipe depends on the output from the first pipe: I end up packing the output into a tuple:

def f(x):
fn = get_fn(x)
if fn is None:
return
df = read_csv(fn)
if df is None:
return
if 'mean' in fn:
x = df.mean()
return x
else:
x = df.std()
return x

In ocaml:

let f x =
get_fn x
>>|
(
fun fn ->
(read_csv fn, fn)
)
>>|
(
fun (df, fn) ->
match String.find fn "mean" with
| true -> calc_mean df
| false -> calc_std df
)

If you parenthesize less aggressively, you should still have fn in scope when you need it, without constructing that tuple.

Also, your boolean pattern-match could instead be an if-then-else.

e.g.

let f x =
get_fn x >>= fun fn ->
read_csv fn >>| fun df ->
if String.find fn "mean" then
calc_mean df
else
calc_std df

This could equivalently be written with let-ops, e.g.

let f x =
let* fn = get_fn x in
let+ df = read_csv fn in
if String.find fn "mean" then
calc_mean df
else
calc_std df

(That is all without seeing your actual definitions/types, of course ā Iām assuming the standard (>>|) = let+ = Option.map and (>>=) = let* = Option.bind)

those are ālet operatorā syntax actually, not ppx. Depending on your standard library choices they may be available with open Option.Let_syntax or you can set up your project with something along the lines of let (let*) = Option.bind and let (let+) x f = Option.map f x