4

我想做的是使用中缀 fmap(我将其定义为 <^>)来处理多种类型,例如 Option 和 Either(自定义类型)。

鉴于:

   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

为了让 Option <^> 工作,我扩展了模块:

    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

这几乎可以工作,但是一次只能使用一个。一个 Either 模块也可以附加到 FSharp.Core,但同样你只能拥有一个或另一个。

我知道这可以用 2 种自定义类型来完成,比如 Either 和 Maybe(Haskell 选项),但是我想坚持使用 Option。

欢迎任何和所有建议。

4

2 回答 2

8

这在 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

现在您可以使用具有静态解析类型参数的函数来根据类型选择适当的方法:

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

对于运算符形式,只需定义:

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

你最终定义了函数:

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 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#+,以更完整地实现许多这些标准功能抽象。

于 2017-08-17T13:33:36.593 回答
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 
于 2017-09-12T08:27:30.963 回答