使用 linq 查询的自定义类型的 F# Linq 扩展方法
F# Linq extension methods for custom type using linq query
在 C# 中,我可以通过实现 Select
和 SelectMany
的扩展方法,在 Linq 查询中为自定义类型启用 monadic 组合,例如:
public static Either<L, R2> Select<L, R, R2>(this Either<L, R> @this, Func<R, R2> fn) => @this.Map(fn);
public static Either<L, R2> SelectMany<L, R, R2>(this Either<L, R> @this, Func<R, Either<L, R2>> fn) => @this.FlatMap(fn);
public static Either<L, R2> SelectMany<L, R, R1, R2>(this Either<L, R> @this, Func<R, Either<L, R1>> fn, Func<R, R1, R2> select) => @this.FlatMap(a => fn(a).FlatMap(b => select(a, b).ToEither<L, R2>()));
第三种扩展方法是在类似于 Haskell 中的 liftM
函数的基础上启用 Linq 查询中的单子组合,例如:
Either<L, C> LiftM2(Either<L, A> m1, Either<L, B> m2, Func<A, B, C> f) {
return from a in m1
from b in m2
select Right(f(a, b));
}
然而,我的问题与通过实现自定义 Either
类型的扩展方法以在 Linq 查询中启用 monadic 组合来实现 F# 中的结果有关。
这是我对 Either
类型的定义:
type Either<'l, 'r> =
| Left of 'l
| Right of 'r
首先,我为 map
和 flatmap
添加了一个函数,包括 map
的自定义运算符作为 <!>
和 flatmap
作为 >>=
和=<<
:
[<AutoOpen>]
module Either =
let lmap f e =
match e with
| Left(l) -> Left(f(l))
| Right(r) -> Right(r)
let rmap f e =
match e with
| Left(l) -> Left(l)
| Right(r) -> Right(f(r))
let map f e = rmap f e
let inline (<!>) e f = map f e
let inline (<!) a e = map >> constant
let lflatmap f e =
match e with
| Left(l) -> f(l)
| Right(r) -> Right(r)
let rflatmap f e =
match e with
| Left(l) -> Left(l)
| Right(r) -> f(r)
let flatmap f e = rflatmap f e
let inline (>>=) f e = flatmap f e
let inline (=<<) e f = flatmap f e
let _return r = Right(r);
let fail (l : string) = Left(l);
然后我添加了扩展方法实现;正如我从其他示例中获得的那样:
[<Extension>]
type EitherExtensions() =
[<Extension>]
static member inline Select(e: Either<'l, 'r>, f: 'r -> 's) = map f e
static member inline SelectMany(e: Either<'l, 'r>, f: 'r -> Either<'l, 's>) = flatmap f e
static member inline SelectMany(e: Either<'l, 'r>, f: 'r -> Either<'l, 's>, select: 'r -> 's -> 't) = (f >>= e) =<< (fun s -> Either.Right(select(e, s)))
问题是,当我尝试使用它来实现 liftM
、liftM2
、...功能时,它似乎没有采用这些扩展方法;相反,它使用 System.Linq.IQueryable
的扩展方法,而不是我的 Linq 自定义扩展方法,例如SelectMany
let liftM f m1 = query {
for a in m1 do
select Right(f(a))
}
liftM 的类型解析为:
liftM:
f: a -> b,
m1: System.Linq.IQueryable<'a>
-> System.Linq.IQueryable<Either<'c, 'a>>
而不是:
liftM:
f: a -> b,
m1: Either<'c, 'a>
-> Either<'c, 'b>
我当然可以使用任一模式匹配来实现 liftM
,例如:
let liftM2 f m1 m2 =
match m1, m2 with
| Right(a), Right(b) -> Right(f(a, b));
| Left(a), _ -> Left(a)
| _, Left(b) -> Left(b)
...
...或内联单子组合,例如:
let liftM2 f m1 m2 = m1 =<< (fun a -> m2 =<< (fun b -> Right(f(a, b))))
然而,为了方便和一些知识,我想知道如何在 F#
中实现与 C#
相同的结果
这可能吗?
C# 查询语法基于语法转换,如您的示例所示,在 F# 中,每个 monad 实例都由一个关联的构建器表示 class,该构建器实现所需的操作,例如seq
、async
、query
。您需要创建一个 either
构建器来实现所需的操作。对于您的示例,您只需要一个最小的实现:
type EitherBuilder() =
member x.Bind(e, f) = flatmap f e
member x.Return(value) = _return value
member x.ReturnFrom(e) = e
let either = new EitherBuilder()
那么你可以用它来实现liftM
:
let liftM f m1 = either {
let! a = m1
return (f a)
}
在 C# 中,我可以通过实现 Select
和 SelectMany
的扩展方法,在 Linq 查询中为自定义类型启用 monadic 组合,例如:
public static Either<L, R2> Select<L, R, R2>(this Either<L, R> @this, Func<R, R2> fn) => @this.Map(fn);
public static Either<L, R2> SelectMany<L, R, R2>(this Either<L, R> @this, Func<R, Either<L, R2>> fn) => @this.FlatMap(fn);
public static Either<L, R2> SelectMany<L, R, R1, R2>(this Either<L, R> @this, Func<R, Either<L, R1>> fn, Func<R, R1, R2> select) => @this.FlatMap(a => fn(a).FlatMap(b => select(a, b).ToEither<L, R2>()));
第三种扩展方法是在类似于 Haskell 中的 liftM
函数的基础上启用 Linq 查询中的单子组合,例如:
Either<L, C> LiftM2(Either<L, A> m1, Either<L, B> m2, Func<A, B, C> f) {
return from a in m1
from b in m2
select Right(f(a, b));
}
然而,我的问题与通过实现自定义 Either
类型的扩展方法以在 Linq 查询中启用 monadic 组合来实现 F# 中的结果有关。
这是我对 Either
类型的定义:
type Either<'l, 'r> =
| Left of 'l
| Right of 'r
首先,我为 map
和 flatmap
添加了一个函数,包括 map
的自定义运算符作为 <!>
和 flatmap
作为 >>=
和=<<
:
[<AutoOpen>]
module Either =
let lmap f e =
match e with
| Left(l) -> Left(f(l))
| Right(r) -> Right(r)
let rmap f e =
match e with
| Left(l) -> Left(l)
| Right(r) -> Right(f(r))
let map f e = rmap f e
let inline (<!>) e f = map f e
let inline (<!) a e = map >> constant
let lflatmap f e =
match e with
| Left(l) -> f(l)
| Right(r) -> Right(r)
let rflatmap f e =
match e with
| Left(l) -> Left(l)
| Right(r) -> f(r)
let flatmap f e = rflatmap f e
let inline (>>=) f e = flatmap f e
let inline (=<<) e f = flatmap f e
let _return r = Right(r);
let fail (l : string) = Left(l);
然后我添加了扩展方法实现;正如我从其他示例中获得的那样:
[<Extension>]
type EitherExtensions() =
[<Extension>]
static member inline Select(e: Either<'l, 'r>, f: 'r -> 's) = map f e
static member inline SelectMany(e: Either<'l, 'r>, f: 'r -> Either<'l, 's>) = flatmap f e
static member inline SelectMany(e: Either<'l, 'r>, f: 'r -> Either<'l, 's>, select: 'r -> 's -> 't) = (f >>= e) =<< (fun s -> Either.Right(select(e, s)))
问题是,当我尝试使用它来实现 liftM
、liftM2
、...功能时,它似乎没有采用这些扩展方法;相反,它使用 System.Linq.IQueryable
的扩展方法,而不是我的 Linq 自定义扩展方法,例如SelectMany
let liftM f m1 = query {
for a in m1 do
select Right(f(a))
}
liftM 的类型解析为:
liftM:
f: a -> b,
m1: System.Linq.IQueryable<'a>
-> System.Linq.IQueryable<Either<'c, 'a>>
而不是:
liftM:
f: a -> b,
m1: Either<'c, 'a>
-> Either<'c, 'b>
我当然可以使用任一模式匹配来实现 liftM
,例如:
let liftM2 f m1 m2 =
match m1, m2 with
| Right(a), Right(b) -> Right(f(a, b));
| Left(a), _ -> Left(a)
| _, Left(b) -> Left(b)
...
...或内联单子组合,例如:
let liftM2 f m1 m2 = m1 =<< (fun a -> m2 =<< (fun b -> Right(f(a, b))))
然而,为了方便和一些知识,我想知道如何在 F#
C#
相同的结果
这可能吗?
C# 查询语法基于语法转换,如您的示例所示,在 F# 中,每个 monad 实例都由一个关联的构建器表示 class,该构建器实现所需的操作,例如seq
、async
、query
。您需要创建一个 either
构建器来实现所需的操作。对于您的示例,您只需要一个最小的实现:
type EitherBuilder() =
member x.Bind(e, f) = flatmap f e
member x.Return(value) = _return value
member x.ReturnFrom(e) = e
let either = new EitherBuilder()
那么你可以用它来实现liftM
:
let liftM f m1 = either {
let! a = m1
return (f a)
}