commander-cli-0.8.0.0: src/Control/Monad/Commander.hs
{-# LANGUAGE DeriveFunctor #-}
{- |
Module: Control.Monad.Commander
Description: A monad for stateful, backtracking computations
Copyright: (c) Samuel Schlesinger 2020
License: MIT
Maintainer: sgschlesinger@gmail.com
Stability: experimental
Portability: POSIX, Windows
-}
module Control.Monad.Commander (
-- ** The CommanderT Monad
{- |
The 'CommanderT' monad is how your CLI programs are interpreted by 'run'.
It has the ability to backtrack and it maintains some state.
-}
CommanderT(Action, Defeat, Victory), runCommanderT,
) where
import Control.Monad (ap)
import Control.Monad.Trans (MonadTrans, lift, liftIO, MonadIO)
import Control.Applicative (Alternative(empty, (<|>)))
-- | A 'CommanderT' action is a metaphor for a military commander. At each
-- step, we have a new 'Action' to take, or we could have experienced
-- 'Defeat', or we can see 'Victory'. While a real life commander
-- worries about moving his troops around in order to achieve a victory in
-- battle, a 'CommanderT' worries about iteratively transforming a state
-- to find some value. We will deal with the subset of these actions where
-- every function must decrease the size of the state, as those are the
-- actions for which this is a monad.
data CommanderT state m a
= Action (state -> m (CommanderT state m a, state))
| Defeat
| Victory a
deriving Functor
-- | We can run a 'CommanderT' action on a state and see if it has
-- a successful campaign.
runCommanderT :: Monad m
=> CommanderT state m a
-> state
-> m (Maybe a)
runCommanderT (Action action) state = do
(action', state') <- action state
runCommanderT action' state'
runCommanderT Defeat _ = return Nothing
runCommanderT (Victory a) _ = return (Just a)
instance (Monad m) => Applicative (CommanderT state m) where
(<*>) = ap
pure = Victory
instance MonadTrans (CommanderT state) where
lift ma = Action $ \state -> do
a <- ma
return (pure a, state)
instance MonadIO m => MonadIO (CommanderT state m) where
liftIO ma = Action $ \state -> do
a <- liftIO ma
return (pure a, state)
-- Return laws:
-- Goal: return a >>= k = k a
-- Proof: return a >>= k
-- = Victory a >>= k
-- = k a
-- = k a
-- Goal: m >>= return = m
-- Proof:
-- Case 1: Defeat >>= return = Defeat
-- Case 2: Victory a >>= return
-- = Victory a
-- Case 3: Action action >>= return
-- = Action $ \state -> do
-- (action', state') <- action state
-- return (action' >>= return, state')
--
-- Case 3 serves as an inductive proof only if action' is a strictly smaller action
-- than action!
--
-- Bind laws:
-- Goal: m >>= (\x -> k x >>= h) = (m >>= k) >>= h
-- Proof:
-- Case 1: Defeat >>= _ = Defeat
-- Case 2: Victory a >>= (\x -> k x >>= f)
-- = k a >>= f
-- = (Victory a >>= k) >>= f
-- Case 3: Action action >>= (\x -> k x >>= h)
-- = Action $ \state -> do
-- (action', state') <- action state
-- return (action' >>= (\x -> k x >>= h), state')
-- = Action $ \state -> do
-- (action', state') <- action state
-- return ((action' >>= k) >>= h, state') -- by IH
-- On the other hand,
-- (Action action >>= k) >>= h
-- = Action (\state -> do
-- (action', state') <- action state
-- return (action' >>= k, state') >>= h
-- = Action $ \state -> do
-- (action', state') <- action state
-- return ((action' >>= k) >>= h, state')
--
-- This completes our proof for the case when these are finite.
-- Basically, we require that the stream an action produces is strictly
-- smaller than any other streams, for all state inputs. The ways that we
-- use this monad transformer satisify this constraint. If this
-- constraint is not met, many of our functions will return bottom.
--
-- We can certainly have functions that operate on these things and
-- change them safely, without violating this constraint. All of the
-- functions that we define on CommanderT programs preserve this
-- property.
--
-- An example of a violating term might be:
--
-- violator :: CommanderT state m
-- violator = Action (\state -> return (violator, state))
--
-- The principled way to include this type would be to parameterize it by
-- a natural number and have that natural number decrease over time, but
-- to enforce that in Haskell we couldn't have the monad instance
-- anyways. This is the way to go for now, despite the type violating the
-- monad laws potentially for infinite inputs.
instance Monad m => Monad (CommanderT state m) where
Defeat >>= _ = Defeat
Victory a >>= f = f a
Action action >>= f = Action $ \state -> do
(action', state') <- action state
return (action' >>= f, state')
instance Monad m => Alternative (CommanderT state m) where
empty = Defeat
Defeat <|> a = a
v@(Victory _) <|> _ = v
Action action <|> p = Action $ \state -> do
(action', state') <- action state
return (action' <|> p, state')