【问题标题】:F# Linq extension methods for custom type using linq query使用 linq 查询的自定义类型的 F# Linq 扩展方法
【发布时间】:2019-10-09 21:15:56
【问题描述】:

在 C# 中,我可以通过实现 SelectSelectMany 的扩展方法,在 Linq 查询中为自定义类型启用一元组合,例如:

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 查询中启用单子组合来实现 F# 中的结果有关。

这是我对Either 类型的定义:

type Either<'l, 'r> =
  | Left of 'l
  | Right of 'r 

首先,我为mapflatmap 添加了一个函数,包括将map 的自定义运算符作为&lt;!&gt;flatmap 作为&gt;&gt;==&lt;&lt;

[<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)))

问题是当我尝试使用它来实现liftMliftM2、...功能时,它似乎没有采用这些扩展方法;相反,它使用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# 相同的结果

这可能吗?

【问题讨论】:

  • 您是在尝试扩展查询表达式,还是只想为您的Either 类型提供一元表达式?在这种情况下,您应该实现一个实现操作的computation expression builder,然后您可以执行例如let liftM f m1 = either { let! a = m1; return (f a) }
  • 在 C# 中,扩展方法的上述实现在 Linq 查询中按预期工作,无需任何额外实现。 F# 对我来说是新事物,因此很可能我没有使用正确的语法来访问 Either 类型的三个自定义 Linq 查询扩展。据我所知,query 将允许我输入 Linq 查询;然而,这似乎只适用于 IQueryable。 F# 是否没有像在 C# 中那样无缝实现 Linq 扩展方法。 F# 中是否需要自定义表达式生成器?

标签: linq f# extension-methods monads


【解决方案1】:

如您的示例所示,C# 查询语法基于语法转换,在 F# 中,每个 monad 实例由一个关联的构建器类表示,该构建器类实现所需的操作,例如seqasyncquery。您需要创建一个实现所需操作的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) 
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-11-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-08-04
    • 1970-01-01
    • 2012-09-09
    • 1970-01-01
    相关资源
    最近更新 更多