2017-08-17 85 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