我应该é¿å…在Haskell中构建å—?

时间:2015-10-03 12:35:23

标签: performance haskell constructor

在从Haskell for Great Good读å–剪辑时,我å‘现了以下情况:

treeInsert :: (Ord a) => a -> Tree a -> Tree a  
treeInsert x EmptyTree = singleton x  
treeInsert x (Node a left right)   
  | x == a = Node x left right  
  | x < a  = Node a (treeInsert x left) right  
  | x > a  = Node a left (treeInsert x right)

如果我们在x == aæ—¶é‡æ–°ä½¿ç”¨ç»™å®šçš„树,那么性能会更好å—?

treeInsert :: (Ord a) => a -> Tree a -> Tree a  
treeInsert x EmptyTree = singleton x  
treeInsert x all@(Node a left right)   
  | x == a = all  
  | x < a  = Node a (treeInsert x left) right  
  | otherwise  = Node a left (treeInsert x right)

在现实生活中的编ç ä¸­ï¼Œæˆ‘该怎么办?返回åŒæ ·çš„东西有什么缺点å—?

5 个答案:

答案 0 :(得分:6)

è®©æˆ‘ä»¬çœ‹çœ‹æ ¸å¿ƒï¼ ï¼ˆè¿™é‡Œæ²¡æœ‰ä¼˜åŒ–ï¼‰

  

$ ghc-7.8.2 -ddump-simpl wtmpf-file13495.hs

相关的区别是第一个版本(没有all@(...))有

                case GHC.Classes.> @ a_aUH $dOrd_aUV eta_B2 a1_aBQ
                of _ [Occ=Dead] {
                  GHC.Types.False ->
                    Control.Exception.Base.patError
                      @ (TreeInsert.Tree a_aUH)
                      "wtmpf-file13495.hs:(9,1)-(13,45)|function treeInsert"#;
                  GHC.Types.True ->
                    TreeInsert.Node
                      @ a_aUH
                      a1_aBQ
                      left_aBR
                      (TreeInsert.treeInsert @ a_aUH $dOrd_aUV eta_B2 right_aBS)

é‡æ–°ä½¿ç”¨å¸¦æœ‰as-pattern的节点åªæ˜¯

                TreeInsert.Node
                  @ a_aUI
                  a1_aBR
                  left_aBS
                  (TreeInsert.treeInsert @ a_aUI $dOrd_aUW eta_B2 right_aBT);

这是一ç§é¿å…检查,å¯èƒ½ä¼šäº§ç”Ÿæ˜¾ç€çš„性能差异。

然而,这ç§å·®å¼‚实际上与as-pattern无关。这åªæ˜¯å› ä¸ºä½ çš„第一个片段使用的是x > aåŽå«ï¼Œè¿™å¹¶éžæ˜“事。第二个使用otherwise,它已ç»è¿‡ä¼˜åŒ–。

如果您将第一个代ç æ®µæ›´æ”¹ä¸º

treeInsert :: (Ord a) => a -> Tree a -> Tree a  
treeInsert x EmptyTree = singleton x  
treeInsert x (Node a left right)   
  | x == a     = Node x left right  
  | x < a      = Node a (treeInsert x left) right  
  | otherwise  = Node a left (treeInsert x right)

然åŽå·®å¼‚归结为

      GHC.Types.True -> TreeInsert.Node @ a_aUH a1_aBQ left_aBR right_aBS

VS

      GHC.Types.True -> wild_Xa

这确实åªæ˜¯Node x left right与all的区别。

......没有优化,就是这样。 The versions diverge further when I turn on -O2。但我无法确定表现如何ä¸åŒï¼Œé‚£é‡Œã€‚

答案 1 :(得分:4)

  

在现实生活中的编ç ä¸­ï¼Œæˆ‘该怎么办?返回åŒæ ·çš„东西有什么缺点å—?

a == b ä¸ä¿è¯æ‰€æœ‰å‡½æ•°f a == f b都f。所以,你å¯èƒ½å¿…须返回新对象,å³ä½¿ä»–们比较相等。

æ¢å¥è¯è¯´ï¼Œæ— è®ºæ€§èƒ½æå‡å¦‚何,Node x left right Node a left right或all都å¯èƒ½

/ p>

例如,您å¯èƒ½æœ‰æºå¸¦å…ƒæ•°æ®çš„类型。当您将它们进行相等性比较时,您å¯èƒ½åªå…³å¿ƒå€¼å¹¶å¿½ç•¥å…ƒæ•°æ®ã€‚但如果你åªæ˜¯å› ä¸ºå®ƒä»¬ç›¸ç­‰è€Œå–代它们就会失去元数æ®ã€‚

a == x

é‡ç‚¹æ˜¯Eq type-class 表示您å¯ä»¥æ¯”较相等的值。 除此之外ä¸ä¿è¯ä»»ä½•äº‹æƒ…。

一个真实示例,其中newtype ValMeta a b = ValMeta (a, b) -- value, along with meta data deriving (Show) instance Eq a => Eq (ValMeta a b) where -- equality only compares values, ignores meta data ValMeta (a, b) == ValMeta (a', b') = a == a' ä¸ä¿è¯a == b是在自平衡树中维护f a == f b个唯一值的时候。自平衡树(例如红黑树)对树的结构有一些ä¿è¯ï¼Œä½†å®žé™…深度和结构å–决于数æ®è¢«æ·»åŠ åˆ°é›†åˆä¸­æˆ–从集åˆä¸­ç§»é™¤çš„顺åºã€‚

现在,当您比较2个集åˆçš„相等性时,您希望比较集åˆä¸­çš„值是å¦ç›¸ç­‰ï¼Œè€Œä¸æ˜¯åŸºç¡€æ ‘具有相åŒçš„确切结构。但是如果你有一个åƒSet这样的函数暴露了维护集åˆçš„底层树的深度,那么å³ä½¿é›†åˆæ¯”较相等,也无法ä¿è¯æ·±åº¦ç›¸ç­‰ã€‚

Here is a video of great Philip Wadler realizing live and on-stage that many useful relations do not preserve equality(从42分钟开始)。

编辑:æ¥è‡ªghc的示例depth并ä¸æš—示a == b:

f a == f b

å¦ä¸€ä¸ªçŽ°å®žä¸–界的例å­æ˜¯å“ˆå¸Œè¡¨ã€‚当且仅当它们的键值对结åˆæ—¶ï¼Œä¸¤ä¸ªå“ˆå¸Œè¡¨æ˜¯ç›¸ç­‰çš„。但是,哈希表的 capacity ,å³æ‚¨å¿…须在é‡æ–°åˆ†é…å’Œé‡æ–°åˆ†é…之å‰æ·»åŠ çš„密钥数é‡ï¼Œå–决于æ’å…¥/删除的顺åºã€‚

因此,如果您有一个返回哈希表容é‡çš„函数,它å¯èƒ½ä¼šä¸ºå“ˆå¸Œè¡¨\> import Data.Set \> let a = fromList [1, 2, 3, 4, 5, 10, 9, 8, 7, 6] \> let b = fromList [1..10] \> let f = showTree \> a == b True \> f a == f b False å’Œa返回ä¸åŒçš„值,å³ä½¿b。

答案 2 :(得分:3)

我的两分钱......或许甚至ä¸æ˜¯åŽŸæ¥çš„问题:

我ä¸ä¼šä½¿ç”¨x < aå’Œx == a编写警å«ï¼Œè€Œæ˜¯å°†compare a b与LT,EQå’ŒGT匹é…,例如:< / p>

treeInsert x all@(Node a left right) =
  case compare x a of
    EQ -> ...
    LT -> ...
    GT -> ...

如果xå’Œaå¯èƒ½æ˜¯å¤æ‚çš„æ•°æ®ç»“构,我会这样åšï¼Œå› ä¸ºåƒx < a这样的测试å¯èƒ½ä¼šå¾ˆæ˜‚贵。

答案 3 :(得分:2)

答案似乎是错误的。我把它留在这里,供å‚考......

使用第二个函数å¯ä»¥é¿å…创建新节点,因为编译器无法真正ç†è§£ç›¸ç­‰ï¼ˆ==åªæ˜¯ä¸€äº›å‡½æ•°ã€‚)如果将第一个版本更改为

-- version C
treeInsert :: (Ord a) => a -> Tree a -> Tree a  
treeInsert x EmptyTree = singleton x  
treeInsert x (Node a left right)   
  | x == a = Node a left right  -- Difference here! Changed x to a.
  | x < a  = Node a (treeInsert x left) right  
  | x > a  = Node a left (treeInsert x right)

编译器å¯èƒ½ä¼šæ‰§è¡Œå¸¸è§çš„å­è¡¨è¾¾å¼æ¶ˆé™¤ï¼Œå› ä¸ºä¼˜åŒ–器将能够看到Node a left right与Node a left right相åŒã€‚

å¦ä¸€æ–¹é¢ï¼Œæˆ‘怀疑编译器å¯ä»¥ä»Ža == x推断Node a left right与Node x left right相åŒã€‚

因此,我éžå¸¸ç¡®å®šåœ¨-O2下,版本B和版本C是相åŒçš„,但版本Aå¯èƒ½æ›´æ…¢ï¼Œå› ä¸ºå®ƒåœ¨a == x情况下进行了é¢å¤–的实例化。

答案 4 :(得分:2)

好å§ï¼Œå¦‚果第一个案例使用了a而ä¸æ˜¯x,那么至少GHCå¯ä»¥é€šè¿‡å…¬å…±å­è¡¨è¾¾å¼æ¶ˆé™¤æ¥æ¶ˆé™¤æ–°èŠ‚点的分é…。

treeInsert x (Node a left right)   
  | x == a = Node a left right

然而,这在任何éžå¹³å‡¡çš„用例中都是无关紧è¦çš„,因为å³ä½¿å…ƒç´ å·²ç»å­˜åœ¨ï¼Œæ ‘下到节点的路径也将被å¤åˆ¶ã€‚除éžä½ çš„用例很简å•ï¼Œå¦åˆ™è¿™æ¡è·¯å¾„将明显长于å•ä¸ªèŠ‚点。

在ML的世界中,é¿å…è¿™ç§æƒ…况的相当惯用的方法是抛出KeyAlreadyExists异常,然åŽåœ¨é¡¶çº§æ’入函数中æ•èŽ·è¯¥å¼‚常并返回原始树。这将导致堆栈展开,而ä¸æ˜¯åœ¨å †ä¸Šåˆ†é…任何Node。

直接实现ML习语在Haskell中基本上是ç¦æ­¢çš„,原因很充分。如果é¿å…è¿™ç§é‡å¤å¾ˆé‡è¦ï¼Œæœ€ç®€å•ä¹Ÿå¯èƒ½æœ€å¥½çš„åšæ³•æ˜¯åœ¨æ’入之å‰æ£€æŸ¥æ ‘是å¦åŒ…å«å¯†é’¥ã€‚

与直接Haskellæ’入或ML习语相比,这ç§æ–¹æ³•çš„缺点是它涉åŠä¸¤æ¬¡é历路径而ä¸æ˜¯ä¸€æ¬¡ã€‚现在,这是一个å¯ä»¥åœ¨Haskell中实现的éžé‡å¤çš„å•éæ’入:

treeInsert :: Ord a => a -> Tree a -> Tree a
treeInsert x original_tree = result_tree
  where
    (result_tree, new_tree) = loop x original_tree

    loop x EmptyTree = (new_tree, singleton x)
    loop x (Node a left right) =
       case compare x a of
         LT -> let (res, new_left) = loop x left
                in (res, Node a new_left right)
         EQ -> (original_tree, error "unreachable")
         GT -> let (res, new_right) = loop x right
                in (res, Node a left new_right)

然而,旧版本的GHC(大约7到10å¹´å‰ï¼‰ä¸èƒ½éžå¸¸æœ‰æ•ˆåœ°é€šè¿‡æƒ°æ€§ç»“果对处ç†è¿™ç§é€’归,并且根æ®æˆ‘çš„ç»éªŒï¼Œåœ¨æ’å…¥å‰æ£€æŸ¥å¯èƒ½ä¼šè¡¨çŽ°å¾—更好。如果这个观察结果在最近的GHC版本的背景下真的å‘生了å˜åŒ–,我会感到有些惊讶。

当然å¯ä»¥æƒ³è±¡ä¸€ä¸ªå‡½æ•°ç›´æŽ¥æž„造(但ä¸è¿”回)树的新路径,并且一旦知é“元素是å¦å·²ç»å­˜åœ¨å°±å†³å®šè¿”回新路径或原始路径。 (如果没有返回,新路径将立å³å˜ä¸ºåžƒåœ¾ã€‚)这符åˆGHCè¿è¡Œæ—¶çš„基本原则,但在æºè¯­è¨€ä¸­å¹¶ä¸èƒ½çœŸæ­£è¡¨è¾¾ã€‚

当然,对于惰性数æ®ç»“构,任何完全ä¸é‡å¤çš„æ’入函数都将具有与简å•çš„é‡å¤æ’å…¥ä¸åŒçš„严格性。因此,无论实现技术如何,如果懒惰都很é‡è¦ï¼Œå®ƒä»¬å°±ä¼šæœ‰ä¸åŒçš„功能。

但是,当然,路径是å¦é‡å¤å¯èƒ½å¹¶ä¸é‡è¦ã€‚最é‡è¦çš„情况是当你æŒç»­ä½¿ç”¨æ ‘时,因为在线性使用情况下,旧路径在æ¯æ¬¡æ’å…¥åŽä¼šç«‹å³å˜æˆåžƒåœ¾ã€‚当然,这åªåœ¨æ‚¨æ’入大é‡é‡å¤é¡¹æ—¶æ‰æœ‰æ„义。