В 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#
Возможно ли это?