用于DSL的Haskell智能链运营商

用于DSL的Haskell智能链运营商,haskell,operators,dsl,Haskell,Operators,Dsl,我有一个状态和类型,我需要评估这些状态列表中序列的有效性: 数据状态=运行|空闲|停止|加热|中断|暂停|启动|停止 seq=[停止、启动、运行、暂停、运行、空闲、停止、停止] 对于seq的每个元素,我需要评估它是否与前面的一个或多个项目兼容。对于seq的每个i,检查seq的值是相当容易的!!(i-1)和(可选)seq!!(i-2) 然而,出于可读性和维护目的,我正在尝试基于链式操作符构建一个迷你DSL,这样对于seq中的每个元素,我都可以根据前面的顺序来评估它是否有效 下面的玩具示例如下:

我有一个
状态
和类型,我需要评估这些状态列表中序列的有效性:

数据状态=运行|空闲|停止|加热|中断|暂停|启动|停止
seq=[停止、启动、运行、暂停、运行、空闲、停止、停止]
对于
seq
的每个元素,我需要评估它是否与前面的一个或多个项目兼容。对于
seq
的每个
i
,检查
seq的值是相当容易的!!(i-1)
和(可选)
seq!!(i-2)

然而,出于可读性和维护目的,我正在尝试基于链式操作符构建一个迷你DSL,这样对于
seq
中的每个元素,我都可以根据前面的顺序来评估它是否有效

下面的玩具示例如下:

——当前/原始实现
--(现实世界遵循相同的模型,但要复杂得多)
isValid::[State]->Int->Bool
有效序号i=
ite(curr==运行)(prev1`elem`[启动、空闲、暂停])
$ite(curr==Idle)(prev2`elem`[暂停]&prev1`elem`[运行])
$ite(curr==损坏)(prev3`elem`[运行]&prev2`elem`[加热]&prev1`elem`[加热])
$False
其中curr=seq!!我
prev1=seq!!(i-1)
prev2=seq!!(i-2)
--使用(|>)链接运算符的“理想”实现
--(有一些句法自由)
isValid::[State]->Int->Bool
有效序号i=
运行=>[启动、空闲、暂停]
$Idle=>[暂停]|>[运行]
$BREAKED=>[运行]|>[加热]|>[加热]
其中(=>)curr prev=--。。。
(|>)prev1 prev2=--。。。
我最挣扎的部分是,如何确保
(|>)
了解它是否必须查找
I-1
I-2
I-3
,这取决于它的链接次数,以便:

[开始]--顺序!!(i-1)=启动
[启动]|>[运行]--顺序!!(i-2)=开始和结束!!(i-1)=运行
[启动]|>[运行]|>[空闲]--顺序!!(i-3)=开始和结束!!(i-2)=正在运行!!(i-1)=怠速

我并不特别喜欢下面的确切地说与上面的“理想”版本一样多的语法糖分,但是任何能更接近它的想法或方法都是受欢迎的。

考虑反向处理列表,并使用
尾部
来获得前缀。因此:

isValidPrefix :: [State] -> Bool
isValidPrefix seq = case seq of
    Running:prev:_ -> prev `elem` [Starting, Idle, Paused]
    Idle:Paused:Running:_ -> True
    Broken:Running:Heating:Heating:_ -> True
    [] -> True -- presumably?
    [_] -> True -- also presumed
    _ -> False

isValidSequence :: [State] -> Bool
isValidSequence = all isValidPrefix . tails . reverse
您的DSL是上下文上的谓词语言(它们表示某个内容是真是假)(它们根据输入的不同部分进行评估)

特别是,上下文由输入和索引组成:

type Context = ([State], Int)
type Predicate = Context -> Bool
然后我们可以自上而下地处理语言,从根目录检查
isValid
的定义。首先,它是一个谓词序列。正如您所注意到的,每一行都是一个逻辑蕴涵,如果任何蕴涵被破坏,您希望谓词失败。所以我们从谓词的连接开始:

infixr 4&&。
(&&&)::谓词->谓词->谓词
p&&。q=\c->p c和q c
可以类似地定义蕴涵,相应的布尔运算与
Ord
运算
(.)一致:谓词->谓词->谓词
p=>。q=\c->pc)
实际上不是一个
谓词
,而是一个
状态
。您可以将其分解为我们刚刚定义的谓词上的二进制操作
(=>)
,以及将状态视为谓词的操作。当您编写
Running=>…
时,您试图说“如果当前状态是
Running
,那么…”。因此,状态
s
对应于谓词“当前状态是
s
”:

我们还想在当前状态之前讨论状态。一种方法是转换谓词,以便在指向前一状态的修改上下文中对其求值:

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
(=>+)
是一个在其左侧带有状态的蕴涵,但也会移动第二个参数以开始直接查看前一个状态(避免保留语法
=>

您的示例的相关操作是
(&&&.)
(|>)
(=>+)
,我们可以注意运算符优先级以避免使用括号

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)

您必须更新所有组合符,但
的定义应保持不变。(读者练习。)

这不是一个完整的答案,但由于这是一个序列识别问题,我建议使用解析库。例如,使用,您可以编写类似于
(sym Running*>psym(`elem`[开始,空闲,暂停])*>pure())(sym Idle*>sym Running*>sym Paused*>pure())…
的代码,并使用
many
来贪婪地匹配整个序列(反向)。您可以对大多数parser combinator库使用几乎相同的代码,或者滚动您自己的
而不是字符串(State[State])
,但这更复杂&可能有些过分。
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)
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