Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: break the functor Run into Run and TryWith #97

Merged
merged 7 commits into from
Jul 31, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/Modifier.ml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ struct
| _ -> None

let run f = try_with f () {effc = handler}
let try_with = run
end

module TryWith (H : Handler) =
struct
module R = Run (H)
let try_with = R.run
end
end
19 changes: 14 additions & 5 deletions src/ModifierSigs.ml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ sig
val shadow : context option -> Trie.bwd_path -> data * tag -> data * tag -> data * tag
(** [shadow ctx path x y] is called when item [y] is being assigned to [path] but [x] is already bound at [path], where [ctx] is the context passed to {!val:S.modify}. Modifiers such as {!val:Language.renaming} and {!val:Language.union} could lead to bindings having the same name, and when that happens, this function is called to resolve the conflicting bindings. To implement silent shadowing, one can simply return item [y]. One can also employ a more sophisticated strategy to implement type-directed disambiguation. *)


val hook : context option -> Trie.bwd_path -> hook -> (data, tag) Trie.t -> (data, tag) Trie.t
(** [hook prefix id input] is called when processing the modifiers created by {!val:Language.hook}, where [ctx] is the context passed to {!val:S.modify}. When the engine encounters the modifier {!val:Language.hook}[ id] while handling the subtree [input] at [prefix], it will call [hook prefix id input] and replace the existing subtree [input] with the return value. *)

Expand All @@ -50,17 +49,27 @@ sig
sig
val run : (unit -> 'a) -> 'a
(** [run f h] initializes the engine and runs the thunk [f], using [h] to handle modifier effects. See {!module-type:Handler}. *)
end

module TryWith (H : Handler) :
sig
val try_with : (unit -> 'a) -> 'a
(** [try_with f h] runs the thunk [f], using [h] to handle the intercepted modifier effects. See {!module-type:Handler}.

Currently, [try_with] is an alias of {!val:run}, but [try_with] is intended to use within {!val:run} to intercept effects,
while {!val:run} is intended to be at the outermost layer to handle effects. That is, the following is the expected program structure:
Currently, [try_with] is an alias of {!val:Run.run}, but [try_with] is intended to be used within {!val:Run.run}
to intercept or reperform effects, while {!val:Run.run} is intended to be at the top-level to set up the environment
and handle effects by itself. That is, the following is the expected program structure:
{[
run @@ fun () ->
module R = Run (H1)
module T1 = TryWith (H2)
module T2 = TryWith (H3)

R.run @@ fun () ->
(* code *)
try_with f
T1.try_with @@ fun () ->
(* more code *)
T2.try_with @@ fun () ->
(* even more code *)
]}
*)
end
Expand Down
3 changes: 2 additions & 1 deletion src/Scope.ml
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,9 @@ struct
module M = Mod.Run (H)
let run ?(export_prefix=Emp) ?(init_visible=Trie.empty) f =
M.run (fun () -> Internal.run ~export_prefix ~init_visible f)
let try_with = M.try_with
end

module TryWith (H : Handler) = Mod.TryWith (H)

module Perform = Mod.Perform
end
19 changes: 14 additions & 5 deletions src/ScopeSigs.ml
Original file line number Diff line number Diff line change
Expand Up @@ -100,23 +100,32 @@ sig
originating from export namespaces. The default is the empty path ([Emp]).
This does not affect paths originating from visible namespaces.
@param init_visible The initial visible namespace. The default is the empty trie. *)
end

module TryWith (H : Handler) :
sig
val try_with : (unit -> 'a) -> 'a
(** Execute the code and handles the internal modifier effects. This can be used to intercept
or reperform those effects; for example, the following function silences the [shadow] effects.
See also {!val:Modifier.S.Run.try_with}.
(** Execute the code and handles the internal modifier effects.

[try_with] is intended to be used within {!val:Run.run} to intercept or reperform internal effects,
favonia marked this conversation as resolved.
Show resolved Hide resolved
while {!val:Run.run} is intended to be at the top-level to set up the environment and handle all
effects by itself. For example, the following function silences the [shadow] effects, but the
silencing function should be used within the dynamic scope of a {!val:Run.run}.
See also {!val:Modifier.S.TryWith.try_with}.
{[
module H =
struct
include Perform
let shadow _ _ _ y = y
end

let silence_shadow f = let module R = Run (H) in R.try_with f
let silence_shadow f =
let module T = TryWith (H) in
T.try_with f
]}

Note that {!val:run} starts a fresh empty scope while [try_with] remains in the current scope.
A consequence of the semantic difference between {!val:Run.run} and [try_with] is that
{!val:Run.run} starts a fresh empty scope while [try_with] stays in the current scope.
*)
end

Expand Down
6 changes: 3 additions & 3 deletions test/Example.ml
Original file line number Diff line number Diff line change
Expand Up @@ -76,16 +76,16 @@ struct
input
end

module SilentH : S.Handler =
module SilenceShadow : S.Handler =
struct
include S.Perform
let shadow _ _ _ y = y
end

(* Mute the [shadow] effects. *)
let silence_shadow f =
let module R = S.Run (SilentH) in
R.try_with f
let module T = S.TryWith (SilenceShadow) in
T.try_with f

(* The interpreter *)
let rec interpret_decl : decl -> unit =
Expand Down