F# Общие инфиксные операторы (fmap, Applicative, bind и т. Д.) Для нескольких типов
Я хотел бы использовать infix fmap (который я определил как <^>) для работы с несколькими типами, такими как Option и Either (пользовательский тип).
Дано:
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
Чтобы заставить Option <^> работать, я расширил модуль:
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
Это почти работает, однако только один может быть использован одновременно. Любой модуль также может быть добавлен к FSharp.Core, но в равной степени вы можете иметь только один или другой.
Я знаю, что это может быть выполнено с двумя пользовательскими типами, скажем, Either и Maybe (опция Haskell), однако я хотел бы придерживаться Option.
Любые предложения приветствуются.
2 ответа
Это не совсем то, что легко представить в 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