我想编写一些使用某种本地状态构建东西的代码。例如,请考虑以下使用本地状态生成顺序整数的代码:
type state = int ref
let uniqueId : (state -> int) =
fun s -> incr s; !s
let makeThings : ((state -> 'a) -> 'a) =
fun body -> body (ref 0)
let () =
let x1 = makeThings(fun s ->
let i = uniqueId s in (* 1 *)
i
) in
print_int x1; print_newline (); (* Prints 1 *)
(* Each makeThings callback gets its own local state.
The ids start being generated from 1 again *)
let x2 = makeThings(fun s ->
let i = uniqueId s in (* 1 *)
let j = uniqueId s in (* 2 *)
i + j
) in
print_int x2; print_newline (); (* Prints 3 *)
()
我很好奇是否有办法在makeThings回调中隐式设置s
状态参数,这样我就不需要反复输入它,所以它保证所有{{} 1}}调用get传递相同的状态参数。例如,在Haskell中,您可以使用monad和do-notation来结束代码
uniqueId
在Ocaml中,我想到的唯一事情就是让makeThings $ do
i <- uniqueId
j <- uniqueId
return (i + j)
成为一个全局变量(非常不受欢迎)或试图模拟Haskell的monadic接口,我担心这将是一项很多工作并最终由于缺乏写法,慢代码也很难看。有没有我没想过的替代方案?
答案 0 :(得分:3)
Monads也在OCaml工作。由于pa_monad_custom语法扩展,您甚至可以使用do-notation。虽然,在大多数情况下只有一个中缀绑定运算符,即>>=
就足以编写一个奇特的代码。
答案 1 :(得分:1)
你的代码看起来像monadic风格+引用的奇怪混合...如果你想限制你的本地状态只能通过特定方式改变,你应该隐藏它们在本地环境中:
let make_unique_id init =
let s = ref (init - 1) (* :-) *) in
fun () ->
incr s;
!s
s
现在隐藏在闭包中。因此,您可以创建彼此独立的计数器:
let () =
let x1 =
let unique_id = make_unique_id 1 in
let i = unique_id () in
i
in
print_int x1; print_newline (); (* Prints 1 *)
let x2 =
let unique_id = make_unique_id 1 in
let i = unique_id () in (* 1 *)
let j = unique_id () in (* 2 *)
i + j
in
print_int x2; print_newline () (* Prints 3 *)
答案 2 :(得分:1)
你已经拥有的东西略有不同。不要使用延续,只需提供一个生成新状态的函数:
module State : sig
type t
val fresh : unit -> t
val uniqueId : t -> int
end = struct
type t = int ref
let fresh () = ref 0
let uniqueId s = incr s; !s
end
let () =
let x1 =
let s = State.fresh () in
let i = State.uniqueId s in
i
in
print_int x1;
print_newline () (* Prints 1 *)
let () =
let x2 =
let s = State.fresh () in
let i = State.uniqueId s in (* 1 *)
let j = State.uniqueId s in (* 2 *)
i + j
in
print_int x2;
print_newline () (* Prints 3 *)
这是处理编译器环境的常用方法,看起来很像你想要做的事情。它不会隐式地通过状态,因为OCaml不支持隐式参数。但是,如果您只需要一个这样的“环境”参数,那么将它添加到所有适当的函数中并不会太繁重。
答案 3 :(得分:1)
我想你想要达到的目标是:
f
的函数state
(make_things)。 f
时,状态都会重置f
的一次通话中,状态可以自动更改如果我是正确的,那么我们不需要Monald,相反,我们可以使用备忘录。
let memo_incr_state () =
let s = ref 0 in
fun() -> s := !s + 1; !s
let make_things f =
let ms = memo_incr_state() in
f ms
let f1 ms =
let i = ms() in
let j = ms() in
i+j
let x1 = make_things f1 (* x1 should be 3 *)
基本思路是我们用thunk来记住状态。
有关记忆的更多知识可以从https://realworldocaml.org/v1/en/html/imperative-programming-1.html#memoization-and-dynamic-programming
获得答案 4 :(得分:0)
听起来你想要一个全局变量
let currentId = ref 0
let uniqueId () =
incr currentId;
!currentId
您建议全局变量是不合需要的,但您指定的行为(&#34;对uniqueId的所有调用都传递相同的状态参数&#34;)正是全局变量的行为。
如果您担心访问全局变量的其他代码,则只需在模块的签名(currentId
文件)中公开.mli
。
如果您担心访问currentId
的同一模块中的其他代码,那么您可以通过将其置于uniqueId
的定义中来限制其范围:
let uniqueId =
let currentId = ref 0 in
fun () ->
incr currentId;
!currentId
或创建一个子模块,该子模块不会在签名中公开currentId
:
module M : sig
val uniqueId : unit -> int
end = struct
let currentId = ref 0
let uniqueId () =
incr currentId;
!currentId
end
include M
就个人而言,我会选择第一个解决方案(.mli
文件隐藏的全局变量)。确保同一模块中的其他代码不会滥用currentId
并且模块系统保护您免受其他代码的攻击并不难。