I have identified some common functionality in two of my datatypes, so like any programmer worth his salt I tried to factor it out:
module Binary where
import Control.Applicative
import Data.Function
import Control.Monad
class Binary f where
yes :: f a a
no :: f a b
(..>) :: f a b -> f b c -> f a c
yes' :: f a ()
(~.>) :: f a b -> f a c -> f a c
try :: (Binary f, Alternative (f a)) => f a a -> f a a
try = (<|> yes)
try' :: (Binary f, Alternative (f a)) => f a () -> f a ()
try' = (<|> yes')
(.>) :: (Binary f, Alternative (f c)) => f a c -> f c c -> f a c
a .> b = a ..> try b
(~>) :: (Binary f, Alternative (f a)) => f a b -> f a () -> f a ()
a ~> b = a ~.> try' b
greedy :: (Binary f, Alternative (f a)) => f a a -> f a a
greedy = fix $ ap (.>)
greedy' :: (Binary f, Alternative (f a)) => f a () -> f a ()
greedy' = fix $ ap (~>)
As you can see, the types of yes and yes', and ..> and ~.> are slightly different - they need to be for me to write instances - and so I end up with duplicate functions.
Is there a way I can get rid of yes' and ~.>, and still make an instance of Binary with those types?
Here are my two instances:
module Example where
import Binary
import Prelude hiding ((.), id)
import Control.Category
import Data.List.Zipper as Z
import Control.Monad.Trans.Maybe
import Control.Monad.State
newtype Opt a b = Opt { runOpt :: a -> Maybe b }
instance Category Opt where
id = yes
(Opt f) . (Opt g) = Opt $ g >=> f
instance Binary Opt where
yes = Opt Just
no = Opt $ const Nothing
(..>) = (>>>)
---------
type Tape = Zipper
newtype Machine a b = Machine { unMachine :: MaybeT (State (Tape a)) b }
instance Functor (Machine a) where
fmap f (Machine x) = Machine $ f <$> x
instance Applicative (Machine a) where
pure = Machine . pure
(Machine f) <*> (Machine x) = Machine $ f <*> x
instance Monad (Machine a) where
(Machine a) >>= f = Machine $ a >>= unMachine <$> f
instance Binary Machine where
no = Machine mzero
yes' = pure ()
a ~.> b = a >> b
I think there is a subtle inconsistency in your two instances -- that is,
OptandMachinedo not quite have enough in common to share this much structure. For example, the methodsare essentially a
Category, as you have noticed (though I would simply makeCategorya superclass ofBinaryinstead of duplicating those methods). ButMachineis not a category as it does not support composition. Also,Optis a profunctor (contravariant in its first argument, covariant in its second), whereasMachineis instead invariant on its first argument. These are my hints that something needs to be changed before you try to abstract over these types.My suspicion is that there is a missing parameter to
Machine, and the state parameter is actually external to theBinaryabstraction. Try using the Kleisli category of your monad.Now
Machine sis aCategoryand the same sort ofBinarythatOptis, and you don't need any of the primed combinators, and you can express any oldMachine a bs asMachine a () bif you need to, but you can also probably generalize them.In fact, the abstraction you are looking for may simply be
ArrowZero.Arrowhas a bit more structure thanCategory, so you should consider whether the rest ofArrowis applicable to your problem. If so, you have just opened a new toolbox of combinators, and you don't need to write any instances by hand because they are all covered by:(or in
newtypestyle withGeneralizedNewtypeDerivingif you prefer)