【问题标题】:How smart is pattern match?模式匹配有多聪明?
【发布时间】:2012-12-14 01:47:50
【问题描述】:

我的程序大部分时间都花在数组模式匹配上,我想知道是否应该重写函数并放弃自动模式匹配。

例如一个很简单的案例

let categorize array =
    match array with
    | [|(1|2);(1|2);(1|2)|] -> 3
    | [|(1|2);(1|2);_|] -> 2
    | [|(1|2);_;_|] -> 1
    | _ -> 0

categorize [|2;1;3|]

在这种情况下,编译器是否会应用最少的比较,通过识别例如第一种情况与第二种情况相同,除了第三个元素。

实际上模式更复杂,预优化的模式匹配可能比完全优化的模式匹配花费更多的时间。

【问题讨论】:

标签: f# pattern-matching


【解决方案1】:

直接来自反射器:

public static int categorize(int[] array)
{
    if ((array > null) && (array.Length == 3))
    {
        switch (array[0])
        {
            case 1:
                switch (array[1])
                {
                    case 1:
                        switch (array[2])
                        {
                            case 1:
                            case 2:
                                goto Label_005C;
                        }
                        goto Label_005A;

                    case 2:
                        switch (array[2])
                        {
                            case 1:
                            case 2:
                                goto Label_005C;
                        }
                        goto Label_005A;
                }
                goto Label_0042;

            case 2:
                switch (array[1])
                {
                    case 1:
                        switch (array[2])
                        {
                            case 1:
                            case 2:
                                goto Label_005C;
                        }
                        goto Label_005A;

                    case 2:
                        switch (array[2])
                        {
                            case 1:
                            case 2:
                                goto Label_005C;
                        }
                        goto Label_005A;
                }
                goto Label_0042;
        }
    }
    return 0;
Label_0042:
    return 1;
Label_005A:
    return 2;
Label_005C:
    return 3;
}

我没有看到任何低效的地方。

【讨论】:

  • 反射器生成array > null?
【解决方案2】:

您的问题中真正缺少的是实际的主题领域。换句话说,您的问题非常笼统(通常,这对 SO 有好处),而针对您的实际问题进行编码可能会以一种优雅的方式解决整个问题。

如果我推断您的问题目前的情况,您只需要既不是1 也不是2 的第一个元素的索引,并且实现很简单:

let categorize arr =
    try
        Array.findIndex (fun x -> not(x = 1 || x = 2)) arr
    with
        | :? System.Collections.Generic.KeyNotFoundException -> Array.length arr

// Usage
let x1 = categorize [|2;1;3|]    // returns 2
let x2 = categorize [|4;2;1;3|]  // returns 0
let x3 = categorize [|1;2;1|]    // returns 3

作为几个免费的好处,您可以获得与数组长度无关且绝对可读的代码。
这是你需要的吗?

【讨论】:

    【解决方案3】:

    你可以写:

    let f (xs: _ []) =
      if xs.Length=3 then
        let p n = n=1 || n=2
        if p xs.[0] then
          if p xs.[1] then
            if p xs.[2] then 3
          else 2
        else 1
      else 0
    

    【讨论】:

      【解决方案4】:

      测试 1

          F#
              let test1 x =
                  match x with
                  | [| 1; 2; 3 |] -> A
                  | [| 1; 2; _ |] -> A
                  | [| 1; _; _ |] -> A
          Decompiled C#
              if (x != null && x.Length == 3)
              {
                  switch (x[0])
                  {
                  case 1:
                      switch (x[1])
                      {
                      case 2:
                          switch (x[2])
                          {
                          case 3:
                              return Program.MyType.A;
                          default:
                              return Program.MyType.A;
                          }
                          break;
                      default:
                          return Program.MyType.A;
                      }
                      break;
                  }
              }
              throw new MatchFailureException(...);
          Decompiled IL
              Code size 107
      

      结论

      1. 模式匹配不会根据 -> 之后的值进行优化。
      2. Pattern Match 能够根据结论 1 找到数组分解的优化方法。
      3. 不完整的模式匹配总是会引发异常,因此添加通配符来捕获缺失的模式并显式引发异常是没有害处的。

      测试 2

          F#
              let test2 x =
                  match x with
                  | [| 1; 2; 3 |] -> A
                  | [| _; 2; 3 |] -> B
                  | [| _; _; 3 |] -> C
          Decompiled C#
              if (x != null && x.Length == 3)
              {
                  switch (x[0])
                  {
                  case 1:
                      switch (x[1])
                      {
                      case 2:
                          switch (x[2])
                          {
                          case 3:
                              return Program.MyType.A;
                          default:
                              goto IL_49;
                          }
                          break;
                      default:
                          switch (x[2])
                          {
                          case 3:
                              break;
                          default:
                              goto IL_49;
                          }
                          break;
                      }
                      break;
                  default:
                      switch (x[1])
                      {
                      case 2:
                          switch (x[2])
                          {
                          case 3:
                              return Program.MyType.B;
                          default:
                              goto IL_49;
                          }
                          break;
                      default:
                          switch (x[2])
                          {
                          case 3:
                              goto IL_58;
                          }
                          goto IL_49;
                      }
                      break;
                  }
                  IL_58:
                  return Program.MyType.C;
              }
              IL_49:
              throw new MatchFailureException(...);
          Decompiled IL
              Code size 185
      

      结论

      1. 模式匹配检查从数组开头到结尾的值。所以它找不到优化的方法。
      2. 代码大小是最佳代码大小的 2 倍。

      测试 3

          F#
              let test3 x =
                  match x with
                  | [| 1; 2; 3 |] -> A
                  | [| 1; 2; a |] when a <> 3 -> B
                  | [| 1; 2; _ |] -> C
          Decompiled C#
              if (x != null && x.Length == 3)
              {
                  switch (x[0])
                  {
                  case 1:
                      switch (x[1])
                      {
                      case 2:
                          switch (x[2])
                          {
                          case 3:
                              return Program.MyType.A;
                          default:
                              if (x[2] != 3)
                              {
                                  int a = x[2];
                                  return Program.MyType.B;
                              }
                              break;
                          }
                          break;
                      }
                      break;
                  }
              }
              if (x != null && x.Length == 3)
              {
                  switch (x[0])
                  {
                  case 1:
                      switch (x[1])
                      {
                      case 2:
                          return Program.MyType.C;
                      }
                      break;
                  }
              }
              throw new MatchFailureException(...);
      

      结论

      1. 编译器不够聪明,无法通过 Guard 检查完整性/重复性。
      2. Guard 使模式匹配产生奇怪的未优化代码。

      测试 4

          F#
              let (| Is3 | IsNot3 |) x =
                 if x = 3 then Is3 else IsNot3
              let test4 x =
                  match x with
                  | [| 1; 2; 3      |] -> A
                  | [| 1; 2; Is3    |] -> B
                  | [| 1; 2; IsNot3 |] -> C
                  | [| 1; 2; _      |] -> D // This rule will never be matched.
          Decompiled C#
              if (x != null && x.Length == 3)
              {
                  switch (x[0])
                  {
                  case 1:
                      switch (x[1])
                      {
                      case 2:
                          switch (x[2])
                          {
                          case 3:
                              return Program.MyType.A;
                          default:
                          {
                              FSharpChoice<Unit, Unit> fSharpChoice = Program.|Is3|IsNot3|(x[2]);
                              if (fSharpChoice is FSharpChoice<Unit, Unit>.Choice2Of2)
                              {
                                  return Program.MyType.C;
                              }
                              return Program.MyType.B;
                          }
                          }
                          break;
                      }
                      break;
                  }
              }
              throw new MatchFailureException(...);
      

      结论

      1. 多种情况下,Active Patterns 编译为 FSharpChoice。
      2. 编译器能够检查活动模式的完整性/重复性,但无法将它们与正常模式进行比较。
      3. 未编译的模式不会被编译。

      测试 5

          F#
              let (| Equal3 |) x =
                 if x = 3 then Equal3 1 else Equal3 0 // Equivalent to "then 1 else 0"
              let test5 x =
                  match x with
                  | [| 1; 2; 3        |] -> A
                  | [| 1; 2; Equal3 0 |] -> B
                  | [| 1; 2; Equal3 1 |] -> C
                  | [| 1; 2; _        |] -> D
          Decompiled C#
              if (x != null && x.Length == 3)
              {
                  switch (x[0])
                  {
                  case 1:
                      switch (x[1])
                      {
                      case 2:
                          switch (x[2])
                          {
                          case 3:
                              return Program.MyType.A;
                          default:
                          {
                              int num = x[2];
                              switch ((num != 3) ? 0 : 1)
                              {
                              case 0:
                                  return Program.MyType.B;
                              case 1:
                                  return Program.MyType.C;
                              default:
                                  return Program.MyType.D;
                              }
                              break;
                          }
                          }
                          break;
                      }
                      break;
                  }
              }
              throw new MatchFailureException(...);
      

      结论

      1. 单例活动模式编译为返回类型。
      2. 编译器有时会自动内联函数。

      测试 6

          F#
              let (| Partial3 | _ |) x =
                 if x = 3 then Some (Partial3 true) else None // Equivalent to "then Some true"
              let test6 x =
                  match x with
                  | [| 1; 2; 3 |] -> A
                  | [| 1; 2; Partial3 true |] -> B
                  | [| 1; 2; Partial3 true |] -> C
          Decompiled C#
              if (x != null && x.Length == 3)
              {
                  switch (x[0])
                  {
                  case 1:
                      switch (x[1])
                      {
                      case 2:
                          switch (x[2])
                          {
                          case 3:
                              return Program.MyType.A;
                          default:
                          {
                              FSharpOption<bool> fSharpOption = Program.|Partial3|_|(x[2]);
                              if (fSharpOption != null && fSharpOption.Value)
                              {
                                  return Program.MyType.B;
                              }
                              break;
                          }
                          }
                          break;
                      }
                      break;
                  }
              }
              if (x != null && x.Length == 3)
              {
                  switch (x[0])
                  {
                  case 1:
                      switch (x[1])
                      {
                      case 2:
                      {
                          FSharpOption<bool> fSharpOption = Program.|Partial3|_|(x[2]);
                          if (fSharpOption != null && fSharpOption.Value)
                          {
                              return Program.MyType.C;
                          }
                          break;
                      }
                      }
                      break;
                  }
              }
              throw new MatchFailureException(...);
      

      结论

      1. 部分活动模式编译为 FSharpOption。
      2. 编译器无法检查部分活动模式的完整性/重复性。

      测试 7

          F#
              type MyOne =
                  | AA
                  | BB of int
                  | CC
              type MyAnother =
                  | AAA
                  | BBB of int
                  | CCC
                  | DDD
              let test7a x =
                  match x with
                  | AA -> 2
              let test7b x =
                  match x with
                  | AAA -> 2
          Decompiled C#
              public static int test7a(Program.MyOne x)
              {
                  if (x is Program.MyOne._AA)
                  {
                      return 2;
                  }
                  throw new MatchFailureException(...);
              }
              public static int test7b(Program.MyAnother x)
              {
                  if (x.Tag == 0)
                  {
                      return 2;
                  }
                  throw new MatchFailureException(...);
              }
      

      结论

      1. 如果联合中的案例超过 3 个,则模式匹配将使用 Tag 属性而不是 is。 (它也适用于多种情况下的活动模式。)
      2. 通常,模式匹配会导致多个 is,这会大大降低性能。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-11-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-10-20
        • 2010-11-26
        相关资源
        最近更新 更多