建立一个大型的功能图?(F#)

ifo*_*o20 4 f#

我正在尝试构建一个F#控制台应用程序,它调用库来解决数学/编程问题的集合.

我的方法涉及构建一个求解器函数的大型映射(特别是a Map<int, (unit -> int)>).

然后应用程序只需要一个整数id并调用相应的解算器来显示解决方案(解决方案必须运行代码+运行一个计时器,因此不能只存储一个字符串).

我的初始设置如下所示.但是我知道随着我添加更多解决方案,这将很快变得混乱 - 在100个解决方案之后,我将拥有1,000条线路(并且看起来很难将它们一次添加到Map一行).

我正在考虑的一种方法是为每个问题创建一个.fs文件,并调用模块,例如Problem001.然后我会在Map构建函数中有~100行(例如Map.Add(1, Problem001.solver)).

我的问题是:上述想法是最好的方法(如果是这样,是否有更简洁的方法将所有不同的模块组合到一个Map中?)

如果没有,最好的方法是什么?

类库:Library.fs

namespace Library

module Problems = 
    let Titles = 
         Map.empty
             .Add(1, "Title1") // etc
    let Descriptions = 
         Map.empty
             .Add(1, "Desc1") // etc

module Solutions = 
    let solution1 () =
        // logic & solution annotation, unique to each problem
        printfn "Solution annotation/text"
        ans // return integer answer

    let solution2 () = // etc
        printfn "blah"
        ans

    let solvers = 
        Map.empty
            .Add(1, solution1)
            .Add(2, solution2)
Run Code Online (Sandbox Code Playgroud)

控制台应用程序:Program.fs

let main argv = 
    // request & validate input
    printfn Problems.Descriptions.[problemId]
    let solver = Solutions.solvers.[problemId]
    solver()
Run Code Online (Sandbox Code Playgroud)

Aar*_*ach 7

我建议创建一个类型来包含问题标题,描述和解决方案.然后,我会创建包含该返回的解决方案,每个问题的功能,使用标准命名约定,诸如一个或多个模块problemN,其中NproblemId.有了这个定义,我只会使用反射来找到返回给定问题的解决方案的函数,并调用它:

open System.Reflection

type Problem =
    { 
        Title: string
        Description: string
        Solution: int  // This could even be a function, int -> int or whatever
    }

module Solutions =
    let problem1 () =
        { Title = "#1"
          Description = "The first problem"
          Solution = 42
        }

let printSolution problemId =
    match Assembly.GetExecutingAssembly().GetTypes() |> Array.tryFind (fun t -> t.Name = "Solutions") with
    | Some solutions ->
        match solutions.GetMethod(sprintf "problem%d" problemId) with
        | null -> 
            printfn "Solution to Problem %d not found" problemId
        | func -> 
            let problem = func.Invoke(null, [||]) |> unbox<Problem>
            printfn "Problem %d:  %s" problemId problem.Title
            printfn "    %s" problem.Description
            printfn "    Solution = %d" problem.Solution
    | None -> printfn "Solutions module not found"
Run Code Online (Sandbox Code Playgroud)

您可以Problem在真实的库中返回实例而不是打印它,但是按照定义,您可以这样称呼它:

printSolution 1
Run Code Online (Sandbox Code Playgroud)

它将打印以下内容:

Problem 1:  #1
    The first problem        
    Solution = 42
Run Code Online (Sandbox Code Playgroud)

编辑

结合评论中ifo20问题的答案和cadull使用自定义属性的强烈建议,这里有一个更灵活的解决方案,允许在许多不同的模块/文件中定义解决方案,并且不依赖于命名约定来查找它们.

open System
open System.Reflection

type Problem =
    { 
        Title: string
        Description: string
        Solution: int  // This could even be a function, int -> int or whatever
    }

[<AllowNullLiteral>]
type SolutionModuleAttribute () =
    inherit Attribute()

[<AllowNullLiteral>]
type SolutionAttribute (problemId: int) =
    inherit Attribute()
    member __.ProblemId = problemId

[<SolutionModule>]
module SomeSolutions =
    [<Solution(1)>]
    let firstProblem () =
        { Title = "#1"
          Description = "The first problem"
          Solution = 42
        }

[<SolutionModule>]
module MoreSolutions =
    [<Solution(2)>]
    let secondProblem () =
        { Title = "#2"
          Description = "The second problem"
          Solution = 17
        }


let findSolutions () =
    Assembly.GetExecutingAssembly().GetTypes() 
    |> Array.filter (fun t -> t.GetCustomAttribute<SolutionModuleAttribute>() |> isNull |> not)
    |> Array.collect (fun t -> t.GetMethods())
    |> Array.choose (fun m -> 
        match m.GetCustomAttribute<SolutionAttribute>() with
        | null -> None
        | attribute -> Some (attribute.ProblemId, fun () -> m.Invoke(null, [||]) |> unbox<Problem>))
    |> Map.ofArray


let printSolution =
    let solutions = findSolutions()
    fun problemId ->
        match solutions |> Map.tryFind problemId with
        | Some func ->
            let problem = func()
            printfn "Problem %d:  %s" problemId problem.Title
            printfn "    %s" problem.Description
            printfn "    Solution = %d" problem.Solution
        | None -> 
            printfn "Solution for Problem %d not found" problemId
Run Code Online (Sandbox Code Playgroud)

除了使用属性来识别解决方案和包含它们的模块之外,最大的变化是将查找逻辑重构为其自己的函数.现在返回一个,Map<int, (unit -> Problem)>所以你只需遍历程序集并按属性找到解决方案一次,然后就可以使用map查找每个问题的解决方案.

printSolution函数的用法和输出保持不变:

printSolution 2

Problem 2:  #2
    The second problem
    Solution = 17
Run Code Online (Sandbox Code Playgroud)