2017-08-17 8 views
3

Ce que je voudrais faire est d'utiliser infixe fmap (que j'ai défini comme < ^>) pour travailler pour plusieurs types tels que Option et Sither (personnalisé type).F # Opérateurs infixes communs (fmap, applicative, bind, etc) pour plusieurs types

Étant donné:

type Either<'a, 'b> = Left of 'a | Right of 'b 

Dans le code, je voudrais être en mesure de le faire:

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 

Où (< ^>) pour chacun:

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 

Pour obtenir la Option < ^> pour travailler j'ai prolongé le module:

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

Et pour un ou l'autre:

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 

Cela fonctionne presque, mais un seul peut être utilisé à la fois. Un module Sither peut également être ajouté à FSharp.Core, mais vous ne pouvez en avoir qu'un seul ou l'autre.

Je suis conscient que cela peut être complété avec 2 types personnalisés, disons Soit et Peut-être (option Haskell), je voudrais cependant rester avec Option.

Toutes les suggestions sont les bienvenues.

+1

Je n'ai pas assez d'expérience pour répondre à votre question principale, mais savez-vous de F # 's [de type 'Choice'] (https://msdn.microsoft.com /en-us/visualfsharpdocs/conceptual/core.choice%5B't1,'t2%5D-union-%5Bfsharp%5D)? C'est l'équivalent intégré à 'Sither'. Ou si Left représente un cas "d'échec" et Right représente un cas de "succès", alors l'équivalent F # est le type ['Result'] (https://github.com/fsharp/fslang-design/blob/master/FSharp -4.1/FS-1004-result-type.md), disponible depuis F # 4.1. – rmunn

+3

Vous pouvez jeter un oeil à [F # +] (https://github.com/gusty/FSharpPlus) qui le fait déjà, bien que l'opérateur pour '' fmap'' soit '' << | '' (le même opérateur est utilisé dans FParsec). Vous avez aussi '' bind'' comme '' >> = '' et les applicatifs '' '' et '' <*> ''. Si vous regardez le code source, vous verrez comment il est mis en œuvre, c'est un raffinement de la technique expliquée dans la réponse ci-dessous par @TheInnerLight – Gustavo

+1

@Gustavo Bon point, j'ai ajouté cela à ma réponse. – TheInnerLight

Répondre

7

Ce n'est pas vraiment quelque chose de facile à représenter en F #, la seule façon d'y parvenir est d'utiliser des paramètres de type résolus statiquement et ce n'est généralement pas considéré comme idiomatique. Cela est assez facile pour les nouveaux types personnalisés, mais la mise à niveau vers des types existants est plus complexe. Soutenir les deux est un peu plus difficile à nouveau.

La façon dont vous pouvez procéder est de créer un type d'aide d'un seul cas de discrimination union avec des méthodes statiques codées en dur pour les types existants:

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 

Maintenant, vous pouvez utiliser une fonction avec paramters de type statique résolus à choisissez la méthode appropriée basée sur le type:

let inline fmap (f : ^c -> ^d) (x : ^a) = 
    ((^b or ^a) : (static member FMap : ^b * (^c -> ^d) * ^a -> ^e) (Functor, f, x)) 

Notez la condition ^b or ^a? Cela nous donne également une méthode pour insérer ce comportement dans les types personnalisés.

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 

Pour formulaire opérateur, définissez simplement:

let inline (<^>) f x = fmap f x 

Vous vous retrouvez avec des fonctions définies:

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) 

Maintenant, vous pouvez faire ce genre de chose avec l'opérateur <^>:

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) 

Vous pouvez également jeter un oeil à F#+ pour une implentation plus complète de beaucoup de ces abstractions fonctionnelles standard.

+0

Thx, a vraiment aidé, j'essaie d'étendre cette idée à l'application et il ne fonctionne pas tout à fait. Comment définir 'let inline fmap (f:^c ->^d) (x:^a) = ((^ b ou^a): (membre statique FMap:^b * (^ c ->^d) *^a ->^e) (Functor, f, x)) 'pour l'applicatif? Je ne sais pas comment composer 'f:^c ->^d' pour gérer les fonctions en contexte (Option, Soit). – rbonallo

+0

Mon implémentation semble fonctionner avec Option et Choice mais pas Either. – rbonallo

+1

@rbonallo Difficile de fournir une implémentation fonctionnelle dans un commentaire mais l'implémentation personnalisée 'Either' devrait ressembler à ceci pour appliquer:' static member Appliquer (Applicative, fa, x) = correspondre fa avec | Droite f -> Soit <_, _> .FMap (Functor, f, x) | Gauche e -> Gauche e' – TheInnerLight

0

Pour être complet, la mise en œuvre éventuelle

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