我想编写一个rename
函数来替换String
中的AST
名称(代表层次标识符),其中GUID
的名称(整数)来自于携带的符号表作为Renamer
monad中的隐藏状态。
我有一个AST a
类型,它通过名称类型进行参数化。 AST叶子中的名称类型为Name a
:
data Name a = Name a
这使得使用SYB变换器轻松定位它们。
解析器是键入的(为简洁起见忽略了出错的可能性):
parse :: String -> AST String
我希望输入rename
函数:
rename :: AST String -> Renamer (AST GUID)
是否可以使用SYB通过变换器将所有Name String
转换为Name GUID
:
resolveName :: Name String -> Renamer (Name GUID)
以及从c String
到c GUID
的所有其他值,通过转换它们的子节点,并将它们与相同的构造函数一起粘贴回来,虽然它们具有不同的类型参数?
everywhereM
功能接近我想要的功能,但它只能变换c a -> m (c a)
而不能变换c a -> m (c b)
。
我的后备解决方案(手动编写样板)除了从AST
中删除类型参数,并像这样定义Name
:
data Name = StrName String
| GuidName GUID
以便输入重命名:
rename :: AST -> Renamer AST
使其与everywhereM
一起使用。但是,这可能会导致AST
在重命名后仍然可以保留StrName
。我想使用类型系统正式捕获重命名的AST
只能保留GUID
名称的事实。
答案 0 :(得分:2)
一个解决方案(可能效率低于您希望的)将使您的AST成为Functor
,Data
和Typeable
的实例(GHC 7可能可能派生所有这些为你)然后做:
import Data.Generics.Uniplate.Data(universeBi) -- from the uniplate package
import qualified Data.Map as Map
rename :: AST String -> Renamer (AST GUID)
rename x = do
let names = nub $ universeBi x :: [Name String]
guids <- mapM resolveName names
let mp = Map.fromList $ zip names guids
return $ fmap (mp Map.!) x
两点:
Name
中的resolveName
位,但我怀疑它是。universeBi
等效内容,但我发现了解Uniplate版本要容易得多。