2014-10-04 54 views
3

我想编写一些代码来构建一些使用本地状态的东西。例如,考虑使用本地状态产生连续整数下面的代码:在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 *) 

() 

我很好奇,如果有一种方法,使该makeThings内s状态参数回调隐式的,所以我就不说了需要反复输入,因此保证所有uniqueId调用都通过相同的状态参数。例如,在哈斯克尔你可以使用单子做,符号与沿

makeThings $ do 
    i <- uniqueId 
    j <- uniqueId 
    return (i + j) 

线OCaml中,浮现在我的脑海里的唯一的东西代码落得正在s一个全局变量(万劫)或者试图模仿Haskell的monadic接口,我担心这会导致很多工作,并且由于缺少标识符而导致代码编写慢也很难看。有没有我没有想到的替代方案?

+0

获得你的问题是有点不明确,espcially你在你的脑海中提到'使SA全球variable',而是在利奥白您的评论你说你不希望全球变化。我想你想达到的是: '1。具有状态的函数f(make_things)。 2.每次调用f时,状态都会被重置 3.但是在一次调用f的过程中,状态可以自动改变。 '我的猜测是正确的吗? – 2014-10-07 16:00:02

回答

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

Monads也在OCaml工作。由于pa_monad_custom语法扩展,您甚至可以有一个符号。虽然在大多数情况下只有一个中缀运算符,即>>=就足以编写一个奇特的代码。

+0

但是人们在实践中是否使用monads来实现这种代码呢?如果你能想到一些这样的项目,链接到它会让我的生活变得更容易:) – hugomg 2014-10-05 00:11:09

+0

通常我们使用monads来进行IO。我个人从未在生产代码中看到过状态monad。我个人的偏好是明确传递状态,并在更改时返回。 – ivg 2014-10-05 01:59:15

1

你的代码看起来像一元的风格+参考奇怪的混合体。如果你想限制你的本地状态只能通过特定的方式发生变化,你应该把他们藏在当地的具体情况:

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 *) 
+0

如果makeBody的回调函数调用其他函数,那么它需要传递'unique_id'给他们,我们又回到了我们开始......我得到的印象是,这只是将状态封装成OO模式(专业人员并对此表示赞同),但并没有解决原来的“线程状态”问题。至于怪异的引用+ monadic风格,这就是为什么我问这个问题:)我认为使用可变状态比通过monads使用虚假状态更清晰,但我是ocaml的新手,所以我知道什么。 – hugomg 2014-10-05 01:29:49

+0

如果你想防止一个'unique_id'函数被滥用多个地方,定义一个更高阶的函数,比如'with_unique_id:((unit - > int) - >'a) - >'a',它的参数需要一个新的函数由'make_unique_id'完成。顺便说一句,如果你想要国家monad的纯粹解决方案,你应该保持纯净。在monadic状态下引用是非常令人困惑的。 – camlspotter 2014-10-07 02:33:31

+0

正如Leo White的回答中所建议的那样,即使将状态设为抽象类型,也正在使用不良参考? – hugomg 2014-10-07 02:42:23

0

这听起来像你想有一个全局变量

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并且模块系统可以保护您免受其他代码的攻击。

+0

我不确定这是否正是我想要的。每次对'makeThings'的调用都应该从0开始生成ID,而不是全局增加的ID(这是因为在我的实际使用情况中,状态是需要在makeThings调用之间清除的事情列表)。另外,如果我使用模块封装状态,有没有办法实例化同一模块的两个实例,还是仅限于单个有状态实例? – hugomg 2014-10-06 13:54:33

+0

您可以使用模块来使用仿函数或一等模块来封装具有多个实例的状态。但在这种情况下它不会带来任何好处,你可能只是传递'updateId'。 – 2014-10-06 20:32:44

+0

我已经添加了一个支持多个实例的不同答案,但它要求您明确地传递一个状态参数。由于OCaml不支持隐式参数,因此您无法避免这种情况。 – 2014-10-06 20:54:27

1

我想你想达到的目的是什么:

  1. 函数f(make_things),其具有state
  2. 每次你打电话f,国家越来越重
  3. 但是,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

相关问题