Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Applicative that increments the environment in haskell

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.

like image 769
Andrew Thaddeus Martin Avatar asked Dec 25 '22 19:12

Andrew Thaddeus Martin


1 Answers

(,) 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.)

like image 82
danidiaz Avatar answered Jan 09 '23 14:01

danidiaz