我开始写一个玩具命令行数据存储的乐趣。我目前将解释器的所有命令都存储在一个Map中,该Map将命令的字符串名称Map为键,并将要执行的方法Map为值。但是,DisplayHelp
命令需要从Commands
的Map
引用自身。DisplayHelp
方法、Commands
Map和InterpCommand
代码看起来都像这样:
let private DisplayHelp (shellState : ShellState) =
printfn "Available commands: "
Commands // <- This is where mutual recursive reference occurs
|> Map.map (fun k _ -> printfn "%s" k)
|> ignore
shellState
let private Commands =
[
"exit", Exit
"help", DisplayHelp // <- This is the other place where mutual reference recursion occurs
"store", StoreItem
"list", ListItems
]
|> Map.ofList
let private InterpCommand (shellState : ShellState) =
let nextState =
match Commands.Keys.Contains shellState.Command with
| true ->
shellState
|> Commands[shellState.Command]
| false ->
shellState
|> DisplayUnrecognizedCommand
// return
nextState
|> ResetStateInputs
可以看到,相互递归发生在DisplayHelp
方法和Commands
Map之间。我试图使用and
关键字来相互定义这些,但这似乎是一种不好的做法。这里使用的正确模式是什么,或者问题本身是以一种完全不同的方式解决的?
2条答案
按热度按时间2skhul331#
你可以通过使用@Fyodor Soikin建议的方法来摆脱相互递归,它通过引入参数来抽象具体引用。在我们的例子中-从函数
DisplayHelp
抽象引用Commands
:从理论Angular 来看,消除
Commands
定义中的递归可能也是有趣的。作为一个选项,它可以通过使用Fixed-point combinator来完成。其思想是在目标函数(createCommands
)中引入额外的参数(f
),该参数应该用来代替递归调用。在这种情况下,需要将目标函数(createCommands
) Package 成定点组合器(fix
)。函数fix本身是递归的(至少在F#实现中是这样),但它可以被普遍使用以消除任何形式的递归:luaexgnf2#
我决定接受@Fyodor Soikin的评论,但最终还是使用了一些递归定义,因为它降低了复杂性,为更大的灵活性铺平了道路。@Gennadii Saltyshchak的回答真的很有趣,但经过一番思考,将
ShellCommands
打包到ShellState
记录中铺平了道路,也可以跟踪像ShellConfig
这样的东西,其中某些命令可以打开和关闭各种配置标志,命令可以禁用,等等(这显然要求每个ShellCommand
最终都变成一个记录/对象类型,带有文本名称,也许还有它们自己的帮助信息,在某种程度上更多的OOP范式中移动)。我最终使用 * 并没有 * 避免递归,但它仍然比我最初试图挖掘的代码更干净。代码如下:(然而,由于我没有像原来的问题那样避免递归,我将接受另一个答案作为正确答案:)