簡體   English   中英

是否有一種慣用的方法在OCaml中進行隱式本​​地狀態?

[英]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不支持隱式參數。 但是,如果您只需要一個這樣的“環境”參數,那么將它添加到所有適當的函數並不是太繁重。

我想你想要達到的目標是:

  1. 具有state函數f (make_things)。
  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獲得

聽起來你想要一個全局變量

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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM