.net 避免函数和其他对象之间相互递归的模式?

6kkfgxo0  于 2023-06-25  发布在  .NET
关注(0)|答案(2)|浏览(110)

我开始写一个玩具命令行数据存储的乐趣。我目前将解释器的所有命令都存储在一个Map中,该Map将命令的字符串名称Map为键,并将要执行的方法Map为值。但是,DisplayHelp命令需要从CommandsMap引用自身。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方法和CommandsMap之间。我试图使用and关键字来相互定义这些,但这似乎是一种不好的做法。这里使用的正确模式是什么,或者问题本身是以一种完全不同的方式解决的?

2skhul33

2skhul331#

你可以通过使用@Fyodor Soikin建议的方法来摆脱相互递归,它通过引入参数来抽象具体引用。在我们的例子中-从函数DisplayHelp抽象引用Commands

let private DisplayHelp(shellState : ShellState, commands) = // introduce parameter commands
    printfn "Available commands: "
    commands
    |> Map.map(fun k _->printfn "%s" k)
    |> ignore
    shellState
    
let rec private Commands =
    [
        "exit", Exit
        "help", fun state -> DisplayHelp(state, Commands) // pass Commands to DisplayHelp as parameter 
        "store", StoreItem
        "list", ListItems
    ]
    |> Map.ofList

从理论Angular 来看,消除Commands定义中的递归可能也是有趣的。作为一个选项,它可以通过使用Fixed-point combinator来完成。其思想是在目标函数(createCommands)中引入额外的参数(f),该参数应该用来代替递归调用。在这种情况下,需要将目标函数(createCommands) Package 成定点组合器(fix)。函数fix本身是递归的(至少在F#实现中是这样),但它可以被普遍使用以消除任何形式的递归:

// Declare fixed point combinator
let rec fix' f = lazy f (fix' f)
let fix f = (fix' f).Value

let private DisplayHelp(shellState : ShellState, commands) =
    printfn "Available commands: "
    commands
    |> Map.map(fun k _->printfn "%s" k)
    |> ignore
    shellState
    
let private createCommands (f: Lazy<_>) () = // transform value to non-recursive function and add f parameter for self referncing
    [
        "exit", Exit
        "help", fun state -> DisplayHelp(state, f.Value ()) // call f function (unwrapped from lazy) insted of recursive call
        "store", StoreItem
        "list", ListItems
    ]
    |> Map.ofList

let Commands = fix createCommands () // wrap function with fixed point combinator and call it
luaexgnf

luaexgnf2#

我决定接受@Fyodor Soikin的评论,但最终还是使用了一些递归定义,因为它降低了复杂性,为更大的灵活性铺平了道路。@Gennadii Saltyshchak的回答真的很有趣,但经过一番思考,将ShellCommands打包到ShellState记录中铺平了道路,也可以跟踪像ShellConfig这样的东西,其中某些命令可以打开和关闭各种配置标志,命令可以禁用,等等(这显然要求每个ShellCommand最终都变成一个记录/对象类型,带有文本名称,也许还有它们自己的帮助信息,在某种程度上更多的OOP范式中移动)。我最终使用 * 并没有 * 避免递归,但它仍然比我最初试图挖掘的代码更干净。代码如下:

type private ShellCommand = ShellState -> ShellState
and private ShellState =
    {
        PromptInput: string
        Command: string
        Arguments: string list
        ItemsStored: string list
        Running: bool
        AvailableCommands: Map<string, ShellCommand>
    }
    with 
    static member init(availableCommands: Map<string, ShellCommand>) : ShellState =
        {
            PromptInput = ""
            Command = ""
            Arguments = []
            ItemsStored = []
            Running = true
            AvailableCommands = availableCommands
        }

/// ... snip ...

let private DisplayUnrecognizedCommand : ShellCommand = fun shellState ->
    eprintf $"Command not recognized: {shellState.Command}"
    shellState

let private DisplayHelp : ShellCommand = fun shellState ->
    printfn "Available commands: "

    shellState.AvailableCommands
    |> Map.map (fun k _ -> printfn "%s" k)
    |> ignore
    
    shellState

let private InterpCommand (shellState : ShellState) =
    let nextState =
        match shellState.AvailableCommands.Keys.Contains shellState.Command with
        | true ->
            shellState
            |> shellState.AvailableCommands[shellState.Command]
        | false ->
            shellState
            |> DisplayUnrecognizedCommand
    // return
    nextState
    |> ResetStateInputs

let private Prompt (shellState: ShellState) =
    printf "> "
    let input = Console.ReadLine().Trim()
    { shellState with 
        PromptInput = input }

let rec private Run (shellState : ShellState) =
    if(shellState.Running) then
        shellState
        |> Prompt
        |> ParseInput
        |> InterpCommand
        |> Run
    else
        ()

let private AvailableCommands =
    [
        "exit", Exit
        "help", DisplayHelp
        "store", StoreItem
        "list", ListItems
    ]
    |> Map.ofList

let StartShell () =
    Run(ShellState.init(AvailableCommands))

(然而,由于我没有像原来的问题那样避免递归,我将接受另一个答案作为正确答案:)

相关问题