【问题标题】:F# Common infix operators (fmap, applicative, bind, etc) for multiple typesF# 多种类型的通用中缀运算符(fmap、applicative、bind 等)
【发布时间】:2018-01-25 15:49:19
【问题描述】:

我想做的是使用中缀 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

为了让选项 工作,我扩展了模块:

    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。

欢迎提出任何建议。

【问题讨论】:

  • 我没有足够的经验来回答你的主要问题,但你知道 F# 的 Choice type 吗?它是 Either 的内置等效项。或者,如果 Left 代表“失败”案例,Right 代表“成功”案例,则 F# 等价物是 Result type,自 F# 4.1 起可用。
  • 您可能想看看 F#+ 已经这样做了,尽管 fmap 的运算符是 &lt;&lt;|(在 FParsec 中使用相同的运算符)。您还拥有bind&gt;&gt;= 以及应用程序&lt;!&gt;&lt;*&gt;。如果您查看源代码,您会看到它是如何实现的,这是对@TheInnerLight 下面的答案中解释的技术的改进
  • @Gustavo 好点,我已将其添加到我的答案中。

标签: .net f# infix-notation infix-operator


【解决方案1】:

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

现在您可以使用&lt;^&gt; 运算符来执行此类操作:

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

【讨论】:

  • 谢谢,真的很有帮助,我正在尝试将这个想法扩展到应用程序,但它并不完全有效。我将如何为应用程序定义let inline fmap (f : ^c -&gt; ^d ) (x : ^a) = ((^b or ^a) : (static member FMap : ^b * ( ^c -&gt; ^d ) * ^a -&gt; ^e ) (Functor, f, x))?不知道如何编写f : ^c -&gt; ^d 来处理上下文中的函数(Option、Either)。
  • 我的实现似乎可以使用 Option 和 Choice 但不是 Either。
  • @rbonallo 很难在评论中提供有效的实现,但自定义的Either 实现应该如下所示:static member Apply (Applicative, fa, x) = match fa with | Right f -&gt; Either&lt;_, _&gt;.FMap(Functor, f, x) | Left e -&gt; Left e
  • 为什么必须将 Functor 类型传递给 FMap 实现。这是否与解决内联方法约束有关?
  • @ThomasDevries 是的,我们只是在利用 ^a or ^b 语法并允许 ^b 被限制为 Functor 类型,因此我们解决了正确的类型。
【解决方案2】:

为了完整性,最终实现

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 

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-01-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多