RULESã«ããã³ã³ãã¤ã«æããã°ã©ãã³ã°
ããã¯Haskell Advent Calendar 2013ã®(3+Ï)æ¥ç®ã®è¨äºã§ãã
(3 + pi)ã(quot 7 8)ã®ãããªåç´ãªå®æ°å¼ã¯ãghc -Oãè¡ãªã宿°ç³ã¿è¾¼ã¿ã«ãã£ã¦ã³ã³ãã¤ã«æã«è¨ç®ããããuncurry (*) (3, max 5 2)ã®ãããªããè¤éãªå¼ããã¤ã³ã©ã¤ã³å±éãã¦ãã宿°ç³ã¿è¾¼ã¿ããããã¨ã§ãã¯ãã³ã³ãã¤ã«æã«æ´æ°ãªãã©ã«ã«ã¾ã§ç°¡ç´ãããã
ããã¯ä¸è¦ä¸è½ã ããå帰çãªé¢æ°ãä¸ã¤ã§ãããã¨ä½ãã§ããªããªããGHCãå台颿°ãã¤ã³ã©ã¤ã³åããªãããã ã(sum [1])ã§ããå®è¡æã®ã«ã¼ãã«ã³ã³ãã¤ã«ããã*1ã
ã©ããã¦ãã³ã³ãã¤ã«æã«è¨ç®ãã¦ã»ãã颿°ãããå ´åã¯ã©ããããè¯ãããä¸ã¤ã®æ¹æ³ã¯Template Haskell(ja)ã使ããã¨ã ããç¹å¥ãªæ§æã使ããªããã°ãããªããã¨ã-fwarn-unused-binds(ja)ãã¯ããã¨ãã¦è¦åãããã¤ãå¹ããªããªããã¨ãªã©å¯ä½ç¨ãããããã®è¨äºã¯ãããä¸ã¤ã®æ¹æ³ãæ¸ãæãè¦å(ja)ã使ã£ã¦ãã®ç¨®ã®ã³ã³ãã¤ã«æè¨ç®ãå®è£ ããè¡ãç´¹ä»ããã
ä¾ã¨ãã¦ãäºã¤ã®æ´æ°ã®æå¤§å ¬ç´æ°ãè¨ç®ãã颿°ãèãããHaskellã§æ®éã«æ¸ãã¨ä»¥ä¸ã®ããã«ãªãã
-- | äºã¤ã®æ´æ°ã®æå¤§å ¬ç´æ°ãæ±ãã -- Preludeã®gcdã¨åºå¥ããããã«gcd'ã¨ããååã«ãã¦ãã gcd' :: Int -> Int -> Int gcd' x 0 = x gcd' x y = pgcd (abs x) (abs y) {-# INLINE gcd' #-} -- | éè² ã®æ´æ°ã¨æ£ã®æ´æ°ã®æå¤§å ¬ç´æ°ãæ±ãã pgcd :: Int -> Int -> Int pgcd x y | q == 0 = y | otherwise = pgcd y q where q = mod x y
颿°gcd'ã¯éå帰çãªã®ã§ãINLINEãã©ã°ã(ja)ãä»ãã¦ããã°ééããªãã¤ã³ã©ã¤ã³å±éããããåé¡ã¯å帰çã«å®ç¾©ãããpgcdã ããããRULESãã©ã°ãã§ä½ã¨ããããã¨ãèããããããã«æãä»ãã®ã¯ã以ä¸ã®ãããªè¦åã使ã£ã¦ã³ã³ãã¤ã«æã«å¼·å¼ã«å帰ãèµ·ãããã¨ã *2ã
{-# RULES "pgcd" forall x y. pgcd x y = let q = rem x y in if q == 0 then y else pgcd y q #-}
ããã¯è¦ããã«ãpgcdã®å®ç¾©ããã®ã¾ã¾RULEã¨ãã¦æ¸ãç´ãããã®ã ãæ®å¿µãªãã¨ã«ããã¯ãã¾ãè¡ããªãã試ãã¦ã¿ãã¨ãGHCããããã¯ãèµ·ããã
ghc: panic! (the 'impossible' happened) (GHC version 7.6.3 for x86_64-unknown-linux): Simplifier ticks exhausted When trying RuleFired Class op == To increase the limit, use -fsimpl-tick-factor=N (default 100) If you need to do this, let GHC HQ know, and what factor you needed To see detailed counts use -ddump-simpl-stats Total ticks: 7320
ãã®ã¨ã©ã¼ã¯GHCã®åç´åå¨(simplifier)ãç¡éã«ã¼ããèµ·ãããã¨ãã«çºçãã*3ãåé¡ã¯ãpgcdè¦åãé©åãã¦ã左辺ããå³è¾ºã¸ã®æ¸ãæããèµ·ãã£ãå¾ãããã«å³è¾ºã®pgcdã®å¼ã³åºããã¾ãè¦åã«é©åãããã¨ã ãããã¯qã0ãã©ããã«ãããããªãèµ·ããã®ã§ãåå¸°ãæ¢ã¾ããªãã
ãã®åé¡ã«è¦è¦ãããã人ãå± ãã¯ãã ãæ£æ ¼ãªè¨èªã§ifã颿°ã¨ãã¦å®è£ ããå ´åã«ã両æ¹ã®åå²ãè©ä¾¡ããã¦ãã¾ãåé¡ã«ä¼¼ã¦ããã®ã ããã¡ãã®åé¡ã®è¯ãç¥ããã解決çã¯ãè©ä¾¡ããã¦æ¬²ãããªãé¨åãã©ã ãã§å ããã¨ã ããããã§ã¯ããã ãã§ã¯è¶³ããªããGHCã®åç´åå¨ã¯ãã©ã ãã®ä¸ã§ãã£ã¦ãç°¡ç´ã§ããããªã¨ããã¯ä½ã§ãç°¡ç´ããããã ãããã§ãåºæ¬çãªèãæ¹ã¯æµç¨ã§ãã¦ãè¦ããã«ifã®åå²é¨åãããè©ä¾¡ãé²ã¾ãªãå½¢ãã«ãã¦æ¸¡ãã¦ããã°è¯ããæ¬¡ã®é¢æ°ãå®ç¾©ãã¦ããã
data Key = Key delay :: Key -> a -> a delay _ x = x {-# NOINLINE delay #-} {-# RULES "delay/Key" forall x. delay Key x = x #-}
delayã¯ç¬¬äºå¼æ°ããã®ã¾ã¾è¿ã颿°ã ãããã®äºå®ã¯NOINLINEãã©ã°ãã«ãã£ã¦é è½ãããã®ã§åç´åå¨ãç¥ããã¨ãã§ããªã*4ãä¾å¤ã¯ãdelayã®ç¬¬ä¸å¼æ°ãå ·ä½çã«Keyã ã¨åãã£ã¦ããå ´åã§ããããã®ã¨ãã¯è¦å"delay/Key"ãçºåãã¦å®è³ªçã«delayãã¤ã³ã©ã¤ã³å±éããããããã使ã£ã¦ã(pgcd y q :: Int)ã¨ããå¼ã次ã®ããã«æ¸ãæããã
(\k -> delay k pgcd y q) :: Key -> Int
ãã®å½¢ã¯ãåç´åå¨ã«ãã£ã¦ç°¡ç´ããå¾ãé ãä¸ã¤ãå«ã¾ãªãã®ã§ãè¦åã®å³è¾ºã«æã£ã¦ãã¦ãå®å ¨ã ãä¸è¬ã«ã¯ãä¸ã¤ã®å¼ã®ä¸ã«ç°¡ç´ãé²ãããã¨ãããè¤æ°ç®æãã£ã¦ãè¯ãããã¨ãã°(f 3 + g 4)ã¯(\k -> delay k f 3 + delay k g 4)ã®ããã«å¤å½¢ã§ããã
ãã®ããã«ãã¦ä½ã£ããè©ä¾¡ã®æ¢ã¾ã£ããå¼ãåã³è§£åãã¦ç°¡ç´ãåéããã颿°ãç¨æããã
force :: (Key -> a) -> a force x = x Key {-# INLINE force #-}
ããã«ãã£ã¦xå é¨ã®delayã®ç¬¬ä¸å¼æ°ãKeyã«ç¢ºå®ããã®ã§ã"delay/Key"è¦åã«ãã£ã¦xå ã®delayãé¤å»ãããã
å¼ã®è©ä¾¡ãæ¢ãããã¨ãã§ããããã«ãªã£ãã®ã§ãããã使ã£ã¦ifãå®è£ ããã
if_ :: Bool -> (Key -> a) -> (Key -> a) -> a if_ c x y = if GHC.Exts.lazy c then force x else force y {-# NOINLINE if_ #-} {-# RULES "if_/True" forall x y. if_ True x y = force x "if_/False" forall x y. if_ False x y = force y #-}
"if_/True"ã¨"if_/False"ã®äºã¤ã®è¦åã«ãã£ã¦ãif_ã®ç¬¬ä¸å¼æ°ãTrueãFalseã«ç¢ºå®ããå ´åãããã«å¯¾å¿ããåå²ã®ã¿ãç°¡ç´åéãããããã以å¤ã®å ´åãã©ã¡ãã®é¸æè¢ãç°¡ç´ãããªã*5ã
ãã¨ã¯ä¸ã®è¦å"pgcd"ãif_ã使ã£ãå½¢ã«æ¸ãç´ãã°è¯ãã
{-# RULES "pgcd" forall x y. pgcd x y = let q = rem x y in if_ (q == 0) (\_ -> y) (\k -> delay k pgcd y q) #-}
ã³ã¼ã
module M where import qualified GHC.Exts as GHC -- | äºã¤ã®æ´æ°ã®æå¤§å ¬ç´æ°ãæ±ãã gcd' :: Int -> Int -> Int gcd' x 0 = x gcd' x y = pgcd (abs x) (abs y) {-# INLINE gcd' #-} -- | éè² ã®æ´æ°ã¨æ£ã®æ´æ°ã®æå¤§å ¬ç´æ°ãæ±ãã pgcd :: Int -> Int -> Int pgcd x y | q == 0 = y | otherwise = pgcd y q where q = mod x y {-# NOINLINE pgcd #-} data Key = Key delay :: Key -> a -> a delay _ x = x {-# NOINLINE delay #-} {-# RULES "delay/Key" forall x. delay Key x = x #-} force :: (Key -> a) -> a force x = x Key {-# INLINE force #-} {-# RULES "pgcd" forall x y. pgcd x y = let q = rem x y in if_ (q == 0) (\_ -> y) (\k -> delay k pgcd y q) #-} if_ :: Bool -> (Key -> a) -> (Key -> a) -> a if_ c x y = if GHC.lazy c then force x else force y {-# NOINLINE if_ #-} {-# RULES "if_/True" forall x y. if_ True x y = force x "if_/False" forall x y. if_ False x y = force y #-} test :: Int test = gcd' 120 84
ãããã³ã³ãã¤ã«ãã¦ã¿ãã*6
% ghc rulegcd.hs -O -ddump-simpl -fforce-recomp | grep 'M.test' M.test :: GHC.Types.Int M.test = GHC.Types.I# 12
ãã£ããï¼
*1:å°æ¥sumãèå夿ã®å¯¾è±¡ã«ãªãã°ããã®ä¾ã¯ã³ã³ãã¤ã«æã«è©ä¾¡ãããããã«ãªãã ãã
*2:modã®ä»£ããã«ã³ã³ãã¤ã«æã«è©ä¾¡ããããremã使ã£ã¦ããã弿°ãéè² ãªã®ã§çµæã¯åã
*3:ä»ã®ç¶æ³ã§ãçºçããã
*4:å®ç¨ããå ´åã¯ãdelayã®å¼ã³åºããæ®ã£ã¦ãæ§è½ã«æªå½±é¿ããªãããã«NOINLINE[0]ã¨ããã®ãè¯ããããããªãããã®å ´åå¾è¿°ã®RULESã«ã¯ãã¹ã¦[~0]ãä»ãããã¨
*5:if_ã®å®ç¾©ã®ä¸ã§GHC.Exts.lazyãå¼ãã§ããã®ã¯ãã£ã¤ãã ããããããªãã¨(if_ (case e of { p -> True; _ -> False }) x y)ã(case e of p -> if_ True x y; _ -> if_ False x y)ã«æ¸ãæãããã¦ãã¾ããçµæã¨ãã¦if_ã®ç¬¬ä¸å¼æ°ã確å®ãã¦ããªãã®ã«"if_/True"ã¨"if_/False"ãçºåãããã¨ã«ãªãã
*6:ãã®ã¾ã¾ã ã¨ãpgcdã®å®ç¾©ã®å³è¾ºã«åºã¦ããpgcdã®å¼ã³åºãã¾ã§ããè¦åã«ãã£ã¦æ¸ãæãããã¦ãã¾ãã®ã§ãããã¡ãã£ã¨å·¥å¤«ã®ä½å°ããã