Строите большую карту функций? (F #) - PullRequest
0 голосов
/ 02 июля 2018

Я пытаюсь создать консольное приложение F #, которое вызывает библиотеку для решения ряда задач по математике / программированию.

Мой подход заключается в создании большой карты решающих функций (в частности, Map<int, (unit -> int)>).

Затем приложение просто запрашивает целочисленный идентификатор и вызывает соответствующий решатель для отображения решения (решение должно запустить код + запустить таймер, поэтому нельзя просто сохранить строку).

Моя начальная настройка показана ниже. Однако я знаю, что это быстро запутается, когда я добавлю больше решений - после 100 решений у меня будет >> 1000 строк (и глупо добавлять их на карту по одной строке за раз).

Один из подходов, который я рассматриваю, - это создать файл .fs для каждой проблемы и вызвать модуль, например. Problem001. Тогда у меня было бы ~ 100 строк в функции построения карты (например, Map.Add(1, Problem001.solver)).

Мой вопрос: является ли вышеуказанная идея лучшим подходом (и если да, то есть ли более чистый способ объединения всех различных модулей в одну карту?)

А если нет, какой подход лучше?

Библиотека классов: 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)

Консольное приложение: Program.fs

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

1 Ответ

0 голосов
/ 02 июля 2018

Я бы предложил создать тип, содержащий заголовок, описание и решение проблемы. Затем я бы создал один или несколько модулей, содержащих функции, которые возвращают решения для каждой проблемы, используя стандартное соглашение об именах, например problemN, где N - это problemId. С этим определением я просто использовал бы отражение, чтобы найти функцию, которая возвращает решение для данной проблемы, и вызвать ее:

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"

Вы можете вернуть экземпляр Problem вместо его печати в вашей реальной библиотеке, но, как определено, вы бы назвали его так:

printSolution 1

И это напечатало бы следующее:

Problem 1:  #1
    The first problem        
    Solution = 42

EDIT

Сочетание ответа на вопрос 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

Самым большим изменением, кроме использования атрибутов для идентификации решений и модулей, которые их содержат, является рефакторинг логики поиска в ее собственную функцию. Теперь это возвращает Map<int, (unit -> Problem)>, поэтому вам нужно только просмотреть сборку и найти решения по их атрибутам один раз, затем вы можете использовать карту для поиска решений для каждой проблемы.

Использование и вывод функции printSolution остаются прежними:

printSolution 2

Problem 2:  #2
    The second problem
    Solution = 17
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...