【问题标题】:when should I use as-patterns to identify common sub-expressions?我什么时候应该使用 as-patterns 来识别常见的子表达式?
【发布时间】:2014-07-22 13:42:41
【问题描述】:

我想知道我必须在多大程度上担心在 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 会识别 (2) 处的 (a:as) 和 (1) 处的 (b:bs) 与输入参数相同吗?或者,我应该使用“as”模式,例如:

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

一般来说,GHC 什么时候可以识别数据构造中的常见子表达式,我什么时候需要帮助它?

【问题讨论】:

  • 我一直在使用它们。这清楚地表明您只是按原样使用输入。

标签: haskell pattern-matching ghc


【解决方案1】:

这取决于。如果你编译没有优化,那么不使用 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
            }
        }
    }

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

Here's the full core dump if you're interested。基本上,我首先将行连接在一起以占用更少的空间,然后删除类型签名中不必要的噪音,删除合格的模块名称,所有[Something] 注释,将merge2_$smerge2 之类的东西重命名为merge2_2,删除所有本地输入签名,然后开始使用 Sublime Text 美妙的多光标功能重命名。我从类型名称开始,将它们全部重命名为 a,然后将变量名称重命名为 xyzwvzs(我很有创意,不是吗?),应用了: 运算符中缀,删除了Rec 区域,然后再添加一些样式,我得到了您在上面看到的输出。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-01
    • 2021-09-07
    • 2012-09-22
    • 1970-01-01
    • 2012-07-24
    相关资源
    最近更新 更多