2010-11-01 53 views
9

如何重寫嵌套模式匹配(如以下示例),以便None僅指定一次?我認爲 Maybe monad解決了這個問題。在F#核心庫中有類似的東西嗎?或者,有沒有其他方法?避免嵌套模式匹配(可能與單子)

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

回答

12

就可以解決這個使用內置的功能: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>" 

坦率地說,我對此表示懷疑,引入全面的「也許」執行(通過計算表達式),在這裏可以縮短代碼。

編輯:夢模式 - 在

我覺得版本Option.bind可以做得更小,如果F#有特殊情況更輕量級語法:拉姆達引用它的參數的一些成員:

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

這是怎麼樣在Nemerle被改寫已經具有了這樣的能力:

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

我很好奇你如何處理這個如果匹配表達式在不同的類型上運行,例如外部的一個選項和內部的列表......整個表達式仍然返回一個選項。 – Daniel 2010-11-01 21:38:15

+0

你能給出一個假設的例子:源代碼和你想得到什麼結果? – desco 2010-11-01 22:06:05

1

我不建議這一點,但你也可以使用異常處理解決它:

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

我只是想指出的異常處理的可能/無論是單子或Option.bind粗糙的等價。通常更喜歡其中之一來拋出和捕捉異常。

+0

我的例子有點侷限性。我也想知道更通用的情況如何解決,也就是嵌套匹配,其中類型不是全部「選項」。 – Daniel 2010-11-01 21:42:04

+0

我不明白你一般情況下的情況;也許你可以在新問題中發表一個例子。 – Brian 2010-11-01 22:12:17

+0

你可以通過混合let和let的各種簽名在monad版本中完成! (假設他們可以被消歧)。 – TechNeilogy 2010-11-01 22:13:20

5

我喜歡desco的回答;人們應該總是喜歡內置的結構。但FWIW,這裏有一個工作流程的版本可能看起來像什麼(如果我理解正確的問題):

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

實際上,您可以在CE類型中使用提到的內置結構。就像'member this.Bind(v,f)= Option.bind f v' – 2010-11-01 22:37:20

0

從FSharpx使用Option.maybe

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 
} 

輸出:

Susan was the previous owner of Mr Burns. 

但是,例如與此人相反,只是沒有輸出:

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