能否用Elixir Spark定义递归实体结构?DSL嵌套设计方案咨询
解决Spark DSL中递归实体的编译无限循环问题
当你在Spark定义递归DSL实体时,直接在entities中调用ReceiveCmd.cmd()会引发编译阶段的无限递归——因为每次定义ReceiveCmd实体时,都会立即求值子实体的cmd()函数,进而再次触发自身的定义逻辑,陷入死循环。
方案一:使用延迟加载的实体引用
Spark支持通过「模块+函数」的引用方式延迟加载子实体,避免在定义阶段直接递归求值。修改你的ReceiveCmd定义:
defmodule Im.Dsl.Entities.ReceiveCmd do def cmd(), do: %Spark.Dsl.Entity{ name: :rcv, describe: "Receive command.", target: Im.Commands.Receive, # 保留其他原有配置 entities: [body: [ Im.Dsl.Entities.IfCondCmd.cmd(), {__MODULE__, :cmd} # 用模块+函数引用替代直接调用 ]] } end
这种方式下,Spark只会在实际解析DSL结构时才调用ReceiveCmd.cmd()获取子实体定义,不会在编译阶段无限递归,完全支持你需要的嵌套rcv语法:
snd user1, commitRequest snd user2, commitRequest rcv {message1, some_user} do rcv {message2, some_other_user} do .... end end
方案二:引入通用容器实体
如果延迟加载的方式不符合你的需求,可以定义一个通用的Block实体作为命令容器,将所有可嵌套的命令(包括rcv、if)纳入其中,避免直接递归引用自身:
defmodule Im.Dsl.Entities.Block do def cmd(), do: %Spark.Dsl.Entity{ name: :block, describe: "容器块,用于嵌套命令", entities: [commands: [ Im.Dsl.Entities.IfCondCmd.cmd(), Im.Dsl.Entities.ReceiveCmd.cmd() ]] } end # 修改ReceiveCmd的实体定义 defmodule Im.Dsl.Entities.ReceiveCmd do def cmd(), do: %Spark.Dsl.Entity{ name: :rcv, describe: "Receive command.", target: Im.Commands.Receive, entities: [body: [Im.Dsl.Entities.Block.cmd()]] } end
对应的DSL写法稍作调整,但同样支持嵌套逻辑:
snd user1, commitRequest snd user2, commitRequest rcv {message1, some_user} do block do rcv {message2, some_other_user} do block do .... end end end end
总结
第一种方案更贴合你的原始需求,无需修改DSL语法,直接通过延迟加载解决递归编译问题;第二种方案适合需要统一管理所有嵌套命令的场景。Spark本身完全支持递归DSL结构,核心是避免在实体定义阶段立即触发递归求值。
内容的提问来源于stack exchange,提问作者kdmnk
相关产品推荐
相关产品推荐

