Question

I am still a newbie to Haskell and I think I am over my head right now. I have code that looks like the following.

data World = World {
  intStack :: [Int],
  boolStack :: [Bool]
} deriving Show

instance IntStack World where
   getIntStack = intStack
   putIntStack ints (World _ bools) = World ints bools

instance BoolStack World where
    getBoolStack = boolStack
    putBoolStack bools (World ints _) = World ints bools

class IntStack a where
    getIntStack :: a -> [Int]
    putIntStack :: [Int] -> a -> a

class BoolStack a where
    getBoolStack :: a -> [Bool]
    putBoolStack :: [Bool] -> a -> a

(<=>) :: (IntStack c, BoolStack c) => c -> c
(<=>) w = putIntStack xs . putBoolStack ((x == x'):bs) $ w
    where (x:x':xs) = getIntStack w
          bs = getBoolStack w

(<+>) :: (IntStack c) => c -> c
(<+>) w = putIntStack ((x+x'):xs) w
    where (x:x':xs) = getIntStack w

My focus (For now ignoring error cases in the functions) is being able to chain together functions like (<=>) and (<+>) assuming that the underlying data type implements the function's required interfaces.

I feel like I can clean this up a lot with a state monad, but I am not sure how to structure it to allow changes to whatever data type that implements IntStack, BoolStack, etc..

I know that this is a horribly vague description, but I feel like the code I have above is probably the absolutely wrong way to go about it.

Thanks for any feedback!

Was it helpful?

Solution

class IntStack a where
    getIntStack :: a -> [Int]
    putIntStack :: [Int] -> a -> a

class BoolStack a where
    getBoolStack :: a -> [Bool]
    putBoolStack :: [Bool] -> a -> a

Congratulations, you've just invented lenses! Abstract the [Int] and [Bool] types, and use data instead of class, and you get something like

data Lens a b = Lens
    { get :: a -> b
    , put :: b -> a -> a
    }

...which is implemented in half a dozen packages on Hackage. Most offer at least:

  • the ability to derive projection lenses like your getIntStack/putIntStack and getBoolStack/putBoolStack directly from a data declaration
  • horizontal composition (running first one lens, then a second lens -- e.g. first picking a World out of some larger structure, then picking the intStack out of the World) and vertical composition (running two lenses in parallel, each on one side of a pair)
  • some interface with State and StateT (e.g. something of type Lens a b -> State b r -> State a r), which would let you write computations on a [Bool] or [Int] and run them as if they were computations on a World

So, check out hackage! There's the data-lens family, which includes a core, the deriving ability, and the stateful interface; the lens package; and the pointless-lenses package. There are probably some I forgot, too.

OTHER TIPS

You could implement push and pop operations in the state monad, and use them to implement your functions:

popInt :: IntStack c => State c Int
popInt = do
  (x:xs) <- getIntStack <$> get
  modify $ putIntStack xs
  return x

pushInt :: IntStack c => Int -> State c ()
pushInt x = do
  xs <- getIntStack <$> get
  modify $ putIntStack (x:xs)

(<+>) :: IntStack c => State c ()
(<+>) = do 
  x <- popInt
  x' <- popInt
  pushInt (x + x')
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top