2014-10-04 19 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

相關問題