Haskell中用于DSL目的的智能链接运营商

如何解决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-1i-2i-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 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 &lt;select id=&quot;xxx&quot;&gt; SELECT di.id, di.name, di.work_type, di.updated... &lt;where&gt; &lt;if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 &lt;property name=&quot;dynamic.classpath&quot; value=&quot;tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-