ocaml中的评估监视器

时间:2015-11-04 14:39:25

标签: 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: 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仍然是免费的;我也不确定如何使类型平等工作。也许我在这里做错了。

2 个答案:

答案 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,可以根据需要进一步自定义监控。