2013-06-04 3 views
5

SYBを使用してeverywhereの代わりに1回だけツリーに変換を適用する最も良い方法は何ですか?たとえば、次の簡略化された表現では、Var "x"といういくつかのインスタンスがあり、最初のインスタンスをVar "y"に置き換えたいとします。ハスケルのボイラープレート(SYB)のスクラップ - どこでもなく1回だけ変換を適用する

data Exp = Var String | Val Int | Plus Exp Exp |...

myExp = Val 5 `Plus` Var "x" `Plus` Val 5 `Plus` Var "x" ...

それはVar "y"Var "x"のすべてのインスタンスを変換しようとするので、これはeverywhereコンビネータを使用して行うことはできません。

EDIT(投稿後):somewhereは私が探しているようです。

答えて

3

私はSYB初心者であり、私の答えは推測のようですが、うまくいくようです。

コンビネータsomewhereニールブラウンが推薦するのは、おそらくあなたが望むだけではないでしょう。それは

-- | 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 

-- | 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 

としてdefinedだ。しかし、私たちは、最高1回を変換する必要があります。このためには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]) 
+0

優れたソリューション!まさに私が探していたもの。感謝万円! – user1546806

+0

私のコードでこれを動作させるには、 'once f x = f x \' mplus \ 'gmapMo(once f)x'と書き直す必要がありました。 – user1546806

+0

@ user1546806はい、申し訳ありませんが、それは愚かな間違いでした。私は答えを修正します。 –

2

はい、あなたはMonadPlusモナドを使用して、探しているものが見つかると成功すると思いますが、somewhere (mkMp mySpecificFunction)と思っています。

フレキシブルしかしハック代替Boolean(またはストアMaybe MyFuncまたは任意)を格納し、状態がTrue又はJust myFuncさに応じて変換を適用することができる状態モナドとeverywhereMを使用することである - あなたが行われる場合、そのよう(例えば変換を1回適用した後など)、状態をFalse/Nothingに変更するだけです。

+0

おかげで、@NeilBrownを。最初のアプローチをもう少し詳しく説明できますか?私はこのライブラリ(http://web.engr.oregonstate.edu/~erwig/reclib/)も見つけました。これはMonadPlusを使って一時変換を指定していますが、 'somewhere'は使用していません。 2番目のアプローチは機能しますが、そのルートには行きたくありません。 – user1546806

関連する問題