I have a state machine where states are implemented using a sum type. Posting a simplified version here:
data State =
A { value :: Int }
| B { value :: Int }
| C { other :: String }
most of my functions are monadic consuming State
s and doing some actions based on the type. Something like (this code doesn't compile):
f :: State -> m ()
f st= case st of
s@(A | B) -> withValueAction (value s)
C -> return ()
I know that I could unroll constructors like:
f :: State -> m ()
f st= case st of
A v -> withValueAction v
B v -> withValueAction v
C _ -> return ()
But that's a lot of boilerplate and brittle to changes. If I change the parameters to the constructor I need to rewrite all case .. of
in my codebase.
So how would you pattern match on a subset of constructors and access a shared element?
CodePudding user response:
One way to implement this idiomatically is to use a slightly different value
function:
value :: State -> Maybe Int
value (A v) = Just v
value (B v) = Just v
value _ = Nothing
Then you can write your case using a pattern guard like this:
f st | Just v <- value st -> withValueAction v
f C{} = return ()
f _ = error "This should never happen"
Or you can simplify this a bit further using view patterns and even more with pattern synonyms:
{-# LANGUAGE ViewPatterns, PatternSynonyms #-}
pattern V :: Int -> State
pattern V x <- (value -> Just v)
{-# COMPLETE V, C #-}
f (V x) = withValueAction x
f C{} = return ()
CodePudding user response:
@Noughtmare's answer demonstrates how you can use view patterns to get the right "pattern matching syntax". To auto-generate the value
function that selects a shared field from several constructors, you can use lens
, though this kind of requires buying into the whole Lens ecosystem. After:
{-# LANGUAGE TemplateHaskell #-}
import Control.Lens
import Control.Lens.TH
data State =
A { _value :: Int }
| B { _value :: Int }
| C { _other :: String }
makeLenses ''State
you will have a traversal value
that can be used to access the partially shared field:
f :: (Monad m) => State -> m ()
f st = case st ^? value of
Just v -> withValueAction v
Nothing -> return ()