2010-11-01 13 views
9

¿Cómo se podría volver a escribir la coincidencia de patrones anidados, como el ejemplo siguiente, de modo que None se especifique solo una vez? Creo que the Maybe mónada resuelve este problema. ¿Hay algo similar en la biblioteca central F #? O, ¿hay un enfoque alternativo?Evitar la coincidencia de patrones anidados (posiblemente con quizás una mónada)

match a with 
| Some b -> 
    let c = b.SomeProperty 
    match c with 
    | Some d -> 
     let e = d.SomeProperty 
     //and so on... 
    | None ->() 
| None ->() 

Respuesta

12

puede resolver esta utilizando una función de las capacidades: Option.bind

type A = 
    member this.X : B option = Unchecked.defaultof<_> 
and B = 
    member this.Y : С option = Unchecked.defaultof<_> 
and С = 
    member this.Z : string option = Unchecked.defaultof<_> 


let a : A = Unchecked.defaultof<_> 
let v = 
    match 
     a.X 
     |> Option.bind (fun v -> v.Y) 
     |> Option.bind (fun v -> v.Z) with 
    | Some s -> s 
    | None -> "<none>" 

Francamente, dudo que la introducción de la aplicación de pleno derecho 'tal vez' (a través de expresiones de cálculo) aquí puede acortar el código.

EDITAR: el modo de sueño - en

creo que la versión con Option.bind puede hacerse más pequeña si F # tiene una sintaxis más ligera para el caso especial: lambda que se refieren a algún miembro de su argumento :

"123" |> fun s -> s.Length // current version 
"123" |> #.Length // hypothetical syntax 

Ésta es la forma en la muestra puede ser reescrito en Nemerle que ya tiene estas capacidades:

using System; 
using Nemerle.Utility; // for Accessor macro : generates property for given field 

variant Option[T] 
{ 
    | Some {value : T} 
    | None 
} 

module OptionExtensions 
{ 
    public Bind[T, U](this o : Option[T], f : T -> Option[U]) : Option[U] 
    { 
     match(o) 
     { 
      | Option.Some(value) => f(value) 
      | Option.None => Option.None() 
     } 
    } 
} 

[Record] // Record macro: checks existing fields and creates constructor for its initialization 
class A 
{ 
    [Accessor] 
    value : Option[A]; 
} 

def print(_) 
{ 
    // shortened syntax for functions with body -> match over arguments 
    | Option.Some(_) => Console.WriteLine("value"); 
    | Option.None => Console.WriteLine("none"); 
} 

def x = A(Option.Some(A(Option.Some(A(Option.None()))))); 
print(x.Value.Bind(_.Value)); // "value" 
print(x.Value.Bind(_.Value).Bind(_.Value)); // "none" 
+0

Tengo curiosidad por saber cómo manejaría esto si las expresiones de coincidencia operaran en diferentes tipos, por ejemplo, la opción externa y la lista interna ... todo expresión aún devuelve una opción. – Daniel

+0

¿puede dar una muestra hipotética: el código fuente y lo que quiere obtener como resultado? – desco

1

Yo no sugieren esto, pero también se puede resolver con el manejo de excepciones:

try 
    <code that just keeps dotting into option.Value with impunity> 
with 
    | :? System.NullReferenceException -> "None" 

Sólo quería señalar la equivalencia aproximada de control de excepciones a las Quizás/mónadas cualquiera o Option.bind. Por lo general, prefieren que uno de ellos arroje y atrape excepciones.

+0

Mi ejemplo es un poco limitante. También me gustaría saber cómo se podría resolver el caso más general, es decir, la coincidencia anidada donde los tipos no son todos 'option'. – Daniel

+0

No entiendo lo que está buscando para el caso general; quizás puedas publicar un ejemplo en una nueva pregunta. – Brian

+0

¡Puedes hacerlo en una versión de mónada mezclando let y varias firmas de let! (suponiendo que puedan ser desambiguados). – TechNeilogy

5

Me gusta la respuesta de desco; uno siempre debe favorecer las construcciones incorporadas. Pero Fwiw, esto es lo que una versión del flujo de trabajo podría ser similar (si he entendido correctamente el problema):

type CE() = 

    member this.Bind (v,f) = 
    match v with 
     | Some(x) -> f x 
     | None -> None 

    member this.Return v = v 


type A (p:A option) = 

    member this.P 
    with get() = p 


let f (aIn:A option) = CE() { 
    let! a = aIn 
    let! b = a.P 
    let! c = b.P 
    return c.P } 

let x = f (Some(A(None))) 

let y = f (Some(A(Some(A(Some(A(Some(A(None))))))))) 

printfn "Your breakpoint here." 
+5

En realidad, puede usar la construcción de construcción mencionada en su tipo de CE. Al igual que 'member this.Bind (v, f) = Option.bind f v' –

0

Usando Option.maybe de FSharpx:

open FSharpx 
type Pet = { Name: string; PreviousOwner: option<string> } 
type Person = { Name: string; Pet: option<Pet> } 

let pers = { Name = "Bob"; Pet = Some {Name = "Mr Burns"; PreviousOwner = Some "Susan"} } 

Option.maybe { 
    let! pet = pers.Pet 
    let! prevOwner = pet.PreviousOwner 
    do printfn "%s was the previous owner of %s." prevOwner pet.Name 
} 

Salida:

Susan was the previous owner of Mr Burns. 

Pero, p.ej con esta persona, en su lugar, simplemente no hay salida:

let pers = { Name = "Bob"; Pet = None } 
Cuestiones relacionadas