你的DSL是一种语言
谓词
(他们说某事是真是假)
上下文
(它们根据输入的不同部分进行评估)。
特别是
上下文
由输入和其中的索引组成:
type Context = ([State], Int)
type Predicate = Context -> Bool
然后,我们可以自上而下地研究语言,检查
isValid
从根。首先,它是一系列谓词。正如您所注意到的,每一行都是一个逻辑含义,如果任何含义被破坏,您希望谓词失败。所以我们从谓词的连词开始:
infixr 4 &&.
(&&.) :: Predicate -> Predicate -> Predicate
p &&. q = \c -> p c && q c
含义可以类似地定义,相应的布尔运算与
Ord
操作
(<=)
.
(=>.) :: Predicate -> Predicate -> Predicate
p =>. q = \c -> p c <= q c
在您提出的语法中
(=>)
实际上不是
Predicate
,但是a
State
。你可以把它分解成二进制运算
(=>.)
在我们刚刚定义的谓词上,加上一个将状态视为谓词的操作。当你写作时
Running => ...
,你试图说“如果当前状态是
Running
那么。。。“那么一个州
s
对应于谓词“当前状态为
s
":
current :: State -> Predicate
current s = \(seq, i) ->
-- Naive version: (seq !! i) == s
index i seq == Just s
-- Total indexing function
index :: Int -> [a] -> Maybe a
index i seq = listToMaybe (drop i seq)
我们还想先谈谈当前的州。一种方法是转换谓词,在指向前一状态的修改后的上下文中对其进行求值:
prev :: Predicate -> Predicate
prev p = \(seq, i) -> p (seq, i-1)
在右侧,您还有状态列表,这意味着如果当前状态是其中任何一个,则有一个匹配的谓词:
currentIn :: [State] -> Predicate
currentIn ss = \(seq, i) ->
case index i seq of
Nothing -> False
Just s -> s `elem` ss
有了所有这些基本的构建块,我们可以构建更高级的组合子,更接近你想要的语法。
(|>)
在列表中查找当前状态(第一个参数),并将其第二个参数向后移动:
infixr 9 |>
(|>) :: [State] -> Predicate -> Predicate
ss |> q = currentIn ss &&. prev q
-- End delimiter/identity element for `(|>)`
true :: Predicate
true = \_ -> True
(=>+)
是一个左侧有一个状态的含义,但也会移动第二个参数,开始直接查看前一个状态(避免保留语法
=>
)
infixr 8 =>+
(=>+) :: State -> Predicate -> Predicate
s =>+ q = current s &&. prev q
您的示例的相关操作是
(&&.)
,
(|>)
,
(=>+)
,我们可以注意运算符优先级,避免使用括号。
isValid :: Predicate
isValid =
Running =>+ [Starting, Idle, Paused] |> true
&&. Idle =>+ [Paused] |> [Running] |> true
&&. Broken =>+ [Running] |> [Heating] |> [Heating] |> true
最后,我们需要从序列中生成所有相关上下文,以验证整个序列:
allContexts :: [State] -> [Context]
allContexts seq = [(seq, i) | i <- [0 .. length seq - 1]]
validate :: [State] -> Bool
validate seq = all isValid (allContexts seq)
这应该足以让事情正常进行,但人们可能会有一个很大的抱怨,那就是所有这些列表查找都很昂贵。将上下文的表示更改为更适合DSL操作的表示将更有效。值得注意的是,我们希望能够查看当前状态以及之前的状态。因此,更好的表示应该更直接地提供这些状态:
type Context = [State] -- A reversed prefix of the whole sequence, so the current state is at the head, and other states precede it.
-- Example:
-- - Old context: ([a,b,c,d,e], 1) ("the element at position 1 in the list [a,b,c,d,e]")
-- - New context: [b,a]
allContexts :: [State] -> [Context]
allContexts seq = init (tails (reverse seq)) -- non-empty prefixes, reversed
你必须更新所有的组合子,但
isValid
应该保持不变。(为读者练习。)