2010-11-01 8 views
9

W jaki sposób można dopasować wzorzyste wzorce, takie jak poniższy przykład, aby None został określony tylko raz? I think the Maybe monad rozwiązuje ten problem. Czy jest coś podobnego w bibliotece rdzeniowej F #? Czy istnieje alternatywne podejście?Unikanie zagnieżdżonego dopasowywania wzorców (prawdopodobnie z być może monadą)

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

Odpowiedz

12

można rozwiązać ten problem za pomocą wbudowanych możliwości: 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>" 

Szczerze mówiąc, wątpię, że wprowadzenie pełnoprawnym „być może” wdrożenie (poprzez wyrażeń obliczeniowych) tutaj można skrócić kod.

EDIT: tryb snu - na

myślę, że wersja z Option.bind mogą być mniejsze, jeśli F # ma więcej lekką składnię szczególnym przypadku: lambda, które odnoszą się do jakiegoś członka swojej tezy :

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

ten sposób próbka może być zapisane w nemerle że ma już takich możliwości:

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

Ciekawe, jak sobie z tym poradzisz, jeśli wyrażenia meczowe działają na różnych typach, na przykład opcja zewnętrzna i wewnętrzna lista ... cały wyrażenie nadal zwraca opcję. – Daniel

+0

Czy możesz podać hipotetyczną próbkę: kod źródłowy i co chcesz uzyskać w rezultacie? – desco

1

Nie sugeruję, ale można również rozwiązać go z obsługi wyjątków:

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

Chciałem tylko zwrócić uwagę na szorstką równoważności wyjątkiem obsługi do monad albo może/lub Option.bind. Zazwyczaj wolą jeden z nich do rzucania i łapania wyjątków.

+0

Mój przykład jest nieco ograniczony. Chciałbym również wiedzieć, jak można rozwiązać bardziej ogólny przypadek, czyli zagnieździć pasujące, gdy typy nie są wszystkie 'opcją'. – Daniel

+0

Nie rozumiem, o co nam chodzi w ogólnym przypadku; może możesz wysłać przykład w nowym pytaniu. – Brian

+0

Możesz to zrobić w wersji monad przez zmieszanie let i różnych podpisów niech! (zakładając, że można je ujednoznacznić). – TechNeilogy

5

Podoba mi się odpowiedź desco; zawsze należy faworyzować wbudowane konstrukcje. Ale FWIW, oto co wersja Workflow może wyglądać (jeśli rozumiem problemu poprawnie):

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

W rzeczywistości możesz użyć wspomnianej konstrukcji wbudowanej w typie CE. Podobnie jak "member this.Bind (v, f) = Option.bind f v" –

0

Korzystanie Option.maybe z 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 
} 

wyjściowa:

Susan was the previous owner of Mr Burns. 

ale, na przykład z tą osobą zamiast po prostu nie ma wyjścia:

let pers = { Name = "Bob"; Pet = None } 
Powiązane problemy