ocaml中的评价监视器
我试图实现的功能类似于日志记录功能,但用于监控和流式传输运行模拟中的任意数据。以下是简化的情况:ocaml中的评价监视器,ocaml,functor,Ocaml,Functor,我试图实现的功能类似于日志记录功能,但用于监控和流式传输运行模拟中的任意数据。以下是简化的情况: 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:
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
模拟循环时,在?
处,我可能希望“点击”数据并将其累积。其他时候,我只想让它运行并以最小的开销禁用数据流——?
处于一个紧密的循环中。因此,我希望流媒体能够以低成本进行配置
我现在得到的是:
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
我在那里放了一个存根监控函数引用。在实际的应用程序脚本中,我可以分配一个函数,例如,将数据值累加到一个列表或类似的列表中。它起作用了
所以问题是:这是实现我想要的最好/最低开销/最好的方法吗
这种方法似乎有点老土,我宁愿使用模块系统而不是函数指针。但是,要传输的数据类型仅在functorSim
中定义。因此,在Sim
之外的另一个模块Sampler
中实现监控功能,并由此对Sim
进行参数化,似乎不方便,并且/或者需要重复代码或递归模块。我试过了,但我不能使所有类型都相等
编辑:下面是它在没有函数引用的情况下的大致尝试:
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
在
?
我不知道如何指定采样器的输出签名,因为输入签名VEC
仍然是免费的;另外,我也不确定如何使类型相等工作。也许我在这里做错了。如评论中所述,您可以使用高阶函数(而不必求助于高阶函子)执行类似操作:
用法:
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
可以避免使用一级模块,但不允许在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
最后一行包含循环期间发生的
Simulator.M.t
类型的累积值列表。步进计数器的监控(一个愚蠢的例子)被关闭。通过创建另一个类型为SAMPLER
的采样函子,并以此参数化Sim
,如果需要,可以进一步自定义监控。当尝试使类型相等时,您是否将与类型约束一起使用?是,请参阅问题末尾的编辑。在您的问题中,它表示要流式传输的类型仅在Sim
中定义。但是,该类型(M.data
)等于V.t
。这是一种简化,还是仅仅是关于使M.data
等于V.t
在Sampler
中的问题?现在,如果Sampler
定义为is,则它无法处理(参数化除外)除V
中的类型以外的任何类型。同时,您是否考虑过将“Sampler”函数简单地传递给循环
,可能作为可选参数?并且将循环转换为类似于前面的注释的折叠:这是一个(n过)简化。事实上,在Sim.M中,一个新的记录类型由一个等于V.t的字段构成。所以,让采样器只依赖于V的天真想法应该注定要失败。但是递归模块很可怕,或者更准确地说,我会发现手工定义Sim的完整输出sig非常麻烦。Sim卡在实际代码中非常长。感谢您制作自我包含的示例!我不确定我是否理解高阶函子解。以下是我的想法:SAMPLER不知道任何依赖于VEC的类型。但这不是一个问题,因为监视器可能会产生副作用,例如,将向量t存储到全局列表中;采样器允许这样做。VEC.t需要从关闭中恢复。现在Sim是一个高阶函子;这是否意味着Sim卡的主体中有V?如果是,怎么做?我想我不知道如何编写测试,以便monitor实际将一些VEC.t(list)导出到外部。无论如何,通过monitor函数的解决方案可能是可行的。唯一缺少的是,没有一个地方可以将所有“监控设置”很好地捆绑在一起——在实际问题中,有许多不同的监控功能。这将需要在应用程序脚本中处理。上面的高阶函子示意图只关注声明高阶函子的语法。我将编辑它(大约一两个小时后),使其更接近我认为您的情景,也许它将回答您的一些问题。Sim
是一个高阶函子,这意味着它有另一个函子Sampler\u fn
,作为参数,因此它可以将它应用于其嵌套模块M
–然后,Sampler\u fn
的主体可以依赖于这些模块,包括声明依赖于M
中类型的监视函数。至于高阶函数的捆绑,我仍然不确定是什么阻止了您这样做,或者使用高阶函子在这方面会获得什么好处。您仍然可以声明您的监视乐趣
(* 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
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
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;;