{-# LANGUAGE ScopedTypeVariables #-}
-- |
-- = 第2章 練習問題 F
-- 
--    * 以下は x の n 乗を求める関数で,この関数では exp x n を求めるのに n-1 回の乗算が必要です.
--
--        @
--        exp :: Integer -> Integer -> Integer
--        exp x n | n == 0    = 1
--                | n == 1    = x
--                | otherwise = x * exp x (n - 1)
--        @
--
--    * 尻高君の方法は以下のとおりで,乗算は高々 2p 回です.
--
--        @
--        exp :: Integer -> Integer -> Integer
--        exp x n | n == 0    = 1
--                | n == 1    = x
--                | even n    = exp (x * x) m
--                | odd n     = x * exp (x * x) m
--        @
--
--
--    * 関数呼び出しトレース
--
--      以下はオマケ(説明はありません).
--
module TFwH.Chap02.ExF where

import Control.Monad.Fix
import Data.List
import Debug.Trace

-- |
-- O(n) 回の乗算を必要とする expG を生成する汎関数
--
gexpG :: (Integer -> Integer -> Integer) -> (Integer -> Integer -> Integer)
gexpG f x n | n == 0    = 1
            | n == 1    = x
            | otherwise = x * f x (n - 1)

-- |
-- O(n) 回の乗算を必要とする exp
--
-- >>> expG 2 13
-- 8192
--
expG  :: Integer -> Integer -> Integer
expG  = fix gexpG

-- |
-- O(n) 回の乗算を必要とする exp のトレース版
--
-- >>> expG' 2 13
-- exp 2 13
-- exp 2 12
-- exp 2 11
-- exp 2 10
-- exp 2 9
-- exp 2 8
-- exp 2 7
-- exp 2 6
-- exp 2 5
-- exp 2 4
-- exp 2 3
-- exp 2 2
-- exp 2 1
-- 8192
--
expG' :: Integer -> Integer -> Integer
expG' = tracing "exp" gexpG

-- |
-- O(log n) 回の乗算を必要とする expS を生成する汎関数
--
gexpS :: (Integer -> Integer -> Integer) -> (Integer -> Integer -> Integer)
gexpS f x n | n == 0  = 1
            | n == 1  = x
            | even n  = f (x * x) (n `div` 2)
            | odd  n  = x * f (x * x) (n `div` 2)

-- |
-- O(log n) 回の乗算を必要とする exp
--
-- >>> expS 2 13
-- 8192
--
expS  :: Integer -> Integer -> Integer
expS  = fix gexpS

-- |
-- O(log n) 回の乗算を必要とする exp のトレース版
--
-- >>> expS' 2 13
-- exp 2 13
-- exp 4 6
-- exp 16 3
-- exp 256 1
-- 8192
--
expS' :: Integer -> Integer -> Integer
expS' = tracing "exp" gexpS

-- |
-- 関数呼び出しの簡単なトレースを生成
--
traceOfCall :: (Show a, Show b) => String -> (a -> b -> c) -> a -> b -> c
traceOfCall s f x n = trace msg f x n
  where
    msg = intercalate " " [s,show x,show n]

-- |
-- トレースを追加する関数
--
tracing :: (Show a, Show b) => String -> ((a -> b -> c) -> a -> b -> c) -> a -> b -> c
tracing = gfun . traceOfCall

-- |
-- 機能 f を g に追加するした汎関数の不動点を求める
--
gfun :: ((a -> b) -> (c -> d)) -> ((c -> d) -> (a -> b)) -> (c -> d)
gfun f g = fix (f . g)