如何解决Haskell中用于DSL目的的智能链接运营商
我有一个State
总和类型,我需要评估此类状态列表中序列的有效性:
data State = Running | Idle | Stopped | Heating | Broken | Paused | Starting | Stopping
seq = [Stopped,Starting,Running,Paused,Idle,Stopping,Stopped]
对于seq
的每个元素,我需要评估它是否与它前面的一个或多个项目兼容。通过i
中的每个seq
,检查seq !! (i-1)
和(可选)seq !! (i-2)
的值,这非常容易。
但是,出于可读性和维护目的,我试图基于链接运算符构建一个微型DSL,以便对于seq
中的每个元素,我可以根据其评估其是否有效。之前的顺序。
以下玩具示例:
-- current/naive implementation
-- (real-world follows the same model but is much more complex)
isValid :: [State] -> Int -> Bool
isValid seq i =
ite (curr == Running) (prev1 `elem` [Starting,Paused])
$ ite (curr == Idle) (prev2 `elem` [Paused] && prev1 `elem` [Running])
$ ite (curr == Broken) (prev3 `elem` [Running] && prev2 `elem` [Heating] && prev1 `elem` [Heating])
$ False
where curr = seq !! i
prev1 = seq !! (i-1)
prev2 = seq !! (i-2)
-- "ideal" implementation using a (|>) chaining operator
-- (with some syntactic liberties)
isValid :: [State] -> Int -> Bool
isValid seq i =
Running => [Starting,Paused]
$ Idle => [Paused] |> [Running]
$ Broken => [Running] |> [Heating] |> [Heating]
where (=>) curr prev = -- ...
(|>) prev1 prev2 = -- ...
我最苦苦挣扎的部分是如何确保(|>)
了解是否必须查找i-1
与i-2
或i-3
,具体取决于次被链接,所以:
[Starting] -- seq !! (i-1) == Starting
[Starting] |> [Running] -- seq !! (i-2) == Starting && seq !! (i-1) == Running
[Starting] |> [Running] |> [Idle] -- seq !! (i-3) == Starting && seq !! (i-2) == Running && seq !! (i-1) == Idle
我并没有特别喜欢下面的“理想”版本中的完全语法糖,但是欢迎任何更接近它的想法或方法。
解决方法
您的DSL是在上下文(它们在输入的不同部分进行评估)上的谓词(他们说是对还是错)的语言。 / p>
尤其是,上下文由输入和指向其中的索引组成:
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
,而是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,e]")
-- - New context: [b,a]
allContexts :: [State] -> [Context]
allContexts seq = init (tails (reverse seq)) -- non-empty prefixes,reversed
您必须更新所有组合器,但是isValid
的定义应保持不变。 (供读者锻炼。)
请考虑反向处理您的列表,并使用 for(Map.Entry<String,Object> entry : map.entrySet()){
Person p = new Person();
p.getClass().getDeclaredField(entry.getKey()).setAccessible(true);
p.getClass().getDeclaredField(entry.getKey()).set(entry.getClass(),entry.getValue());
p.getClass().getDeclaredField(entry.getKey()).setAccessible(false);
}
获取前缀。所以:
tails
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。