I'm wondering if there is an Applicative
that can track how many applicative operations have occurred. I tried to implement it as follows:
import Control.Applicative
main :: IO ()
main = print $ run 1 $ (,,,) <$> FromInt id <*> FromInt id <*> FromInt id <*> FromInt id
data FromInt a = FromInt (Int -> a)
run :: Int -> FromInt a -> a
run i (FromInt f) = f i
instance Functor FromInt where
fmap g (FromInt f) = FromInt (g . f)
instance Applicative FromInt where
pure a = FromInt (const a)
FromInt f <*> FromInt g = FromInt (\i -> f i (g (i + 1)))
But, this of course does not work. If we call runhaskell on the file, we get this:
(1,2,2,2)
And what I want is this:
(1,2,3,4)
I've seen people accomplish this effect by pushing the requirement to increment into the actual data (this is how yesod-forms does its formlet-style implementation). This more-or-less uses a variation on State
, and it allows people to break assumed invariants if they don't use particular helper functions (I think the yesod one is called mhelper
). I want to know if the incrementing can be pulled into the applicative instance as I have tried to do. This would make violating this particular invariant impossible.
(,) a
is an Applicative
when a
is a Monoid
. We can compose (,) (Sum Int)
with other applicative using Data.Functor.Compose
, and get an applicative that lets us estimate the "cost" assigned to a computation before running it.
To count the steps, we need a lift function from the base applicative that always assigns a cost of 1:
module Main where
import Data.Monoid
import Control.Applicative
import Data.Functor.Compose
type CountedIO a = Compose ((,) (Sum Int)) IO a
-- lift from IO
step :: IO a -> CountedIO a
step cmd = Compose (Sum 1, cmd)
countSteps :: CountedIO a -> Int
countSteps = getSum . fst . getCompose
exec :: CountedIO a -> IO a
exec = snd . getCompose
program :: CountedIO ()
program = step (putStrLn "aaa") *> step (putStrLn "bbb") *> step (putStrLn "ccc")
main :: IO ()
main = do
putStrLn $ "Number of steps: " ++ show (countSteps program)
exec program
For greater safety, we could hide the composed aplicative behind a newtype, and not export the constructor, only the step
function.
(Actions created with pure
have cost 0 and do not count as a step.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With