Skip to content

Commit

Permalink
Merge pull request #10097 from gasche/lazy-map
Browse files Browse the repository at this point in the history
Lazy.map
  • Loading branch information
gasche committed Apr 14, 2021
2 parents 2e25fec + 3d93b0b commit b926823
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 21 deletions.
6 changes: 6 additions & 0 deletions Changes
Expand Up @@ -121,6 +121,12 @@ Working version
- #9582: Add Array.{find_opt,find_map,split,combine}.
(Nicolás Ojeda Bär, review by Daniel Bünzli and Gabriel Scherer)

- #10097: Lazy.map, Lazy.map_val: ('a -> 'b) -> 'a Lazy.t -> 'b Lazy.t
(map f x) is always (lazy (f (force x))), whereas (map_val f x)
applies f directly if x is already forced.
(Gabriel Scherer, review by Nicolás Ojeda Bär, Alain Frisch, Xavier Leroy,
Daniel Bünzli and Stephen Dolan)

* #10169, #10270: Use capitalized module names in the Standard Library prefixing
scheme to match Dune, e.g. Stdlib__String instead of Stdlib__string. This is a
breaking change only to code which attempted to use the internal names before.
Expand Down
11 changes: 9 additions & 2 deletions stdlib/lazy.ml
Expand Up @@ -55,7 +55,6 @@ external make_forward : 'a -> 'a lazy_t = "caml_lazy_make_forward"

external force : 'a t -> 'a = "%lazy_force"

(* let force = force *)

let force_val = CamlinternalLazy.force_val

Expand All @@ -64,7 +63,6 @@ let from_fun (f : unit -> 'arg) =
Obj.set_field x 0 (Obj.repr f);
(Obj.obj x : 'arg t)


let from_val (v : 'arg) =
let t = Obj.tag (Obj.repr v) in
if t = Obj.forward_tag || t = Obj.lazy_tag || t = Obj.double_tag then begin
Expand All @@ -81,3 +79,12 @@ let lazy_from_fun = from_fun
let lazy_from_val = from_val

let lazy_is_val = is_val


let map f x =
lazy (f (force x))

let map_val f x =
if is_val x
then lazy_from_val (f (force x))
else lazy (f (force x))
76 changes: 57 additions & 19 deletions stdlib/lazy.mli
Expand Up @@ -57,7 +57,6 @@ type 'a t = 'a CamlinternalLazy.t

exception Undefined

(* val force : 'a t -> 'a *)
external force : 'a t -> 'a = "%lazy_force"
(** [force x] forces the suspension [x] and returns its result.
If [x] has already been forced, [Lazy.force x] returns the
Expand All @@ -67,36 +66,75 @@ external force : 'a t -> 'a = "%lazy_force"
recursively.
*)

val force_val : 'a t -> 'a
(** [force_val x] forces the suspension [x] and returns its
result. If [x] has already been forced, [force_val x]
returns the same value again without recomputing it.
(** {1 Iterators} *)

If the computation of [x] raises an exception, it is unspecified
whether [force_val x] raises the same exception or {!Undefined}.
@raise Undefined if the forcing of [x] tries to force [x] itself
recursively.
val map : ('a -> 'b) -> 'a t -> 'b t
(** [map f x] returns a suspension that, when forced,
forces [x] and applies [f] to its value.
It is equivalent to [lazy (f (Lazy.force x))].
@since 4.13.0
*)

(** {1 Reasoning on already-forced suspensions} *)

val is_val : 'a t -> bool
(** [is_val x] returns [true] if [x] has already been forced and
did not raise an exception.
@since 4.00.0 *)

val from_val : 'a -> 'a t
(** [from_val v] evaluates [v] first (as any function would) and returns
an already-forced suspension of its result.
It is the same as [let x = v in lazy x], but uses dynamic tests
to optimize suspension creation in some cases.
@since 4.00.0 *)

val map_val : ('a -> 'b) -> 'a t -> 'b t
(** [map_val f x] applies [f] directly if [x] is already forced,
otherwise it behaves as [map f x].
When [x] is already forced, this behavior saves the construction of
a suspension, but on the other hand it performs more work eagerly
that may not be useful if you never force the function result.
If [f] raises an exception, it will be raised immediately when
[is_val x], or raised only when forcing the thunk otherwise.
If [map_val f x] does not raise an exception, then
[is_val (map_val f x)] is equal to [is_val x].
@since 4.13.0 *)


(** {1 Advanced}
The following definitions are for advanced uses only; they require
familiary with the lazy compilation scheme to be used appropriately. *)

val from_fun : (unit -> 'a) -> 'a t
(** [from_fun f] is the same as [lazy (f ())] but slightly more efficient.
[from_fun] should only be used if the function [f] is already defined.
It should only be used if the function [f] is already defined.
In particular it is always less efficient to write
[from_fun (fun () -> expr)] than [lazy expr].
@since 4.00.0 *)

val from_val : 'a -> 'a t
(** [from_val v] returns an already-forced suspension of [v].
This is for special purposes only and should not be confused with
[lazy (v)].
@since 4.00.0 *)
val force_val : 'a t -> 'a
(** [force_val x] forces the suspension [x] and returns its
result. If [x] has already been forced, [force_val x]
returns the same value again without recomputing it.
val is_val : 'a t -> bool
(** [is_val x] returns [true] if [x] has already been forced and
did not raise an exception.
@since 4.00.0 *)
If the computation of [x] raises an exception, it is unspecified
whether [force_val x] raises the same exception or {!Undefined}.
@raise Undefined if the forcing of [x] tries to force [x] itself
recursively.
*)


(** {1 Deprecated} *)

val lazy_from_fun : (unit -> 'a) -> 'a t
[@@ocaml.deprecated "Use Lazy.from_fun instead."]
Expand Down
57 changes: 57 additions & 0 deletions testsuite/tests/lib-lazy/test.ml
@@ -0,0 +1,57 @@
(* TEST
* expect
*)

(* expect-tests currently do not collect I/O,
so we emulate I/O by collecting output in a "log" *)
let logger () =
let log = ref [] in
let show_log v = List.rev !log, v in
let log v = log := v :: !log in
log, show_log
[%%expect{|
val logger : unit -> ('a -> unit) * ('b -> 'a list * 'b) = <fun>
|}]

let _ =
let log, show_log = logger () in
let x = lazy (log "x"; 41) in
let y =
log "map";
Lazy.map (fun n -> log "y"; n+1) x in
log "force y";
show_log (Lazy.force y)
;;
[%%expect{|
- : string list * int = (["map"; "force y"; "x"; "y"], 42)
|}]

let _ =
let log, show_log = logger () in
let x = lazy (log "x"; 41) in
let y =
log "map_val";
Lazy.map_val (fun n -> log "y"; n+1) x in
assert (not (Lazy.is_val y));
log "force y";
show_log (Lazy.force y)
;;
[%%expect{|
- : string list * int = (["map_val"; "force y"; "x"; "y"], 42)
|}]

let _ =
let log, show_log = logger () in
let x = lazy (log "x"; 41) in
log "force x";
let () = ignore (Lazy.force x) in
let y =
log "map_val";
Lazy.map_val (fun n -> log "y"; n+1) x in
assert (Lazy.is_val y);
log "y is val";
show_log (Lazy.force y)
;;
[%%expect{|
- : string list * int = (["force x"; "x"; "map_val"; "y"; "y is val"], 42)
|}]

0 comments on commit b926823

Please sign in to comment.