5

When you define an operator such as

let (++) a b = a :: b 

When you do

let v = foo a ++ bar b 

bar is evaluated before foo. A workaround is to use let expression, i.e.

let e1 = foo a in let e2 = bar b in e1 ++ e2

However, sometimes it'd be handy to define an operator such as it's always evaluated from left to right. Is there a way to do it in OCaml or with a ppx or with lazy ?

Chris
  • 26,361
  • 5
  • 21
  • 42
Butanium
  • 726
  • 5
  • 19
  • 1
    My inclination is to say "no" because in `foo a ++ bar b` , `foo a` and `bar b` are both evaluated before `++` ever is considered, but I don't know ppx well enough to rule out a hack to make this happen. – Chris Jan 20 '23 at 15:26

2 Answers2

4

The evaluation order of function arguments is part of the semantics of the language (it is currently not specified in OCaml), you cannot change it.

More often than not, whenever the order of evaluation of function argument starts to matter, it is a sign that there are too many loose global mutable states and that you want to tighten your grip on mutable states.

For instance, if you need to enforce a strict order of mutation on a global state, the common solution is to use a state monad, or a more eager variant like

type context (* all of your previous global state should fit here *)
type 'a data_and_context = context * 'a
type 'a m = context -> 'a data_and_context
val (>>=): 'a m -> ('a -> 'b m) -> 'b m 

This is essentially gives you the ability to define yourself how the state is evaluated in

foo x >>= bar x
octachron
  • 17,178
  • 2
  • 16
  • 23
  • Do you have a toy example using this operator ? – Butanium Jan 21 '23 at 16:07
  • 2
    @Butanium assume you need to "print" a string (here the global, mutable state is the "output"). You could define `type context = string`, and `let (>>=) (output, x) f = let (output', result) = f x in (output ^ output', result)`. Here, a function that "prints" should return the string printed instead. That is, very roughly, the IO monad in Haskell. Doing this requires no mutation, and no global variables, so it makes your functions pure (or, at least, *more* pure than they were before), and so the order of evaluation has no importance (as it should, which is why it's left unspecified in OCaml) – jthulhu Jan 21 '23 at 20:51
  • Note that it's not *quite* the same interface as proposed by @octachron, but it's still quite similar, probably a bit simpler to understand, and most importantly it fits in a single comment :) – jthulhu Jan 21 '23 at 20:54
0

Another solution which might be less handy as it requires more refactoring is to use some lazy operator as suggested by @Null on the OCaml discord:

let (+++) a b = let lazy b = b in a + b
let (!!!) (lazy a) = a
let _ = !!! (lazy (printf "%d%!" 1;1)) +++ lazy (printf "%d%!" 2;2) +++ lazy (printf "%d%!" 3;3) +++ lazy (printf "%d%!" 4;4)
1234
Butanium
  • 726
  • 5
  • 19