Home > Net >  Pattern Matching Element in List in Haskell
Pattern Matching Element in List in Haskell

Time:06-25

Let's say I have the following type, where KType is defined somewhere else:

data PExpr = 
    PVal KType |
    PCall String [PExpr]

I have a function:

tryReplace:: PExpr -> Maybe PExpr 

That for our purposes, does the following:

  1. Returns Nothing if the given PExpr is a PVal
  2. Returns Nothing if the given PExpr is a PCall and is empty
  3. Returns Nothing if the given PExpr is a PCall and is composed entirely of PVal
  4. Returns (Just PExpr) if the given PExpr is a PCall and has a PCall in the list, where the first PCall found in the list is replaced with a globally defined KType x.

So far, this is what I have in the way of accomplishing 1 and 2:

tryReplace (PVal _) = Nothing
tryReplace (PCall _ []) = Nothing

I am less sure about my version of #3 staying when #4 is implemented:

tryReplace (PCall _ [PVal _]) = Nothing
tryReplace (PCall str (PVal x:xs)) = tryReplace (PCall str xs)

I essentially want #4 to pattern match as such:

tryReplace (PCall str (PVal a:PVal b:...:PCall _:rest)) = 
    Just (PCall str (PVal a:PVal b:...:PVal newValue:rest))

"..." is supposed to represent all the PVal before a PCall is found.

I'm sure there's a function that does something very similar to this already defined, but regardless of that I am trying to implement my own version.

Unless there is a single pattern function match case that can take care of #3, I can't think of a way for #4 to work, as I think I would be forced to build a list while traversing the given list. But the list being built might not even be returned if a PCall is found, which means extra work was done for nothing. How should I go about this? Should I define another function to assist in implementing #4?

CodePudding user response:

You can use an additional function that will find out if the list satisfies #3 or #4, and returns a substitute list accordingly

hasPCall :: [PExpr] -> (Bool, [PExpr])
hasPCall [] = (False, [])
hasPCall (PCall _ _ : rst) = (True, (PVal newValue:rst))
hasPCall (PVal a : rst) = (fst val, (PVal a:snd val))
    where
        val = hasPCall rst

tryReplace:: PExpr -> Maybe PExpr
tryReplace (PVal _) = Nothing
tryReplace (PCall _ []) = Nothing
tryReplace (PCall s n) = case val of
    (True, a) -> Just (PCall s (snd newlist))
    (False, _) -> Nothing
  where
    newlist = hasPCall n

Instead of (Bool, [PExpr]), Just [PExpr] can be implemented for hasPCall but that would be messier than this one.

CodePudding user response:

Lets simplify. Assume you have a list of items which can be one of A, B, C:

data Item = A | B | C
  deriving (Show)

xs1 = []
xs2 = [A, B, B, A, A]
xs3 = [A, A, A, A, A]
xs4 = [B, B, B, A, B]
xs5 = [A, A, A, A, B]

We can add a Boolean to remember that we have only seen As so far:

import Control.Arrow

process :: (Bool, [Item]) -> (Bool, [Item])
process (_, []) = (True, [])
process (_, A:xs) = second (A :) (process (True, xs))
process (_, B:xs) = (False, C : xs)

Here we are using second @(->) :: (b -> c) -> (d, b) -> (d, c) to add an A in the second part of the result tuple:

ghci> :set -XTypeApplicationsghci>
:t second @(->)
second @(->) :: (b -> c) -> (d, b) -> (d, c)
ghci> second (A:) (True, [B, C])
(True,[A,B,C])

Which will give us:

ghci> process (True, xs1)
(True,[])
ghci> process (True, xs2) 
(False,[A,C,B,A,A])
ghci> process (True, xs3)
(True,[A,A,A,A,A])
ghci> process (True, xs4)
(False,[C,B,B,A,B])
ghci> process (True, xs5)
(False,[A,A,A,A,C])

With the help of the state monad we can even hide the input Bool and get:

process' :: State [Item] Bool
process' = state go
  where
    go [] = (True, [])
    go (A:xs) = second (A:) (go xs)
    go (B:xs) = (False, C:xs)

Giving the same result:

ghci> runState process' xs1
(True,[])
ghci> runState process' xs2
(False,[A,C,B,A,A])
ghci> runState process' xs3
(True,[A,A,A,A,A])
ghci> runState process' xs4
(False,[C,B,B,A,B])
ghci> runState process' xs5
(False,[A,A,A,A,C])
  • Related