Home > OS >  How to pattern match on Constructors in Haskell?
How to pattern match on Constructors in Haskell?

Time:09-30

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 States 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 ()
  • Related