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:
- Returns Nothing if the given PExpr is a PVal
- Returns Nothing if the given PExpr is a PCall and is empty
- Returns Nothing if the given PExpr is a PCall and is composed entirely of PVal
- 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 A
s 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])