2017-08-17 62 views
3

我想要做的是使用中綴fmap(我定義爲< ^>)爲多種類型工作,如Option和Either(自定義類型)。F#針對多種類型的常見中綴運算符(fmap,applicative,bind等)

考慮:

type Either<'a, 'b> = Left of 'a | Right of 'b 

在代碼中,我希望能夠做到:

let fO (a : int option) = None 
    let fE (a : Either<string,int>) = Left "dummy" 

    let mO = Some 1 
    let mE = Right 1 

    let testO = f0 <^> m0 
    let testE = fE <^> mE 

其中每個(< ^>):

let (<^>) f m = match m with | Some a -> Some <| f a | None -> None 
    let (<^>) f m = match m with | Right a -> Right <| f a | Left a -> Left a 

要獲得選項< ^>工作我已擴展模塊:

namespace Microsoft.FSharp.Core 
    [<AutoOpen>] 
    module Option = 
    let (<^>) f m = match m with | Some a -> Some <| f a | None -> None 

    [<assembly:AutoOpen("Microsoft.FSharp.Core")>] 
    do() 

以及針對:

type Either<'a, 'b> = Left of 'a | Right of 'b with 
     static member (<^>) (f,m) = match m with | Right a -> Right <| f a | Left a -> Left a 

這幾乎工程,但只有一個可以在同一時間使用。 任一模塊也可以附加到FSharp.Core,但同樣你也只能有一個或另一個。

我知道這可以用2個自定義類型完成,比如Either和Maybe(Haskell選項),但是我會堅持使用Option。

歡迎任何和所有建議。

+1

我沒有足夠的經驗來回答您的主要問題,但你是否知道F#的['Choice'型]的(https://msdn.microsoft.com /en-us/visualfsharpdocs/conceptual/core.choice%5B't1,'t2%5D-union-%5Bfsharp%5D)?這是與'Either'等價的內置等價物。或者,如果Left表示「失敗」的情況,而Right表示「成功」的情況,那麼F#等價物就是['Result'類型](https://github.com/fsharp/fslang-design/blob/master/FSharp -4.1/FS-1004-result-type.md),自F#4.1開始提供。 – rmunn

+3

你可能想看看[F#+](https://github.com/gusty/FSharpPlus),它已經這樣做了,儘管''fmap''的運算符是''<< |''( FParsec中使用相同的運算符)。您還將''綁定爲''>> =''和應用程序''''和''<*>''。如果你看源代碼,你會看到它是如何實現的,這是對@TheInnerLight – Gustavo

+1

@Gustavo答案在下面的答案中解釋的技術的改進好點,我已經添加到我的答案。 – TheInnerLight

回答

7

這並不是真的很容易在F#中表現出來,唯一的方法就是使用靜態解析的類型參數,而且它通常不被認爲是慣用的。

對於新的自定義類型,這樣做很容易,但是將其改造爲現有類型會更加複雜。再次支持兩者都稍微困難。

可以繼續進行下去的方法是創建一個輔助型單宗歧視工會與靜態方法硬編碼爲現有類型:

type Functor = Functor 
    with 
    static member FMap (Functor, mapper : 'T -> 'U, opt : Option<'T>) : Option<'U> = 
     Option.map mapper opt 
    static member FMap (Functor, mapper : 'T -> 'U, ch : Choice<'T, _>) : Choice<'U, _> = 
     match ch with 
     |Choice1Of2 v -> Choice1Of2 (mapper v) 
     |Choice2Of2 v -> Choice2Of2 v 

現在你可以使用函數與靜態解析型paramters到根據類型選擇合適的方法:

let inline fmap (f : ^c -> ^d) (x : ^a) = 
    ((^b or ^a) : (static member FMap : ^b * (^c -> ^d) * ^a -> ^e) (Functor, f, x)) 

請注意^b or ^a的條件?這也爲我們提供了一種將此行爲插入到自定義類型中的方法。

type Either<'a, 'b> = Left of 'a | Right of 'b with 
    static member FMap (Functor, f, m) = 
     match m with | Right a -> Right <| f a | Left a -> Left a 

對於運營商的形式,只是定義:

val inline fmap : 
    f:(^c -> ^d) -> x: ^a -> ^e 
    when (Functor or ^a) : (static member FMap : Functor * (^c -> ^d) * ^a -> ^e) 
val inline (<^>) : 
    f:(^a -> ^b) -> x: ^c -> ^d 
    when (Functor or ^c) : (static member FMap : Functor * (^a -> ^b) * ^c -> ^d) 

現在你可以做這種類型的事情與<^>操作:

let inline (<^>) f x = fmap f x 

您有定義的函數最終

let x = (fun x -> x + 1) <^> (Some 1) 
let x' = (fun x -> x + 1) <^> (None) 
let z<'a> : Either<'a, _> = (fun x -> x + 2) <^> (Right 2) 
let z' = (fun x -> x + 2) <^> (Left 5) 

另外,您可以查看F#+,以獲得對這些標準功能抽象的更完整實現。

+0

Thx,真的幫了我很大的忙,我試圖將這個想法擴展到應用,它不是很有效。我如何定義'let inline fmap(f:^ c - >^d)(x:^ a)= ((^ b或^ a):(靜態成員FMap:^ b *(^ c - >^d )*^a - >^e)(Functor,f,x))'爲適用?不知道如何編寫'f:^ c - >^d'來處理上下文中的函數(Option,Either)。 – rbonallo

+0

我的實現似乎使用選項和選擇,但不是任一。 – rbonallo

+1

@rbonallo很難在評論中提供工作實現,但自定義的'Either'實現應該如下所示:'static member Apply(Applicative,fa,x)= match |右f - ><_, _> .FMap(Functor,f,x)|左e - >左e' – TheInnerLight

0

爲了完整起見,最終實施

type Functor = Functor 
    with 
    static member FMap (Functor, mapper : 'T -> 'U, opt : Option<'T>) : Option<'U> = 
     Option.map mapper opt 
    static member FMap (Functor, mapper : 'T -> 'U, ch : Choice<'T, _>) : Choice<'U, _> = 
     match ch with 
     |Choice1Of2 v -> Choice1Of2 (mapper v) 
     |Choice2Of2 v -> Choice2Of2 v 

type Applicative = Applicative 
    with 
    static member Apply (Applicative, mapperInContext : Option<('T -> 'U)>, opt : Option<'T>) : Option<'U> = 
     match mapperInContext with | Some mapper -> Option.map mapper opt | _ -> None 
    static member Apply (Applicative, mapperInContext : Choice<_,_>, ch : Choice<'T,_>) : Choice<'U,_> = 
     match mapperInContext with 
     | Choice1Of2 mapper -> 
      match ch with 
      |Choice1Of2 v -> Choice1Of2 (mapper v) 
      |Choice2Of2 v -> Choice1Of2 v 
     | Choice2Of2 v -> Choice2Of2 v 

let inline fmap (f : ^c -> ^d) (x : ^a) = 
    ((^b or ^a) : (static member FMap : ^b * (^c -> ^d) * ^a -> ^e) (Functor, f, x)) 

let inline applicative (mf : ^f) (x : ^a) = 
    ((^b or ^a) : (static member Apply : ^b * ^f * ^a -> ^e) (Applicative, mf, x)) 

let inline (<^>) f x = fmap f x 

let inline (<*>) m x = applicative m x 

type Either<'a, 'b> = Left of 'a | Right of 'b with 
    static member FMap (Functor, f, m) = 
     match m with | Right a -> Right <| f a | Left a -> Left a 

    static member Apply (Applicative, fa, x) = match fa with | Right f -> Either<_, _>.FMap(Functor, f, x) | Left e -> Left e 
相關問題