使用SYB将变换仅应用于树而不是everywhere
的最佳方法是什么?例如,在以下简化表达式中,有Var "x"
的几个实例,我想仅用Var "y"
替换第一个实例。
data Exp = Var String
| Val Int
| Plus Exp Exp
|...
myExp = Val 5 `Plus` Var "x" `Plus` Val 5 `Plus` Var "x" ...
使用everywhere
组合器无法完成此操作,因为它会尝试将Var "x"
的所有实例转换为Var "y"
。
编辑(发布后):看起来somewhere
是我正在寻找的。
答案 0 :(得分:3)
作为一名SYB初学者,我的答案更像是猜测,但似乎有效。
Neil Brown推荐的组合somewhere
可能并不完全符合您的要求。它是defined
-- | Apply a monadic transformation at least somewhere
somewhere :: MonadPlus m => GenericM m -> GenericM m
-- We try "f" in top-down manner, but descent into "x" when we fail
-- at the root of the term. The transformation fails if "f" fails
-- everywhere, say succeeds nowhere.
--
somewhere f x = f x `mplus` gmapMp (somewhere f) x
,其中
-- | Transformation of at least one immediate subterm does not fail
gmapMp :: forall m. MonadPlus m => (forall d. Data d => d -> m d) -> a -> m a
但我们需要最多转换一次。为此,似乎gmapMo
会更好:
-- | Transformation of one immediate subterm with success
gmapMo :: forall m. MonadPlus m => (forall d. Data d => d -> m d) -> a -> m a
所以我做了自己的组合器:
{-# LANGUAGE DeriveDataTypeable, RankNTypes #-}
import Control.Monad
import Data.Maybe (fromMaybe)
import Data.Data
import Data.Typeable (Typeable)
import Data.Generics.Schemes
import Data.Generics.Aliases
-- | Apply a monadic transformation once.
once :: MonadPlus m => GenericM m -> GenericM m
once f x = f x `mplus` gmapMo (once f) x
如果替换失败,则返回mzero
,否则返回替换结果。如果你不关心替换是否失败(没有匹配),你可以使用像
once' :: (forall a. Data a => a -> Maybe a) -> (forall a. Data a => a -> a)
once' f x = fromMaybe x (once f x)
有了这些,我们可以做一些替换:
data Exp = Var String | Val Int | Plus Exp Exp
deriving (Show, Typeable, Data)
myExp = Val 5 `Plus` Var "x" `Plus` Val 5 `Plus` Var "x"
replM :: (MonadPlus m) => Exp -> m Exp
replM (Var "x") = return $ Var "y"
replM t = mzero
main = do
-- `somewhere` doesn't do what we want:
print $ (somewhere (mkMp replM) myExp :: Maybe Exp)
-- returns `Just ..` if the substitution succeeds once,
-- Nothing otherwise.
print $ (once (mkMp replM) myExp :: Maybe Exp)
-- performs the substitution once, if possible.
print $ (once' (mkMp replM) myExp :: Exp)
-- Just for kicks, this returns all possible substitutions
-- where one `Var "x"` is replaced by `Var "y"`.
print $ (once (mkMp replM) myExp :: [Exp])
答案 1 :(得分:2)
是的,我认为somewhere (mkMp mySpecificFunction)
应该这样做,如果你使用MonadPlus monad并在找到你想要的东西时让它成功。
一个灵活但有瑕疵的替代方法是将everywhereM
与状态monad一起使用,该monad可以存储Boolean
(或存储Maybe MyFunc
或其他什么)并根据状态{应用转换} {1}}或True
- 这样,当您完成后(例如,在应用转换一次之后),您只需将状态更改为Just myFunc
/ False
。