In the first case, email is passed as a 1st arg to the function, that is return from the partial application String.split_on_char '@'.
In the second case, email is passed as a 1st arg to Result.bind directly, get_email function is passed to the 2nd arg of it. So, the Result.bind get_email expression is not treated as a partial application.
Nothing in the API documentation or the Result.bind signature can tell that Result.bind is non-curried and that allows it to be used for chaining in the way shown above.
My question is: is it possible to deduce the nature of the function like Result.bind (curried or not) in such a case?
Your second example is introduced by the following paragraph:
Unfortunately, calling Result.bind can be a bit awkward. We can’t pipe our value through a series of binds like we can do with calls to map. For example, this isn’t valid:
The issue with Result.bind isn’t related to currying, it’s about the order of arguments. The pipe operator x |> f reduces to function application f x, so get_user () |> Result.bind get_email is the same as Result.bind get_email (get_user ()). Unfortunately, Result.bind takes the result value first and the function second, which we can see in the type signature val bind : ('a, 'e) result -> ('a -> ('b, 'e) result) -> ('b, 'e) result, so the piped code doesn’t work. The rest of the example is about playing a trick with let operators to make using bind a little nicer syntactically.
Result.bind is in fact a curried function. An uncurried version would take a tuple of arguments, and have a signature like this: val bind_uncurried : (('a, 'e) result * ('a -> ('b, 'e) result)) -> ('b, 'e) result.
To be honest this is not a very convincing argument for the benefits of let-operators since it would be sufficient to define a flipped version of bind to use piping with results, or alternatively define an infix version of bind as it is in fact standard to do:
The actual point of let-operators is that you can intersperse the result bindings with regular bindings and with control flow constructs that depend on the bound elements, without having to pre-define auxiliary functions or ending up with a hard-to-read pile of anonymous functions.
Imho, the actual point of let-operators is flipping back the right-to-left callback style into the more natural-looking left-to-right assignment style. With callbacks code looks backwards. With letops it looks like natural, direct assignments again.
In some languages the flipped version is called flat_map, join_map or concat_map (just mentioning in case it helps connect some ideas for people). I think it would be helpful to have the flipped operators defined in the Stdlib for types like Option and Result, but I know OCaml tends towards minimalism for things like this.
I know that this isn’t how the operators and style evolved, but I’ve always understood the bind operator in terms of Danvy’s three-step explanation of CPS via Felleisen’s A-translation.
(1) start with a direct-style program
... F (M N) ....
(2) A-translate it
let f = F in
let x = M in
let y = N in
let z = x y in
...
(3) then flip into CPS by “squinting”
F (\f .
M (\x .
N (\y .
(x y) (\z .
...... ) ) ) )
And with CPS.bind : (('m -> 'a) -> 'a) -> ('m -> 'a) -> 'a you get
CPS.bind F (\f .
CPS.bind M (\x .
CPS.bind N (\y .
CPS.bind (x y) (\z .
...... ) ) ) )
And the generalization to other monads is to just change from CPS.bind to Result.bind (or whatever).
So the reason I always remember for why XX.bind has the type it does, is that it makes the “squinting” operation in step #3 easiest to do manually (before the advent of syntax support via letops in the parser).
ETA: and finally, in standard semantics the continuation always comes last. And that function argument to bind is really a continuation.