什么时候应该使用as-patterns来识别常见的子表达式?

时间:2014-07-22 13:42:42

标签: haskell pattern-matching ghc

我想知道在多大程度上我必须担心在Haskell(使用GHC)中微观优化常见的子表达式,特别是在数据解构方面。

例如,请考虑使用此代码合并两个有序列表:

merge :: Ord a => [a] -> [a] -> [a]
merge [] bs = bs
merge as [] = as
merge (a:as) (b:bs) =
  case compare a b of
    LT -> a : merge as (b:bs)  -- (1)
    EQ -> a : merge as bs
    GT -> b : merge (a:as) bs  -- (2)

GHC是否会认识到(1)处的(a:as)和(1)处的(b:bs)与输入参数相同?或者,我应该使用"作为"模式,例如:

merge a'@(a:as) b'@(b:bs) =
  ...
  LT -> a : merge as b'
  ...
  GT -> b : merge a' bs

一般情况下,GHC能否识别数据结构中的常见子表达式,何时需要帮助它?

1 个答案:

答案 0 :(得分:7)

这取决于。如果您在没有优化的情况下进行编译,那么不使用as-patterns肯定会受到惩罚,但如果您编译以下代码

module Test where


merge1 :: Ord a => [a] -> [a] -> [a]
merge1 [] bs = bs
merge1 as [] = as
merge1 (a:as) (b:bs) = case compare a b of
    LT -> a : merge1 as (b:bs)
    EQ -> a : merge1 as bs
    GT -> b : merge1 (a:as) bs


merge2 :: Ord a => [a] -> [a] -> [a]
merge2 [] bs = bs
merge2 as [] = as
merge2 xs@(a:as) ys@(b:bs) = case compare a b of
    LT -> a : merge2 as ys
    EQ -> a : merge2 as bs
    GT -> b : merge2 xs bs

-O2-ddump-simpl,以及很多明智的编辑,重命名变量,剥离元数据和注释,以及其他各种技巧,你可以得到一些东西像

merge2_2 :: Ord a => a -> [a] -> [a] -> [a]
merge2_2 = \ ordInst x y z -> case z of _ {
      [] -> (x:y);
      (w:v) -> case compare ordInst x w of _ {
          LT -> x : merge2_1 ordInst y w v;
          EQ -> x : merge2   ordInst y   v;
          GT -> w : merge2_2 ordInst x y v
        }
    }

merge2_1 :: Ord a => [a] -> a -> [a] -> [a]
merge2_1 = \ ordInst x y z -> case x of _ {
      [] -> (y:z);
      (w:v) -> case compare ordInst w y of _ {
          LT -> w : merge2_1 ordInst v y z;
          EQ -> w : merge2   ordInst v   z;
          GT -> y : merge2_2 ordInst w v z
        }
    }

merge2 :: Ord a => [a] -> [a] -> [a]
merge2 = \ ordInst x y -> case x of wild {
      [] -> y;
      (w:v) -> case y of _ {
          [] -> wild;
          (z:zs) -> case compare ordInst w z of _ {
              LT -> w : merge2_1 ordInst v z zs;
              EQ -> w : merge2   ordInst v   zs;
              GT -> z : merge2_2 ordInst w v zs
            }
        }
    }


merge1_2 :: Ord a => a -> [a] -> [a] -> [a]
merge1_2 = \ ordInst x y z -> case z of _ {
      [] -> (x:y);
      (w:v) -> case compare ordInst x w of _ {
          LT -> x : merge1_1 ordInst y w v;
          EQ -> x : merge1   ordInst y   v;
          GT -> w : merge1_2 ordInst x y v
        }
    }

merge1_1 :: Ord a => [a] -> a -> [a] -> [a]
merge1_1 = \ ordInst x y z -> case x of _ {
      [] -> (y:z);
      (w:v) -> case compare ordInst w y of _ {
          LT -> w : merge1_1 ordInst v y z;
          EQ -> w : merge1   ordInst v   z;
          GT -> y : merge1_2 ordInst w v z
        }
    }

merge1 :: Ord a => [a] -> [a] -> [a]
merge1 = \ ordInst x y -> case x of wild {
      [] -> y;
      (w:v) -> case y of _ {
          [] -> wild;
          (z:zs) -> case compare ordInst w z of _ {
              LT -> w : merge1_1 ordInst v z zs;
              EQ -> w : merge1   ordInst v   zs;
              GT -> z : merge1_2 ordInst w v zs
            }
        }
    }

与diffing工具进行比较后,告诉我这两个定义之间的唯一区别是merge函数名末尾的数字。所以是的,在应用优化之后,GHC可以为您找出这些模式,至少在列表的情况下。但是,我仍然建议使用它们,因为总有边缘情况,如果你使用它们,那么你不必相信编译器做一些复杂的事情。这也意味着即使没有启用优化,您的代码仍然会在那里进行优化。这似乎是一个微小的变化,但如果这个功能最终在内部循环或你正在进行的结构非常大,它可能会产生重大的性能影响。另外,我发现对于具有大量字段的构造函数来说,通常更方便的是有一个名称来引用"构造的"形式。

Here's the full core dump if you're interested。基本上,我开始通过连接线来占用更少的空间,然后删除类型签名中的不必要的噪音,删除合格的模块名称,所有[Something]注释,重命名为merge2_$smerge2之类的内容到{{1删除所有本地类型签名,然后使用Sublime Text的精彩多个游标功能开始重命名。我从类型名称开始,将它们全部重命名为merge2_2,然后将变量名称重命名为axyz,{{1我和w(我是创意,不是吗?),制作了v运算符中缀的所有应用,删除了zs个区域,以及稍后我会得到你在上面看到的输出。