我想做的是使用中缀fmap(我定义为<^>(来处理多种类型,例如选项和任一(自定义类型(。
鉴于:
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
这几乎有效,但是一次只能使用一个。 一个 Any 模块也可以附加到 FSharp.Core,但同样,您只能有一个或另一个。
我知道这可以通过 2 种自定义类型来完成,比如说要么和也许(Haskell选项(,但我想坚持使用选项。
欢迎任何和所有建议。
这在 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#+,以更完整地嵌入其中许多标准函数抽象。
为了完整性,最终实现
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