我想要实现的是类似于日志记录工具,但用于监视和流式传输模拟中的任意数据。这是简化的情况:
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
IE中。我在那里放了一个存根监控功能参考。在实际的应用程序脚本中,我可以分配一个例如将数据值累积到列表或某些列表中。它有效。
所以问题是:这是实现我想要的最好/最低开销/最好的方式吗?
这种方法似乎有点hackish,我宁愿使用模块系统而不是函数指针。但是,要流式传输的数据类型仅在仿函数Sim
内定义。因此,在Sampler
之外的另一个模块Sim
中创建监视功能并由此参数化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
在??
我不知道如何指定Sampler的输出签名,因为输入签名VEC
仍然是免费的;我也不确定如何使类型平等工作。也许我在这里做错了。
答案 0 :(得分:2)
正如评论中所讨论的,你可以使用更高阶的函数来做这样的事情(而不是求助于更高阶的函子):
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
。上面的代码打印foo
5次。请注意,您的monitor
函数仍然可以来自Sampler
模块,在实例化Sim
时,您无需传递整个模块。
编辑:如果您仍想宣布更高阶的仿函数,请按以下步骤操作(...)
编辑2 :更改了给出附加信息的示例,即高阶仿函数的原因是要调用多个监视函数。请注意,在这种情况下,除了高阶函子之外,还有其他解决方案:您可以将函数分组到记录中,并将记录传递给loop
。与此类似,您可以传递一流的模块。或者,您可以创建一个函数,该函数采用变量类型,其案例指示调用监视函数的阶段,并携带与每个阶段关联的数据。您也可以使用类,但我不推荐它。但是,如果您致力于在M
内声明Sim
,那么仿函数方法确实有优势。
我已经从草图中省略了签名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
答案 1 :(得分:0)
为了完整性:
基于antron's answer的仿函数部分,这就是我目前正在使用的部分。它仍然有点涉及,也许它可以更简洁,但它有一些很好的优点。即:可以在集中的位置(类型为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
这里,采样仿函数应用于仿真循环中的适当位置。 (val ...)
只是因为第一个类模块包装才需要。
最后,在应用程序脚本中,可以执行此操作:
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
,可以根据需要进一步自定义监控。