在F#中改进我的嵌套匹配

sdg*_*sdh 3 f# pattern-matching

假设我想重复地图查找.

在C#中,我可以使用return"平面"控制流程:

Thing v = null;

if (a.TryGetValue(key, out v)) 
{
    return v;
}

if (b.TryGetValue(key, out v)) 
{
    return v;
}

if (c.TryGetValue(key, out v)) 
{
    return v;
}

return defaultValue;
Run Code Online (Sandbox Code Playgroud)

它有点难看,但很可读.

在F#中,我不太熟悉,我会使用match表达式:

match a.TryGetValue(key) with
| (true, v) -> v
| _ -> 
  match b.TryGetValue(key) with
  | (true, v) -> v
  | _ -> 
    match c.TryGetValue(key) with
    | (true, v) -> v
    | _ -> defaultValue
Run Code Online (Sandbox Code Playgroud)

这感觉不对 - 代码越来越多地与每个地图嵌套.

F#是否提供了"压扁"此代码的方法?

Tom*_*cek 8

您可以稍微更改语义并预先运行所有TryGetValue调用.然后,您只需要一个平面模式匹配,因为您可以同时对所有结果进行模式匹配,并使用或模式(使用写入|)选择成功的第一个:

match a.TryGetValue(key), b.TryGetValue(key), c.TryGetValue(key) with
| (true, v), _, _
| _, (true, v), _
| _, _, (true, v) -> v
| _ -> defaultValue
Run Code Online (Sandbox Code Playgroud)

这会使模式匹配变得平坦,但是您可能正在进行不必要的查找(这可能不是什么大问题,但值得注意的是,这是语义的变化).

另一种选择是使用活动模式 - 您可以定义模式在字典上匹配的参数化活动模式,将键作为输入参数并执行查找:

let (|Lookup|_|) key (d:System.Collections.Generic.IDictionary<_, _>) = 
  match d.TryGetValue(key) with
  | true, v -> Some v
  | _ -> None
Run Code Online (Sandbox Code Playgroud)

现在Lookup <key> <pat>,当它包含<pat>与键匹配的模式值时,您可以编写与字典匹配的pattner <key>.使用此方法,您可以将模式匹配重写为:

match a, b, c with
| Lookup key v, _, _ 
| _, Lookup key v, _ 
| _, _, Lookup key v -> v 
| _ -> defaultValue
Run Code Online (Sandbox Code Playgroud)

F#编译器处理这种情况的方式是它将一个接一个地运行模式并匹配第一个成功的模式 - 所以如果第一个成功,则只执行一次查找.