Методы расширения F # Linq для пользовательского типа с использованием запроса linq - PullRequest
1 голос
/ 10 октября 2019

В C # я могу включить монадическую композицию в запросе Linq для пользовательского типа, реализовав методы расширения для Select и SelectMany, например:

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>()));

Третий метод расширения - это то, что включает монадическоесоставление в запросе Linq аналогично liftM функциям в Haskell, например:

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));
}

Однако моя проблема связана с достижением результата в F # путем реализации методов расширения для пользовательского Either тип, чтобы включить монадную композицию в запросе Linq.

Вот мое определение типа 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))))

Однако, как для целесообразности, так и для небольших знаний, я хотел бы знать, как достичь того же результата, что и C# в F#

Возможно ли это?

1 Ответ

3 голосов
/ 10 октября 2019

Синтаксис запроса C # основан на синтаксическом преобразовании, как показывает ваш пример, в F # каждый экземпляр монады представлен связанным классом компоновщика, который реализует требуемые операции, например 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) 
}
...