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 
Другие вопросы по тегам