2017-03-15 18 views
4

Kleisli組合運算符>=>也被稱爲haskell圈子中的「魚」,在需要組合專門功能的許多情況下可能會派上用場。它的工作方式類似於>>運算符,但不是構成簡單函數'a -> 'b它賦予它們一些特殊屬性,可能最好用'a -> m<'b>表示,其中m是monad-like類型或函數返回值的某個屬性。 在更廣泛的F#社區中可以找到這種做法的證據,例如在Scott Wlaschin的Railway oriented programming (part 2)中作爲返回Result<'TSuccess,'TFailure>類型的函數的組合。在F#中製作魚

理由是那裏的困境,就必須有同樣的魚,我嘗試用bind函數本身參數化規範Kleisli運營商的定義let (>=>) f g a = f a >>= g

let mkFish bind f g a = bind g (f a) 

此需要提醒的是通常一個不該奇妙的作品不會在面向用戶的代碼上釋放特殊的操作符。我可以撰寫功能返回選項...

module Option = 
    let (>=>) f = mkFish Option.bind f 
    let odd i = if i % 2 = 0 then None else Some i 
    let small i = if abs i > 10 then None else Some i 
    [0; -1; 9; -99] |> List.choose (odd >=> small) 
    // val it : int list = [-1; 9] 

...或I可以設計一個功能應用到堆棧的兩個最上面的值和推回結果,而不必引用我操作所述數據結構明確地:

module Stack = 
    let (>=>) f = mkFish (<||) f 
    type 'a Stack = Stack of 'a list 
    let pop = function 
    | Stack[] -> failwith "Empty Stack" 
    | Stack(x::xs) -> x, Stack xs 
    let push x (Stack xs) = Stack(x::xs) 
    let apply2 f = 
     pop >=> fun x -> 
     pop >=> fun y -> 
     push (f x y) 

但令我困擾的是,簽名val mkFish : bind:('a -> 'b -> 'c) -> f:('d -> 'b) -> g:'a -> a:'d -> 'c是沒有意義的。類型變量的順序是混亂的,它過於籠統('a應該是一個函數),而我沒有看到自然的方式來註釋它。 如何在沒有正式函數和monads的情況下抽象出這些,而不必爲每種類型明確定義Kleisli運算符?

回答

4

你不能以自然的方式沒有更高的種類。

魚的簽名應該是這樣的:

let (>=>) (f:'T -> #Monad<'U>``) (g:' U -> #Monad<'V>) (x:'T) : #Monad<'V> = bind (f x) g 

這是當前.NET類型系統不可表示。如上所述,如果您確實想使用通用魚類操作符,則可以使用F#+,它已通過使用靜態約束來定義它。如果你看第五個code sample here你會看到它在不同類型的行動。

當然你也可以定義你自己的,但是有很多東西需要編碼,才能使它在大多數常見場景中正常工作。您可以從庫中獲取代碼,或者如果您想要,我可以編寫一個小的(但有限的)代碼示例。

通用魚在this line中定義。

我覺得在使用操作符時一般來說你會覺得缺乏泛型函數,因爲正如你發現的那樣,你需要打開和關閉模塊。它不像函數那樣以模塊名稱作爲前綴,你也可以使用運算符(類似於Option.(>=>)),但是它違背了使用運算符的全部目的,我的意思是它不再是運算符。

+0

像往常一樣,靜態解析類型參數來拯救!我想這是唯一可能的答案,至少在全球泛型運算符的明顯用例中是這樣。我正在考慮一些更特別的方法,這裏是我的'bind',給我Kleisli--但是然後在定製的運算符定義中闡明函數應用程序並沒有太多的努力。 – kaefer