2013-06-04 25 views
5

Cách tốt nhất để áp dụng chuyển đổi cho cây chỉ một lần thay vì everywhere bằng SYB? Ví dụ, trong biểu thức đơn giản sau đây, có một số trường hợp của Var "x" và tôi muốn thay thế phiên bản đầu tiên chỉ với Var "y".Phế liệu của Haskell Boilerplate (SYB) của bạn - chỉ áp dụng chuyển đổi một lần thay vì ở mọi nơi

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

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

này không thể được thực hiện bằng cách sử dụng everywhere combinator vì nó sẽ cố gắng để chuyển đổi tất cả các trường của Var "x"-Var "y".

EDIT (sau khi đăng bài): Có vẻ như somewhere là những gì tôi đang tìm kiếm.

Trả lời

3

Là người mới bắt đầu với SYB, câu trả lời của tôi giống như đoán, nhưng có vẻ như hiệu quả.

Bộ kết hợp somewhere được đề xuất bởi Neil Brown có thể không thực hiện chính xác những gì bạn muốn. Đó là defined như

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

nơi

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

Nhưng chúng ta cần phải chuyển đổi tối đa một lần. Đối với điều này có vẻ như gmapMo sẽ tốt hơn:

-- | Transformation of one immediate subterm with success 
gmapMo :: forall m. MonadPlus m => (forall d. Data d => d -> m d) -> a -> m a 

Vì vậy, tôi đã combinator của riêng tôi:

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

Nếu sự thay thế không, nó sẽ trả mzero, nếu không nó sẽ trả về kết quả thay thế. Nếu bạn không quan tâm nếu thay không (không có trận đấu), bạn có thể sử dụng một cái gì đó giống như

once' :: (forall a. Data a => a -> Maybe a) -> (forall a. Data a => a -> a) 
once' f x = fromMaybe x (once f x) 

Với những, chúng ta có thể làm một số thay thế:

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

Giải pháp tuyệt vời! Chính xác những gì tôi đang tìm kiếm. Cảm ơn rất nhiều! – user1546806

+0

Để làm cho công việc này trên mã của tôi, tôi đã phải viết lại một lần là 'một lần f x = f x \ 'mplus \' gmapMo (một lần f) x'. – user1546806

+0

@ user1546806 Vâng, xin lỗi, đó là một sai lầm ngớ ngẩn. Tôi sẽ sửa câu trả lời. –

2

Vâng, tôi nghĩ rằng somewhere (mkMp mySpecificFunction) nên làm điều đó, nếu bạn sử dụng MonadPlus monad và làm cho nó thành công khi nó tìm thấy những gì bạn đang tìm kiếm.

Một lựa chọn linh hoạt nhưng hacky là sử dụng everywhereM với một đơn nguyên nhà nước có thể lưu trữ một Boolean (hoặc lưu trữ Maybe MyFunc hoặc bất cứ điều gì) và áp dụng việc chuyển đổi tùy thuộc vào tình trạng bị True hoặc Just myFunc - theo cách đó, khi bạn đã làm xong (ví dụ: sau khi áp dụng phép biến đổi một lần), bạn chỉ cần thay đổi trạng thái thành False/Nothing.

+0

Cảm ơn, @NeilBrown. Bạn có thể xây dựng phương pháp tiếp cận đầu tiên nhiều hơn một chút không? Tôi tìm thấy [thư viện] này (http://web.engr.oregonstate.edu/~erwig/reclib/), cũng sử dụng MonadPlus để chỉ định chuyển đổi giờ, nhưng nó không sử dụng 'đâu đó'. Cách tiếp cận thứ hai hoạt động, nhưng chúng tôi không muốn đi tuyến đường đó. – user1546806

Các vấn đề liên quan