Coming from the scala land, I’m used to seeing the Option.fold take a thunk as argument that only gets executed if the Option is None.
I was hoping there would be something similar in OCaml but it appears there isn’t. Am I wrong?
The workaround that I came up with to emulate similar behavior is OCaml is wrap it with Lazy.t. Is there a better way than this? I’d like to avoid match statement if possible. Thanks!
let init = Lazy.from_fun (fun _ -> linspace (t, x_max) |> Tensor.unsqueeze ~dim:1) in
let handle_cdim _ dim =
let splits = Tensor.split t ~split_size:1 ~dim in
let x_maxs = List.map splits ~f:(Fn.compose Tensor.to_float0_exn Tensor.maximum) in
let ts = List.zip_exn splits x_maxs |> List.map ~f:linspace in
Lazy.from_val (Tensor.stack ts ~dim:1)
in
let result = Base.Option.fold channel_dim ~init ~f:handle_cdim in
Lazy.force_val result
It looks like you lazy-based solution is more verbose than a simple pattern matching:
match channel_dim with
| None -> linspace (t, x_max) |> Tensor.unsqueeze ~dim:1
| Some dim ->
let splits = Tensor.split t ~split_size:1 ~dim in
let x_maxs = List.map splits ~f:(Fn.compose Tensor.to_float0_exn Tensor.maximum) in
let ts = List.zip_exn splits x_maxs |> List.map ~f:linspace in
Tensor.stack ts ~dim:1
I also find it lighter syntactically than creating two lambdas to pass to a utility function.
I agree that it is simpler in this case. I was trying to avoid a match statement since reading “fold” tells me that the Option container is removed but with a match statement I have to read the branches to figure out what happens to the container. My reasoning is similar to why one would prefer map over a match statement. Maybe it’s just a matter of personal taste. I will go with a match statement in this case but it would have been nicer if init accepted a thunk
Generally speaking, OCaml doesn’t have a call-by-name function parameter feature like Scala. In OCaml we need to be explicit about delayed evaluation. So usually we end up using a function or a lazy value.
The main exception to this is for the || (or) logical operator, which does need to have delayed evaluation for short-circuiting to work like one would expect. This is a special case.
This is … fertile food for thought. One says to oneself that lazy itself is a counter-example: its argument is lazily evaluated, right?
And so, perhaps what one might want, is a systematic methodology for recognizing arguments that are to be evaluated lazily. Something like (inventing syntax that nobody should take seriously):
[where “<:” is “we’re being lazy”]
let f <:x = .... bla bla ... eventually use x ...
and then you could use it with
f ~<:e
The type of f would be something like
val f : <:int -> string
[let’s assume that the x argument of f evaluated to an int and the body of f evaluates to a string]
So really, the type of f is something like int lazy_t -> string but with an explicit name there.
Why the explicit name? Not quite sure: I’m basically just riffing off of @yawaramin 's comment.
Anyway, it’s not a crazy idea, is what I’m saying. Not a crazy idea. But hey, I could be wrong/wrong/WRONG.
That’s why I said ‘The main exception’, to avoid going into a rabbit hole (also, lazy is not a function i.e. value-level, it’s a language-level syntax. || is the only truly value-level operator with lazy evaluation)
grin I’m only half-kidding, of course, Yawar. Half, b/c I can see the sense in “well, why not? It might be useful!” But also, “boy howdy, that’s a big, biiig can of worms, mang”.
I realize this thread has spun off on a tangent, but I think it should be noted that Lazy.force is not concurrency-safe. So making ways of hiding or implicitly using it before there is an effect system in place to expose “forcers” seems like a bad idea.
There’s no point using lazy here since you’re immediately forcing, you can just use a normal thunk:
let init () = linspace (t, x_max) |> Tensor.unsqueeze ~dim:1 in
let handle_cdim _ dim () =
let splits = Tensor.split t ~split_size:1 ~dim in
let x_maxs = List.map splits ~f:(Fn.compose Tensor.to_float0_exn Tensor.maximum) in
let ts = List.zip_exn splits x_maxs |> List.map ~f:linspace in
Tensor.stack ts ~dim:1
in
let result = Base.Option.fold channel_dim ~init ~f:handle_cdim () in
result