这与解析问题非常相似,所以让我们从解析器 monad 中获得提示:
-
match 应该返回解析的所有可能延续的列表
- 如果匹配失败,它应该返回空列表
- 当前的分配集将是必须进行计算的状态
要了解我们的发展方向,让我们假设我们有这个神奇的单子。尝试将“abba”与字符串匹配将如下所示:
matchAbba = do
var 'a'
var 'b'
var 'b'
var 'a'
return () -- or whatever you want to return
test = runMatch matchAbba "redbluebluered"
原来这个 monad 是 List monad 之上的 State monad。 List monad 提供回溯,State monad 携带当前的分配和输入。
代码如下:
import Data.List
import Control.Monad
import Control.Monad.State
import Control.Monad.Trans
import Data.Maybe
import qualified Data.Map as M
import Data.Monoid
type Assigns = M.Map Char String
splits xs = tail $ zip (inits xs) (tails xs)
var p = do
(assigns,input) <- get
guard $ (not . null) input
case M.lookup p assigns of
Nothing -> do (a,b) <- lift $ splits input
let assigns' = M.insert p a assigns
put (assigns', b)
return a
Just t -> do guard $ isPrefixOf t input
let inp' = drop (length t) input
put (assigns, inp')
return t
matchAbba :: StateT (Assigns, String) [] Assigns
matchAbba = do
var 'a'
var 'b'
var 'b'
var 'a'
(assigns,_) <- get
return assigns
test1 = evalStateT matchAbba (M.empty, "xyyx")
test2 = evalStateT matchAbba (M.empty, "xyy")
test3 = evalStateT matchAbba (M.empty, "redbluebluered")
matches :: String -> String -> [Assigns]
matches pattern input = evalStateT monad (M.empty,input)
where monad :: StateT (Assigns, String) [] Assigns
monad = do sequence $ map var pattern
(assigns,_) <- get
return assigns
试试,例如:
matches "ab" "xyz"
-- [fromList [('a',"x"),('b',"y")],fromList [('a',"x"),('b',"yz")],fromList [('a',"xy"),('b',"z")]]
要指出的另一件事是,将像“abba”这样的字符串转换为一元值do var'a'; var'b'; var 'b'; var 'a' 的代码很简单:
sequence $ map var "abba"
更新:正如@Sassa NF 指出的那样,要匹配您要定义的输入结尾:
matchEnd :: StateT (Assigns,String) [] ()
matchEnd = do
(assigns,input) <- get
guard $ null input
然后将其插入到 monad 中:
monad = do sequence $ map var pattern
matchEnd
(assigns,_) <- get
return assigns