2011-03-20 57 views
5

在JavaScript中有一種叫做俄羅斯娃娃模式的模式(這也可以稱爲「一次性」)。基本上,這是一個功能,在某些時候可以用另一種替代。如何在Ocaml中實現俄羅斯娃娃模式?

簡單的例子:

var func = function(){ 
    func = function(){ console.log("subsequent calls call this...");}; 
    console.log("first call"); 
} 

所以第一次調用FUNC它會輸出「第一個電話」和下一個(次及以後),它的打印「後續調用調用這個......」。 (例如,在Scheme中也很容易做到這一點)

我一直在困惑於如何在Ocaml中做到這一點?

編輯:一個解決方案,我想出來的:

let rec func = ref(fun() -> func := (fun() -> Printf.printf("subsequent..\n"));Printf.printf("First..\n"));; 

古稱: FUNC();;有趣的是,如果我在定義中不包含'rec',它就不會調用後續函數......它總是打印'First ...'。

+4

這就是......錯了。 – phooji 2011-03-20 21:32:55

+2

請注意,下面提出的OCaml解決方案與您的JavaScript示例一樣「清潔」,因爲它們封裝了「func」的可變性:第一次調用後,函數被更改爲良好,沒有人可以訪問引用來更改它背部。這是通過將「f」引用局部用於「玩偶」調用來完成的。當然,如果您希望提及的內容仍然可以修改,那也是可以的。 – gasche 2011-03-21 13:59:49

回答

9

這很簡單,但你需要使用副作用。這是一個函數,它以兩個thunk作爲參數,並返回一個新的thunk,第一次調用第一個thunk,而第二個thunk每隔一個時間調用一次。

let doll f1 f2 = 
    let f = ref f1 in 
    (fun() -> 
     let g = !f in 
     f := f2; 
     g()) 

這並不是最佳的,因爲我們會繼續一遍又一遍用相同的值覆蓋裁判。

這是一個稍微好一點的版本,它使用遞歸定義。

let doll f1 f2 = 
    let rec f = ref (fun() -> f := f2;f1()) in 
    (fun() -> !f()) 

所以,現在,你會得到這樣的:

# let f = doll (fun() -> 1) (fun() -> 2);; 
val f : unit -> int = <fun> 
# f();; 
- : int = 1 
# f();; 
- : int = 2 
10

yzzlr答案是很不錯的,但有兩個備註:

它強制功能輸入的類型爲單位。您可以使用多態版本:

let doll f1 f2 = 
    let rec f = ref (fun x -> f := f2; f1 x) in 
    (fun x -> !f x);; 

你可以不使用毛茸茸的遞歸:

let doll f1 f2 = 
    let f = ref f1 in 
    f := (fun x -> f := f2; f1 x); 
    (fun x -> !f x);; 

(更換與突變遞歸是一種常見的伎倆,它實際上可以用來定義固定點,而不使用「 rec「)