![](/img/trans.png)
[英]In Scala Functional Programming, is there an idiomatic way to map with a state?
[英]Is there an idiomatic way to do implicit local state in OCaml?
我想编写一些使用某种本地状态构建东西的代码。 例如,请考虑以下使用本地状态生成顺序整数的代码:
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 *)
()
我很好奇,如果有一种方法,使s
状态参数的内部,makeThings回调隐含的,所以我并不需要一遍又一遍,因此键入它保证所有的uniqueId
电话获得通过相同的状态prameter。 例如,在Haskell中,你可以使用monad和do-notation来结束代码
makeThings $ do
i <- uniqueId
j <- uniqueId
return (i + j)
在Ocaml中,我想到的唯一的事情就是使s
成为一个全局变量(非常不受欢迎)或试图模拟Haskell的monadic接口,我担心这将是很多工作并最终导致缓慢的代码也很丑陋到期缺乏记号。 有没有我没想过的替代方案?
Monads也在OCaml工作。 由于pa_monad_custom语法扩展,您甚至可以使用do-notation。 虽然,在大多数情况下只有一个中缀绑定运算符,即>>=
就足以编写一个花哨的代码。
您的代码看起来像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 *)
你已经拥有的东西略有不同。 而不是使用延续,只需提供一个函数来生成一个新的状态:
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不支持隐式参数。 但是,如果您只需要一个这样的“环境”参数,那么将它添加到所有适当的函数并不是太繁重。
我想你想要达到的目标是:
state
函数f
(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获得
听起来你想要一个全局变量
let currentId = ref 0
let uniqueId () =
incr currentId;
!currentId
您建议全局变量是不合需要的,但您指定的行为(“对uniqueId的所有调用都传递相同的状态参数”)正是全局变量的行为。
如果您担心其他代码访问全局变量,那么就不要在模块的签名( .mli
文件)中公开currentId
。
如果您担心访问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
并且模块系统保护您免受其他地方的代码的currentId
并不困难。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.