ocaml中的评估监视器
evaluation monitor in ocaml
我要实现的目标类似于日志记录工具,但用于监视和传输来自 运行ning 模拟的任意数据。这是简化的情况:
module Sim (V:VEC) = struct
module V = V
module M = struct type data = V.t end
let loop n init_data =
let running_data = ref init_data in
for _i = 1 to n do
(*?*) (* monitor here: data => outside world *)
rdata := process_data !rdata
done
end
虽然模拟循环,但在 ?
我可能想要 'tap' 数据并累积它。其他时候,我只想让它 运行 并以最小的开销禁用数据流——?
处于紧密循环中。所以我希望流式传输能够以很少的成本进行配置。
我现在拥有的是:
module Sim (V:VEC) = struct
module V = V
module M = struct type data = V.t end
let data_monitor : (M.data -> unit) ref = ref (fun d -> ())
let loop n init_data =
let running_data = ref init_data in
for _i = 1 to n do
!data_monitor !rdata; (* monitor here *)
rdata := process_data !rdata
done
end
即。我在那里放了一个存根监控功能参考。在实际的应用程序脚本中,我可以分配一个函数,例如将数据值累积到列表或类似列表中。有效。
所以问题是:这是实现我想要的 best/lowest overhead/nicest 方法吗?
这种方法似乎有点hackish,我宁愿使用模块系统而不是函数指针。但是,要流式传输的数据类型仅在仿函数 Sim
内定义。因此,在 Sim
之外的另一个模块 Sampler
中创建监视功能并以此参数化 Sim
似乎不方便 and/or 需要重复代码或递归模块。我试过了,但我无法使所有类型都相等。
编辑:这是它在没有函数引用的情况下的大致尝试:
module Sampler (V:VEC) : sig
module V : VEC
type data = V.t
val monitor_data : data -> unit
end
with type data = V.t = struct
module V = V
type data = V.t
let monitor_data data = store_away_the data
end
module Sim (V:VEC) (Sampler:??) : sig
...
end with type M.data = V.t
在 ??
我不确定如何指定 Sampler 的输出签名,因为输入签名 VEC
仍然是免费的;我也不确定如何使类型相等工作。也许我在这里做错了。
正如评论中所讨论的那样,您可以使用高阶函数(而不是必须求助于高阶函子)来做这样的事情:
module type VEC = sig type t end
module Vec = struct type t = unit end
module Sim (V : VEC) =
struct
module M = struct type data = V.t list end
let process x = x
let rec loop ?(monitor : M.data -> unit = ignore) n data =
if n <= 0 then data
else
(monitor [];
process data |> loop ~monitor (n - 1))
end
module MySim = Sim (Vec)
let monitor _ = print_endline "foo"
let () =
MySim.loop ~monitor 5 ()
上面的 loop
将一个可选函数作为参数,您可以使用语法 ~monitor:my_fun
或 ~monitor:(fun data -> ...)
传递它。如果您已经在范围内有一个名为 monitor
的值,您可以简单地执行 ~monitor
来传递它。如果您不传递任何内容,则默认值为 ignore
(即 fun _ -> ()
)。
我还以递归方式重写了 loop
。上面的代码打印了 5 次 foo
。请注意,您的 monitor
函数仍然可以来自 Sampler
模块,只是在实例化 Sim
.
时无需传入整个模块
编辑:如果你仍然想声明一个高阶仿函数,你可以这样做 (...)
编辑 2:更改了给出附加信息的示例,高阶仿函数的原因是有多个监视函数要调用。请注意,在这种情况下,除了高阶函子之外还有其他解决方案:您可以将函数分组到一个记录中,然后将记录传递给 loop
。与此类似,您可以传递一个 first-class 模块。或者,您可以创建一个采用变体类型的函数,其案例指示在哪个阶段调用监视函数,并携带与每个阶段关联的数据。您也可以为此使用 classes,但我不推荐它。但是,如果您致力于在 Sim
.
中声明 M
,则函子方法确实有一个优势
我在草图中省略了签名VEC
,因为我的印象是提问者知道在哪里添加它,并且没有问题:)
module type SAMPLER =
sig
type data
val monitor : data -> unit
val monitor' : data list -> unit
end
(* These are created inside Sim. *)
module type DATA =
sig
type data
val show : data -> string
end
(* Note that I am using destructive substitution (:=) to avoid the need
to have a type data declared in the body of MySampler below. If you
use a regular type equality constraint, you need to add a field
"type data = Data.data" to the body. *)
module type SAMPLER_FN =
functor (Data : DATA) -> SAMPLER with type data := Data.data
(* This is the higher-order functor (it takes another functor as an
argument). *)
module Sim (Sampler_fn : SAMPLER_FN) =
struct
(* Corresponds to module "Sim.M" in the question. *)
module Data =
struct
type data = string
let show s = s
end
(* Note that without additional type constraints or rearrangements,
the type data is abstract to Sampler (more precisely, Sampler_fn
is parametric over Data). This means that Sampler_fn can't
analyze values of type data, which is why we need to provide
functions such as Data.show to Sampler_fn for instances of
Sampler_fn to be "useful". If you are trying to avoid this and
are having trouble with these specific constraints, let me
know. The ability to pass types and related values (functions
in this case) to Sampler_fn is the main argument in favor of
using a higher-order functor. *)
module Sampler = Sampler_fn (Data)
let simulate x =
(* Call one monitoring function. *)
Sampler.monitor "hi!";
(* Do some computation and call another monitoring function. *)
Sampler.monitor' ["hello"; "world"]
end
用法:
module MySampler (Data : DATA) =
struct
let monitor data = data |> Data.show |> print_endline
let monitor' data =
data
|> List.map Data.show
|> String.concat " "
|> print_endline
end
module MySim = Sim (MySampler)
let () = MySim.simulate ()
这会打印
hi!
hello world
完整性:
建立在 的仿函数部分之上,这就是我目前正在使用的。它仍然有点复杂,也许可以做得更简洁,但它有一些不错的优点。即:可以在集中位置(类型 SAMPLER
的模块)打开和关闭各个方面的监视,并且可以导出任意类型,即使它们仅在模拟器模块内部的某个地方定义。
我这样定义监控(=采样)模块和模块类型:
module type STYPE = sig type t end
module type SSAMPLER = sig
type t
val ev : t React.event
val mon : t -> unit
end
module type SAMPLER_FN = functor (Data : STYPE) -> SSAMPLER
with type t := Data.t
(* stub sampler function for a single one *)
module Never : SAMPLER_FN = functor (Data : STYPE) -> struct
let ev = React.E.never
let mon = ignore
end
(* event primitive generating sampling function *)
module Event : SAMPLER_FN = functor (Data : STYPE) -> struct
let (ev : Data.t React.event), mon' = React.E.create ()
let mon = mon' ?step:None
end
在这里,我使用 React 库生成输出数据流。 React.E.never
事件什么都不做,对应于采样被关闭。然后像这样指定完整的采样配置:
(* the full sampling config *)
module type SAMPLER = sig
val sampler_pos : (module SAMPLER_FN)
val sampler_step : (module SAMPLER_FN)
(* and several more... *)
end
module NoSampling : SAMPLER = struct
let sampler_pos = (module Never: SAMPLER_FN)
let sampler_step = (module Never: SAMPLER_FN)
(* ... *)
end
(* default sampling config *)
module DefaultSampling : SAMPLER = struct
include NoSampling
(* this is only possible when using first class modules *)
let sampler_pos = (module Event : SAMPLER_FN)
end
可以避免第一个 class 模块,但是 DefaultSampling
中方便的包含和覆盖将不被允许。
在模拟库代码中是这样使用的:
module type VEC = sig
type t
val zeropos : t
val wiggle : t -> t
end
module Sim (V:VEC) (Sampler:SAMPLER) = struct
module V = V
module M = struct
type t = { mutable pos : V.t }
val create () = { pos=V.zeropos }
module Sampler_pos = (val Sampler.sampler_pos) (struct type nonrec t = t end)
let update f m = m.pos <- f m.pos
end
module Sampler_b = (val Sampler.sampler_b) (struct type t = int end)
let loop n (running_data:M.t) =
for i = 1 to n do
(* monitor step number: *)
Sampler_b.mon i;
(* monitor current pos: *)
Sampler_pos.mon running_data;
M.update V.wiggle running_data
done
end
此处,采样仿函数应用于模拟循环中的适当位置。 (val ...)
再次是必要的,只是因为第一个 class 模块包装。
最后,在应用程序脚本中,可以这样做:
module Simulator = Sim (V) (DefaultSampling);;
let trace = Simulator.M.Sampler_pos.ev
|> React.E.fold (fun l h -> h :: l) []
|> React.S.hold [];;
let init_m = Simulator.M.create () in
Simulator.loop 100 init_m;;
React.S.value trace;;
最后一行包含循环期间出现的 Simulator.M.t
类型值的累积列表。计步器的监控(一个愚蠢的例子)被关闭。通过制作另一个 SAMPLER
类型的采样函子并以此参数化 Sim
,如果需要,可以进一步自定义监控。
我要实现的目标类似于日志记录工具,但用于监视和传输来自 运行ning 模拟的任意数据。这是简化的情况:
module Sim (V:VEC) = struct
module V = V
module M = struct type data = V.t end
let loop n init_data =
let running_data = ref init_data in
for _i = 1 to n do
(*?*) (* monitor here: data => outside world *)
rdata := process_data !rdata
done
end
虽然模拟循环,但在 ?
我可能想要 'tap' 数据并累积它。其他时候,我只想让它 运行 并以最小的开销禁用数据流——?
处于紧密循环中。所以我希望流式传输能够以很少的成本进行配置。
我现在拥有的是:
module Sim (V:VEC) = struct
module V = V
module M = struct type data = V.t end
let data_monitor : (M.data -> unit) ref = ref (fun d -> ())
let loop n init_data =
let running_data = ref init_data in
for _i = 1 to n do
!data_monitor !rdata; (* monitor here *)
rdata := process_data !rdata
done
end
即。我在那里放了一个存根监控功能参考。在实际的应用程序脚本中,我可以分配一个函数,例如将数据值累积到列表或类似列表中。有效。
所以问题是:这是实现我想要的 best/lowest overhead/nicest 方法吗?
这种方法似乎有点hackish,我宁愿使用模块系统而不是函数指针。但是,要流式传输的数据类型仅在仿函数 Sim
内定义。因此,在 Sim
之外的另一个模块 Sampler
中创建监视功能并以此参数化 Sim
似乎不方便 and/or 需要重复代码或递归模块。我试过了,但我无法使所有类型都相等。
编辑:这是它在没有函数引用的情况下的大致尝试:
module Sampler (V:VEC) : sig
module V : VEC
type data = V.t
val monitor_data : data -> unit
end
with type data = V.t = struct
module V = V
type data = V.t
let monitor_data data = store_away_the data
end
module Sim (V:VEC) (Sampler:??) : sig
...
end with type M.data = V.t
在 ??
我不确定如何指定 Sampler 的输出签名,因为输入签名 VEC
仍然是免费的;我也不确定如何使类型相等工作。也许我在这里做错了。
正如评论中所讨论的那样,您可以使用高阶函数(而不是必须求助于高阶函子)来做这样的事情:
module type VEC = sig type t end
module Vec = struct type t = unit end
module Sim (V : VEC) =
struct
module M = struct type data = V.t list end
let process x = x
let rec loop ?(monitor : M.data -> unit = ignore) n data =
if n <= 0 then data
else
(monitor [];
process data |> loop ~monitor (n - 1))
end
module MySim = Sim (Vec)
let monitor _ = print_endline "foo"
let () =
MySim.loop ~monitor 5 ()
上面的 loop
将一个可选函数作为参数,您可以使用语法 ~monitor:my_fun
或 ~monitor:(fun data -> ...)
传递它。如果您已经在范围内有一个名为 monitor
的值,您可以简单地执行 ~monitor
来传递它。如果您不传递任何内容,则默认值为 ignore
(即 fun _ -> ()
)。
我还以递归方式重写了 loop
。上面的代码打印了 5 次 foo
。请注意,您的 monitor
函数仍然可以来自 Sampler
模块,只是在实例化 Sim
.
编辑:如果你仍然想声明一个高阶仿函数,你可以这样做 (...)
编辑 2:更改了给出附加信息的示例,高阶仿函数的原因是有多个监视函数要调用。请注意,在这种情况下,除了高阶函子之外还有其他解决方案:您可以将函数分组到一个记录中,然后将记录传递给 loop
。与此类似,您可以传递一个 first-class 模块。或者,您可以创建一个采用变体类型的函数,其案例指示在哪个阶段调用监视函数,并携带与每个阶段关联的数据。您也可以为此使用 classes,但我不推荐它。但是,如果您致力于在 Sim
.
M
,则函子方法确实有一个优势
我在草图中省略了签名VEC
,因为我的印象是提问者知道在哪里添加它,并且没有问题:)
module type SAMPLER =
sig
type data
val monitor : data -> unit
val monitor' : data list -> unit
end
(* These are created inside Sim. *)
module type DATA =
sig
type data
val show : data -> string
end
(* Note that I am using destructive substitution (:=) to avoid the need
to have a type data declared in the body of MySampler below. If you
use a regular type equality constraint, you need to add a field
"type data = Data.data" to the body. *)
module type SAMPLER_FN =
functor (Data : DATA) -> SAMPLER with type data := Data.data
(* This is the higher-order functor (it takes another functor as an
argument). *)
module Sim (Sampler_fn : SAMPLER_FN) =
struct
(* Corresponds to module "Sim.M" in the question. *)
module Data =
struct
type data = string
let show s = s
end
(* Note that without additional type constraints or rearrangements,
the type data is abstract to Sampler (more precisely, Sampler_fn
is parametric over Data). This means that Sampler_fn can't
analyze values of type data, which is why we need to provide
functions such as Data.show to Sampler_fn for instances of
Sampler_fn to be "useful". If you are trying to avoid this and
are having trouble with these specific constraints, let me
know. The ability to pass types and related values (functions
in this case) to Sampler_fn is the main argument in favor of
using a higher-order functor. *)
module Sampler = Sampler_fn (Data)
let simulate x =
(* Call one monitoring function. *)
Sampler.monitor "hi!";
(* Do some computation and call another monitoring function. *)
Sampler.monitor' ["hello"; "world"]
end
用法:
module MySampler (Data : DATA) =
struct
let monitor data = data |> Data.show |> print_endline
let monitor' data =
data
|> List.map Data.show
|> String.concat " "
|> print_endline
end
module MySim = Sim (MySampler)
let () = MySim.simulate ()
这会打印
hi!
hello world
完整性:
建立在 SAMPLER
的模块)打开和关闭各个方面的监视,并且可以导出任意类型,即使它们仅在模拟器模块内部的某个地方定义。
我这样定义监控(=采样)模块和模块类型:
module type STYPE = sig type t end
module type SSAMPLER = sig
type t
val ev : t React.event
val mon : t -> unit
end
module type SAMPLER_FN = functor (Data : STYPE) -> SSAMPLER
with type t := Data.t
(* stub sampler function for a single one *)
module Never : SAMPLER_FN = functor (Data : STYPE) -> struct
let ev = React.E.never
let mon = ignore
end
(* event primitive generating sampling function *)
module Event : SAMPLER_FN = functor (Data : STYPE) -> struct
let (ev : Data.t React.event), mon' = React.E.create ()
let mon = mon' ?step:None
end
在这里,我使用 React 库生成输出数据流。 React.E.never
事件什么都不做,对应于采样被关闭。然后像这样指定完整的采样配置:
(* the full sampling config *)
module type SAMPLER = sig
val sampler_pos : (module SAMPLER_FN)
val sampler_step : (module SAMPLER_FN)
(* and several more... *)
end
module NoSampling : SAMPLER = struct
let sampler_pos = (module Never: SAMPLER_FN)
let sampler_step = (module Never: SAMPLER_FN)
(* ... *)
end
(* default sampling config *)
module DefaultSampling : SAMPLER = struct
include NoSampling
(* this is only possible when using first class modules *)
let sampler_pos = (module Event : SAMPLER_FN)
end
可以避免第一个 class 模块,但是 DefaultSampling
中方便的包含和覆盖将不被允许。
在模拟库代码中是这样使用的:
module type VEC = sig
type t
val zeropos : t
val wiggle : t -> t
end
module Sim (V:VEC) (Sampler:SAMPLER) = struct
module V = V
module M = struct
type t = { mutable pos : V.t }
val create () = { pos=V.zeropos }
module Sampler_pos = (val Sampler.sampler_pos) (struct type nonrec t = t end)
let update f m = m.pos <- f m.pos
end
module Sampler_b = (val Sampler.sampler_b) (struct type t = int end)
let loop n (running_data:M.t) =
for i = 1 to n do
(* monitor step number: *)
Sampler_b.mon i;
(* monitor current pos: *)
Sampler_pos.mon running_data;
M.update V.wiggle running_data
done
end
此处,采样仿函数应用于模拟循环中的适当位置。 (val ...)
再次是必要的,只是因为第一个 class 模块包装。
最后,在应用程序脚本中,可以这样做:
module Simulator = Sim (V) (DefaultSampling);;
let trace = Simulator.M.Sampler_pos.ev
|> React.E.fold (fun l h -> h :: l) []
|> React.S.hold [];;
let init_m = Simulator.M.create () in
Simulator.loop 100 init_m;;
React.S.value trace;;
最后一行包含循环期间出现的 Simulator.M.t
类型值的累积列表。计步器的监控(一个愚蠢的例子)被关闭。通过制作另一个 SAMPLER
类型的采样函子并以此参数化 Sim
,如果需要,可以进一步自定义监控。