2017-07-18 47 views
1

在OCaml中編寫以下代碼的慣用方式具有更好的可讀性?OCaml中沒有where子句的長函數

let big_function arg = 
    let big_helper_fn acc = function 
     | p -> ... 
     ... 
     ... 
     ...  foo(arg) 
     ... 
     ... 
     | _ -> ... 
    in 
    let small_helper_1 a b = 
    ... 
    ... 
    in 
    let small_helper_2 a b = 
    ... 
    ... 
    in 

    fold big_function default_acc 
    %> small_helper_1 aa1 
    %> small_helper_2 aa2 

吊裝內功能以外可以有兩個原因是不期望的:

  • 人們可能需要通過幾個參數明確地,而不是直接訪問(用上面foo(arg)示出)。如果有更多的參數,這會變得很麻煩,比如​​需要3個參數,big_helper_fn使用所有參數,並且累加器也是3元素的元組。
  • 輔助函數在比所需範圍更大的範圍內變得不必要地可見。當人們僅僅撇開模塊時,他們可能會分心,因爲與重要的​​相同的壓痕深度。

如果OCaml有第一類where子句,這不會是一個問題。我確實找到了一個PPX和另一個Github repo

請按照您的答案建議的方法,提供一本書/風格指南/大型項目的官方文檔/名稱的參考。

編輯:我用這個代碼示例遇到的麻煩是可讀性受損,因爲​​的實際定義與頂部的原始let big_function ...語句分開很多。所以我正在尋找一種更可讀的慣用選擇。

+0

什麼的(%>)運算符嗎? – user3240588

+1

@ user3240588,它構成功能從左至右(而不是通常的從右到左)。 – theindigamer

+0

我真的不明白這個問題。你的代碼已經在OCaml中有效並且相當習慣。 「where」子句只會顛倒聲明的順序(這在OCaml中是非常不慣用的)。 – Drup

回答

1

你的代碼看起來已經很OCamlish了。許多大型項目都是以這種方式編寫的:請參閱OCaml編譯器實現本身。我喜歡在Haskell中使用where,但是我個人反對使用非純語言的任意where,因爲它可能會使副作用的排序非常混亂,這可能會導致很難修復的錯誤。將where的定義僅限制在非擴展表達式中是可以的,但我不確定您提到的PPX是否執行這樣的檢查。

我從來沒有做過這個,但你可以首先把「主」任務使用let rec

let big_function arg = 
    let rec go() = 
    fold big_helper_fn default_acc 
    %> small_helper_1 aa1 
    %> small_helper_2 aa2 
    and big_helper_fn acc = function 
    .. 
    and small_helper_1 a b = 
    .. 
    and small_helper_2 a b = 
    .. 
    in 
    go() 

或者,你可以使用本地模塊:

module BigFunctionHelpers(A : sig val arg : t end) = struct 
    open A 

    let big_helper_fn acc = function ... foo(arg) ... 

    let small_helper_1 a b = ... 

    let small_helper_2 a b = ... 
end 

let big_function arg = 
    let module H = BigFunctionHelpers(struct let arg = arg end) in 
    let open H in 
    fold big_helper_fn default_acc 
    %> small_helper_1 aa1 
    %> small_helper_2 aa2 

我這樣做有時候在用父函數提取許多參數的本地定義時。

+0

我問這個的原因是因爲我寫的代碼似乎有點難以閱讀,因爲'big_function'的實際定義與原來的'let in'語句分開了很多。你的第二個解決方案看起來不錯,但它又不像「where」那樣在語法上重量輕。 – theindigamer

1

很難說不知道輔助函數中發生了什麼,但一般來說可以將它們作爲頂級函數提取。這有幾個優點:

  • 更容易閱讀
  • 它減少的範圍變量的數量在每一點上,減少指的是錯誤的變量的風險(可以摺疊/累加器發生)
  • 這使得它可以測試內部函數

有一些缺點,如你所說:

  • 將有更多頂級的功能,所以不可能有命名衝突(你可以使用一個模塊來幫助與)
  • 你需要傳遞更多變量明確,而不是通過關閉。我會說這是一個優勢,因爲這會使耦合更加明顯。這是傳遞較少數據的機會(例如,一個字段而不是整個記錄)。
+0

關於測試的觀點很有用;我沒有想到這一點。雖然我不確定第二個優點,如果你無論如何都通過父範圍隱式地傳遞參數,它並不重要。 OTOH,如果外部函數的參數沒有被直接使用,那麼我同意最好將較小的函數移出。 – theindigamer